Update 0.3.x for 0.3.11 (#455)
1//! Geometric primitives useful for layout
2
3use crate::style::{Dimension, FlexDirection};
4use crate::sys::f32_max;
5use core::ops::Add;
6
7#[cfg(feature = "grid")]
8use crate::axis::AbstractAxis;
9
10/// An axis-aligned UI rectangle
11#[derive(Debug, Copy, Clone, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13pub struct Rect<T> {
14 /// This can represent either the x-coordinate of the starting edge,
15 /// or the amount of padding on the starting side.
16 ///
17 /// The starting edge is the left edge when working with LTR text,
18 /// and the right edge when working with RTL text.
19 pub left: T,
20 /// This can represent either the x-coordinate of the ending edge,
21 /// or the amount of padding on the ending side.
22 ///
23 /// The ending edge is the right edge when working with LTR text,
24 /// and the left edge when working with RTL text.
25 pub right: T,
26 /// This can represent either the y-coordinate of the top edge,
27 /// or the amount of padding on the top side.
28 pub top: T,
29 /// This can represent either the y-coordinate of the bottom edge,
30 /// or the amount of padding on the bottom side.
31 pub bottom: T,
32}
33
34impl<T: Default> Default for Rect<T> {
35 fn default() -> Self {
36 Rect {
37 left: Default::default(),
38 right: Default::default(),
39 top: Default::default(),
40 bottom: Default::default(),
41 }
42 }
43}
44
45impl<U, T: Add<U>> Add<Rect<U>> for Rect<T> {
46 type Output = Rect<T::Output>;
47
48 fn add(self, rhs: Rect<U>) -> Self::Output {
49 Rect {
50 left: self.left + rhs.left,
51 right: self.right + rhs.right,
52 top: self.top + rhs.top,
53 bottom: self.bottom + rhs.bottom,
54 }
55 }
56}
57
58impl<T> Rect<T> {
59 /// Applies the function `f` to all four sides of the rect
60 ///
61 /// When applied to the left and right sides, the width is used
62 /// as the second parameter of `f`.
63 /// When applied to the top or bottom sides, the height is used instead.
64 pub(crate) fn zip_size<R, F, U>(self, size: Size<U>, f: F) -> Rect<R>
65 where
66 F: Fn(T, U) -> R,
67 U: Copy,
68 {
69 Rect {
70 left: f(self.left, size.width),
71 right: f(self.right, size.width),
72 top: f(self.top, size.height),
73 bottom: f(self.bottom, size.height),
74 }
75 }
76
77 /// Applies the function `f` to the left, right, top, and bottom properties
78 ///
79 /// This is used to transform a `Rect<T>` into a `Rect<R>`.
80 pub fn map<R, F>(self, f: F) -> Rect<R>
81 where
82 F: Fn(T) -> R,
83 {
84 Rect { top: f(self.top), bottom: f(self.bottom), left: f(self.left), right: f(self.right) }
85 }
86
87 /// Returns a `Line<T>` representing the left and right properties of the Rect
88 pub fn horizontal_components(self) -> Line<T> {
89 Line { start: self.left, end: self.right }
90 }
91
92 /// Returns a `Line<T>` containing the top and bottom properties of the Rect
93 pub fn vertical_components(self) -> Line<T> {
94 Line { start: self.top, end: self.bottom }
95 }
96}
97
98impl<T, U> Rect<T>
99where
100 T: Add<Output = U> + Copy + Clone,
101{
102 /// The sum of [`Rect.start`](Rect) and [`Rect.end`](Rect)
103 ///
104 /// This is typically used when computing total padding.
105 ///
106 /// **NOTE:** this is *not* the width of the rectangle.
107 #[inline(always)]
108 pub(crate) fn horizontal_axis_sum(&self) -> U {
109 self.left + self.right
110 }
111
112 /// The sum of [`Rect.top`](Rect) and [`Rect.bottom`](Rect)
113 ///
114 /// This is typically used when computing total padding.
115 ///
116 /// **NOTE:** this is *not* the height of the rectangle.
117 #[inline(always)]
118 pub(crate) fn vertical_axis_sum(&self) -> U {
119 self.top + self.bottom
120 }
121
122 /// Both horizontal_axis_sum and vertical_axis_sum as a Size<T>
123 ///
124 /// **NOTE:** this is *not* the width/height of the rectangle.
125 #[inline(always)]
126 #[allow(dead_code)] // Fixes spurious clippy warning: this function is used!
127 pub(crate) fn sum_axes(&self) -> Size<U> {
128 Size { width: self.horizontal_axis_sum(), height: self.vertical_axis_sum() }
129 }
130
131 /// The sum of the two fields of the [`Rect`] representing the main axis.
132 ///
133 /// This is typically used when computing total padding.
134 ///
135 /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::horizontal`].
136 /// Otherwise, this is [`Rect::vertical`].
137 pub(crate) fn main_axis_sum(&self, direction: FlexDirection) -> U {
138 if direction.is_row() {
139 self.horizontal_axis_sum()
140 } else {
141 self.vertical_axis_sum()
142 }
143 }
144
145 /// The sum of the two fields of the [`Rect`] representing the cross axis.
146 ///
147 /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::vertical`].
148 /// Otherwise, this is [`Rect::horizontal`].
149 pub(crate) fn cross_axis_sum(&self, direction: FlexDirection) -> U {
150 if direction.is_row() {
151 self.vertical_axis_sum()
152 } else {
153 self.horizontal_axis_sum()
154 }
155 }
156}
157
158impl<T> Rect<T>
159where
160 T: Copy + Clone,
161{
162 /// The `start` or `top` value of the [`Rect`], from the perspective of the main layout axis
163 pub(crate) fn main_start(&self, direction: FlexDirection) -> T {
164 if direction.is_row() {
165 self.left
166 } else {
167 self.top
168 }
169 }
170
171 /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
172 pub(crate) fn main_end(&self, direction: FlexDirection) -> T {
173 if direction.is_row() {
174 self.right
175 } else {
176 self.bottom
177 }
178 }
179
180 /// The `start` or `top` value of the [`Rect`], from the perspective of the cross layout axis
181 pub(crate) fn cross_start(&self, direction: FlexDirection) -> T {
182 if direction.is_row() {
183 self.top
184 } else {
185 self.left
186 }
187 }
188
189 /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
190 pub(crate) fn cross_end(&self, direction: FlexDirection) -> T {
191 if direction.is_row() {
192 self.bottom
193 } else {
194 self.right
195 }
196 }
197}
198
199impl Rect<f32> {
200 /// Creates a new Rect with `0.0` as all parameters
201 pub const ZERO: Rect<f32> = Self { left: 0.0, right: 0.0, top: 0.0, bottom: 0.0 };
202
203 /// Creates a new Rect
204 #[must_use]
205 pub const fn new(start: f32, end: f32, top: f32, bottom: f32) -> Self {
206 Self { left: start, right: end, top, bottom }
207 }
208}
209
210/// An abstract "line". Represents any type that has a start and an end
211#[derive(Debug, Copy, Clone, PartialEq, Eq)]
212#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
213#[cfg_attr(feature = "serde", serde(default))]
214pub struct Line<T> {
215 /// The start position of a line
216 pub start: T,
217 /// The end position of a line
218 pub end: T,
219}
220
221impl<T> Line<T> {
222 /// Applies the function `f` to both the width and height
223 ///
224 /// This is used to transform a `Line<T>` into a `Line<R>`.
225 pub fn map<R, F>(self, f: F) -> Line<R>
226 where
227 F: Fn(T) -> R,
228 {
229 Line { start: f(self.start), end: f(self.end) }
230 }
231}
232
233impl<T: Add + Copy> Line<T> {
234 /// Adds the start and end values together and returns the result
235 pub fn sum(&self) -> <T as Add>::Output {
236 self.start + self.end
237 }
238}
239
240/// The width and height of a [`Rect`]
241#[derive(Debug, Copy, Clone, PartialEq, Eq)]
242#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
243pub struct Size<T> {
244 /// The x extent of the rectangle
245 pub width: T,
246 /// The y extent of the rectangle
247 pub height: T,
248}
249
250impl<T: Default> Default for Size<T> {
251 fn default() -> Self {
252 Size { width: Default::default(), height: Default::default() }
253 }
254}
255
256// Generic Add impl for Size<T> + Size<U> where T + U has an Add impl
257impl<U, T: Add<U>> Add<Size<U>> for Size<T> {
258 type Output = Size<<T as Add<U>>::Output>;
259
260 fn add(self, rhs: Size<U>) -> Self::Output {
261 Size { width: self.width + rhs.width, height: self.height + rhs.height }
262 }
263}
264
265impl<T> Size<T> {
266 /// Applies the function `f` to both the width and height
267 ///
268 /// This is used to transform a `Size<T>` into a `Size<R>`.
269 pub fn map<R, F>(self, f: F) -> Size<R>
270 where
271 F: Fn(T) -> R,
272 {
273 Size { width: f(self.width), height: f(self.height) }
274 }
275
276 /// Applies the function `f` to the width
277 pub fn map_width<F>(self, f: F) -> Size<T>
278 where
279 F: Fn(T) -> T,
280 {
281 Size { width: f(self.width), height: self.height }
282 }
283
284 /// Applies the function `f` to the height
285 pub fn map_height<F>(self, f: F) -> Size<T>
286 where
287 F: Fn(T) -> T,
288 {
289 Size { width: self.width, height: f(self.height) }
290 }
291
292 /// Applies the function `f` to both the width and height
293 /// of this value and another passed value
294 pub fn zip_map<Other, Ret, Func>(self, other: Size<Other>, f: Func) -> Size<Ret>
295 where
296 Func: Fn(T, Other) -> Ret,
297 {
298 Size { width: f(self.width, other.width), height: f(self.height, other.height) }
299 }
300
301 /// Sets the extent of the main layout axis
302 ///
303 /// Whether this is the width or height depends on the `direction` provided
304 pub(crate) fn set_main(&mut self, direction: FlexDirection, value: T) {
305 if direction.is_row() {
306 self.width = value
307 } else {
308 self.height = value
309 }
310 }
311
312 /// Sets the extent of the cross layout axis
313 ///
314 /// Whether this is the width or height depends on the `direction` provided
315 pub(crate) fn set_cross(&mut self, direction: FlexDirection, value: T) {
316 if direction.is_row() {
317 self.height = value
318 } else {
319 self.width = value
320 }
321 }
322
323 /// Creates a new value of type Self with the main axis set to value provided
324 ///
325 /// Whether this is the width or height depends on the `direction` provided
326 #[allow(dead_code)]
327 pub(crate) fn with_main(self, direction: FlexDirection, value: T) -> Self {
328 let mut new = self;
329 if direction.is_row() {
330 new.width = value
331 } else {
332 new.height = value
333 }
334 new
335 }
336
337 /// Creates a new value of type Self with the cross axis set to value provided
338 ///
339 /// Whether this is the width or height depends on the `direction` provided
340 pub(crate) fn with_cross(self, direction: FlexDirection, value: T) -> Self {
341 let mut new = self;
342 if direction.is_row() {
343 new.height = value
344 } else {
345 new.width = value
346 }
347 new
348 }
349
350 /// Gets the extent of the main layout axis
351 ///
352 /// Whether this is the width or height depends on the `direction` provided
353 pub(crate) fn main(self, direction: FlexDirection) -> T {
354 if direction.is_row() {
355 self.width
356 } else {
357 self.height
358 }
359 }
360
361 /// Gets the extent of the cross layout axis
362 ///
363 /// Whether this is the width or height depends on the `direction` provided
364 pub(crate) fn cross(self, direction: FlexDirection) -> T {
365 if direction.is_row() {
366 self.height
367 } else {
368 self.width
369 }
370 }
371
372 /// Gets the extent of the specified layout axis
373 /// Whether this is the width or height depends on the `GridAxis` provided
374 #[cfg(feature = "grid")]
375 pub(crate) fn get(self, axis: AbstractAxis) -> T {
376 match axis {
377 AbstractAxis::Inline => self.width,
378 AbstractAxis::Block => self.height,
379 }
380 }
381
382 /// Sets the extent of the specified layout axis
383 /// Whether this is the width or height depends on the `GridAxis` provided
384 #[cfg(feature = "grid")]
385 pub(crate) fn set(&mut self, axis: AbstractAxis, value: T) {
386 match axis {
387 AbstractAxis::Inline => self.width = value,
388 AbstractAxis::Block => self.height = value,
389 }
390 }
391}
392
393impl Size<f32> {
394 /// A [`Size`] with zero width and height
395 pub const ZERO: Size<f32> = Self { width: 0.0, height: 0.0 };
396
397 /// Applies f32_max to each component separately
398 pub fn f32_max(self, rhs: Size<f32>) -> Size<f32> {
399 Size { width: f32_max(self.width, rhs.width), height: f32_max(self.height, rhs.height) }
400 }
401}
402
403impl Size<Option<f32>> {
404 /// A [`Size`] with `None` width and height
405 pub const NONE: Size<Option<f32>> = Self { width: None, height: None };
406
407 /// A [`Size<Option<f32>>`] with `Some(width)` and `Some(height)` as parameters
408 #[must_use]
409 pub const fn new(width: f32, height: f32) -> Self {
410 Size { width: Some(width), height: Some(height) }
411 }
412
413 /// Applies aspect_ratio (if one is supplied) to the Size:
414 /// - If width is `Some` but height is `None`, then height is computed from width and aspect_ratio
415 /// - If height is `Some` but width is `None`, then width is computed from height and aspect_ratio
416 ///
417 /// If aspect_ratio is `None` then this function simply returns self.
418 pub fn maybe_apply_aspect_ratio(self, aspect_ratio: Option<f32>) -> Size<Option<f32>> {
419 match aspect_ratio {
420 Some(ratio) => match (self.width, self.height) {
421 (Some(width), None) => Size { width: Some(width), height: Some(width / ratio) },
422 (None, Some(height)) => Size { width: Some(height * ratio), height: Some(height) },
423 _ => self,
424 },
425 None => self,
426 }
427 }
428}
429
430impl<T> Size<Option<T>> {
431 /// Performs Option::unwrap_or on each component separately
432 pub fn unwrap_or(self, alt: Size<T>) -> Size<T> {
433 Size { width: self.width.unwrap_or(alt.width), height: self.height.unwrap_or(alt.height) }
434 }
435
436 /// Performs Option::or on each component separately
437 pub fn or(self, alt: Size<Option<T>>) -> Size<Option<T>> {
438 Size { width: self.width.or(alt.width), height: self.height.or(alt.height) }
439 }
440
441 /// Return true if both components are Some, else false.
442 #[inline(always)]
443 pub fn both_axis_defined(&self) -> bool {
444 self.width.is_some() && self.height.is_some()
445 }
446}
447
448impl Size<Dimension> {
449 /// Generates a [`Size<Dimension>`] using [`Dimension::Points`] values
450 #[must_use]
451 pub const fn from_points(width: f32, height: f32) -> Self {
452 Size { width: Dimension::Points(width), height: Dimension::Points(height) }
453 }
454
455 /// Generates a [`Size<Dimension>`] using [`Dimension::Percent`] values
456 #[must_use]
457 pub const fn from_percent(width: f32, height: f32) -> Self {
458 Size { width: Dimension::Percent(width), height: Dimension::Percent(height) }
459 }
460}
461
462/// A 2-dimensional coordinate.
463///
464/// When used in association with a [`Rect`], represents the bottom-left corner.
465#[derive(Debug, Copy, Clone, PartialEq, Eq)]
466pub struct Point<T> {
467 /// The x-coordinate
468 pub x: T,
469 /// The y-coordinate
470 pub y: T,
471}
472
473impl Point<f32> {
474 /// A [`Point`] with values (0,0), representing the origin
475 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
476}
477
478impl Point<Option<f32>> {
479 /// A [`Point`] with values (None, None)
480 pub const NONE: Self = Self { x: None, y: None };
481}
482
483/// Generic struct which holds a "min" value and a "max" value
484#[derive(Debug, Copy, Clone, PartialEq, Eq)]
485#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
486pub struct MinMax<Min, Max> {
487 /// The value representing the minimum
488 pub min: Min,
489 /// The value representing the maximum
490 pub max: Max,
491}