Minor: fixed comment for geometry::Point type (#505)
1//! Geometric primitives useful for layout
2
3use crate::style::Dimension;
4use crate::util::sys::f32_max;
5use core::ops::{Add, Sub};
6
7#[cfg(feature = "flexbox")]
8use crate::style::FlexDirection;
9
10/// The simple absolute horizontal and vertical axis
11#[derive(Copy, Clone, Debug, PartialEq, Eq)]
12pub enum AbsoluteAxis {
13 /// The horizontal axis
14 Horizontal,
15 /// The vertical axis
16 Vertical,
17}
18
19impl AbsoluteAxis {
20 /// Returns the other variant of the enum
21 #[inline]
22 pub const fn other_axis(&self) -> Self {
23 match *self {
24 AbsoluteAxis::Horizontal => AbsoluteAxis::Vertical,
25 AbsoluteAxis::Vertical => AbsoluteAxis::Horizontal,
26 }
27 }
28}
29
30impl<T> Size<T> {
31 #[inline(always)]
32 /// Get either the width or height depending on the AbsoluteAxis passed in
33 pub fn get_abs(self, axis: AbsoluteAxis) -> T {
34 match axis {
35 AbsoluteAxis::Horizontal => self.width,
36 AbsoluteAxis::Vertical => self.height,
37 }
38 }
39}
40
41impl<T: Add> Rect<T> {
42 #[inline(always)]
43 /// Get either the width or height depending on the AbsoluteAxis passed in
44 pub fn grid_axis_sum(self, axis: AbsoluteAxis) -> <T as Add>::Output {
45 match axis {
46 AbsoluteAxis::Horizontal => self.left + self.right,
47 AbsoluteAxis::Vertical => self.top + self.bottom,
48 }
49 }
50}
51
52/// The CSS abstract axis
53/// <https://www.w3.org/TR/css-writing-modes-3/#abstract-axes>
54#[derive(Copy, Clone, Debug, PartialEq, Eq)]
55pub enum AbstractAxis {
56 /// The axis in the inline dimension, i.e. the horizontal axis in horizontal writing modes and the vertical axis in vertical writing modes.
57 Inline,
58 /// The axis in the block dimension, i.e. the vertical axis in horizontal writing modes and the horizontal axis in vertical writing modes.
59 Block,
60}
61
62impl AbstractAxis {
63 /// Returns the other variant of the enum
64 pub fn other(&self) -> AbstractAxis {
65 match *self {
66 AbstractAxis::Inline => AbstractAxis::Block,
67 AbstractAxis::Block => AbstractAxis::Inline,
68 }
69 }
70}
71
72/// Container that holds an item in each absolute axis without specifying
73/// what kind of item it is.
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub(crate) struct InBothAbsAxis<T> {
76 /// The item in the horizontal axis
77 pub horizontal: T,
78 /// The item in the vertical axis
79 pub vertical: T,
80}
81
82impl<T: Copy> InBothAbsAxis<T> {
83 #[cfg(feature = "grid")]
84 /// Get the contained item based on the AbsoluteAxis passed
85 pub fn get(&self, axis: AbsoluteAxis) -> T {
86 match axis {
87 AbsoluteAxis::Horizontal => self.horizontal,
88 AbsoluteAxis::Vertical => self.vertical,
89 }
90 }
91}
92
93/// An axis-aligned UI rectangle
94#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
95#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
96pub struct Rect<T> {
97 /// This can represent either the x-coordinate of the starting edge,
98 /// or the amount of padding on the starting side.
99 ///
100 /// The starting edge is the left edge when working with LTR text,
101 /// and the right edge when working with RTL text.
102 pub left: T,
103 /// This can represent either the x-coordinate of the ending edge,
104 /// or the amount of padding on the ending side.
105 ///
106 /// The ending edge is the right edge when working with LTR text,
107 /// and the left edge when working with RTL text.
108 pub right: T,
109 /// This can represent either the y-coordinate of the top edge,
110 /// or the amount of padding on the top side.
111 pub top: T,
112 /// This can represent either the y-coordinate of the bottom edge,
113 /// or the amount of padding on the bottom side.
114 pub bottom: T,
115}
116
117impl<U, T: Add<U>> Add<Rect<U>> for Rect<T> {
118 type Output = Rect<T::Output>;
119
120 fn add(self, rhs: Rect<U>) -> Self::Output {
121 Rect {
122 left: self.left + rhs.left,
123 right: self.right + rhs.right,
124 top: self.top + rhs.top,
125 bottom: self.bottom + rhs.bottom,
126 }
127 }
128}
129
130impl<T> Rect<T> {
131 /// Applies the function `f` to all four sides of the rect
132 ///
133 /// When applied to the left and right sides, the width is used
134 /// as the second parameter of `f`.
135 /// When applied to the top or bottom sides, the height is used instead.
136 #[cfg(feature = "flexbox")]
137 pub(crate) fn zip_size<R, F, U>(self, size: Size<U>, f: F) -> Rect<R>
138 where
139 F: Fn(T, U) -> R,
140 U: Copy,
141 {
142 Rect {
143 left: f(self.left, size.width),
144 right: f(self.right, size.width),
145 top: f(self.top, size.height),
146 bottom: f(self.bottom, size.height),
147 }
148 }
149
150 /// Applies the function `f` to the left, right, top, and bottom properties
151 ///
152 /// This is used to transform a `Rect<T>` into a `Rect<R>`.
153 pub fn map<R, F>(self, f: F) -> Rect<R>
154 where
155 F: Fn(T) -> R,
156 {
157 Rect { left: f(self.left), right: f(self.right), top: f(self.top), bottom: f(self.bottom) }
158 }
159
160 /// Returns a `Line<T>` representing the left and right properties of the Rect
161 pub fn horizontal_components(self) -> Line<T> {
162 Line { start: self.left, end: self.right }
163 }
164
165 /// Returns a `Line<T>` containing the top and bottom properties of the Rect
166 pub fn vertical_components(self) -> Line<T> {
167 Line { start: self.top, end: self.bottom }
168 }
169}
170
171impl<T, U> Rect<T>
172where
173 T: Add<Output = U> + Copy + Clone,
174{
175 /// The sum of [`Rect.start`](Rect) and [`Rect.end`](Rect)
176 ///
177 /// This is typically used when computing total padding.
178 ///
179 /// **NOTE:** this is *not* the width of the rectangle.
180 #[inline(always)]
181 pub(crate) fn horizontal_axis_sum(&self) -> U {
182 self.left + self.right
183 }
184
185 /// The sum of [`Rect.top`](Rect) and [`Rect.bottom`](Rect)
186 ///
187 /// This is typically used when computing total padding.
188 ///
189 /// **NOTE:** this is *not* the height of the rectangle.
190 #[inline(always)]
191 pub(crate) fn vertical_axis_sum(&self) -> U {
192 self.top + self.bottom
193 }
194
195 /// Both horizontal_axis_sum and vertical_axis_sum as a Size<T>
196 ///
197 /// **NOTE:** this is *not* the width/height of the rectangle.
198 #[inline(always)]
199 #[allow(dead_code)] // Fixes spurious clippy warning: this function is used!
200 pub(crate) fn sum_axes(&self) -> Size<U> {
201 Size { width: self.horizontal_axis_sum(), height: self.vertical_axis_sum() }
202 }
203
204 /// The sum of the two fields of the [`Rect`] representing the main axis.
205 ///
206 /// This is typically used when computing total padding.
207 ///
208 /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::horizontal`].
209 /// Otherwise, this is [`Rect::vertical`].
210 #[cfg(feature = "flexbox")]
211 pub(crate) fn main_axis_sum(&self, direction: FlexDirection) -> U {
212 if direction.is_row() {
213 self.horizontal_axis_sum()
214 } else {
215 self.vertical_axis_sum()
216 }
217 }
218
219 /// The sum of the two fields of the [`Rect`] representing the cross axis.
220 ///
221 /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::vertical`].
222 /// Otherwise, this is [`Rect::horizontal`].
223 #[cfg(feature = "flexbox")]
224 pub(crate) fn cross_axis_sum(&self, direction: FlexDirection) -> U {
225 if direction.is_row() {
226 self.vertical_axis_sum()
227 } else {
228 self.horizontal_axis_sum()
229 }
230 }
231}
232
233impl<T> Rect<T>
234where
235 T: Copy + Clone,
236{
237 /// The `start` or `top` value of the [`Rect`], from the perspective of the main layout axis
238 #[cfg(feature = "flexbox")]
239 pub(crate) fn main_start(&self, direction: FlexDirection) -> T {
240 if direction.is_row() {
241 self.left
242 } else {
243 self.top
244 }
245 }
246
247 /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
248 #[cfg(feature = "flexbox")]
249 pub(crate) fn main_end(&self, direction: FlexDirection) -> T {
250 if direction.is_row() {
251 self.right
252 } else {
253 self.bottom
254 }
255 }
256
257 /// The `start` or `top` value of the [`Rect`], from the perspective of the cross layout axis
258 #[cfg(feature = "flexbox")]
259 pub(crate) fn cross_start(&self, direction: FlexDirection) -> T {
260 if direction.is_row() {
261 self.top
262 } else {
263 self.left
264 }
265 }
266
267 /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
268 #[cfg(feature = "flexbox")]
269 pub(crate) fn cross_end(&self, direction: FlexDirection) -> T {
270 if direction.is_row() {
271 self.bottom
272 } else {
273 self.right
274 }
275 }
276}
277
278impl Rect<f32> {
279 /// Creates a new Rect with `0.0` as all parameters
280 pub const ZERO: Rect<f32> = Self { left: 0.0, right: 0.0, top: 0.0, bottom: 0.0 };
281
282 /// Creates a new Rect
283 #[must_use]
284 pub const fn new(start: f32, end: f32, top: f32, bottom: f32) -> Self {
285 Self { left: start, right: end, top, bottom }
286 }
287}
288
289/// An abstract "line". Represents any type that has a start and an end
290#[derive(Debug, Copy, Clone, PartialEq, Eq)]
291#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
292#[cfg_attr(feature = "serde", serde(default))]
293pub struct Line<T> {
294 /// The start position of a line
295 pub start: T,
296 /// The end position of a line
297 pub end: T,
298}
299
300impl<T> Line<T> {
301 /// Applies the function `f` to both the width and height
302 ///
303 /// This is used to transform a `Line<T>` into a `Line<R>`.
304 pub fn map<R, F>(self, f: F) -> Line<R>
305 where
306 F: Fn(T) -> R,
307 {
308 Line { start: f(self.start), end: f(self.end) }
309 }
310}
311
312impl Line<bool> {
313 /// A `Line<bool>` with both start and end set to `true`
314 pub const TRUE: Self = Line { start: true, end: true };
315 /// A `Line<bool>` with both start and end set to `false`
316 pub const FALSE: Self = Line { start: false, end: false };
317}
318
319impl<T: Add + Copy> Line<T> {
320 /// Adds the start and end values together and returns the result
321 pub fn sum(&self) -> <T as Add>::Output {
322 self.start + self.end
323 }
324}
325
326/// The width and height of a [`Rect`]
327#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
328#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
329pub struct Size<T> {
330 /// The x extent of the rectangle
331 pub width: T,
332 /// The y extent of the rectangle
333 pub height: T,
334}
335
336// Generic Add impl for Size<T> + Size<U> where T + U has an Add impl
337impl<U, T: Add<U>> Add<Size<U>> for Size<T> {
338 type Output = Size<<T as Add<U>>::Output>;
339
340 fn add(self, rhs: Size<U>) -> Self::Output {
341 Size { width: self.width + rhs.width, height: self.height + rhs.height }
342 }
343}
344
345// Generic Sub impl for Size<T> + Size<U> where T + U has an Sub impl
346impl<U, T: Sub<U>> Sub<Size<U>> for Size<T> {
347 type Output = Size<<T as Sub<U>>::Output>;
348
349 fn sub(self, rhs: Size<U>) -> Self::Output {
350 Size { width: self.width - rhs.width, height: self.height - rhs.height }
351 }
352}
353
354// Note: we allow dead_code here as we want to provide a complete API of helpers that is symetrical in all axes,
355// but sometimes we only currently have a use for the helper in a single axis
356#[allow(dead_code)]
357impl<T> Size<T> {
358 /// Applies the function `f` to both the width and height
359 ///
360 /// This is used to transform a `Size<T>` into a `Size<R>`.
361 pub fn map<R, F>(self, f: F) -> Size<R>
362 where
363 F: Fn(T) -> R,
364 {
365 Size { width: f(self.width), height: f(self.height) }
366 }
367
368 /// Applies the function `f` to the width
369 pub fn map_width<F>(self, f: F) -> Size<T>
370 where
371 F: Fn(T) -> T,
372 {
373 Size { width: f(self.width), height: self.height }
374 }
375
376 /// Applies the function `f` to the height
377 pub fn map_height<F>(self, f: F) -> Size<T>
378 where
379 F: Fn(T) -> T,
380 {
381 Size { width: self.width, height: f(self.height) }
382 }
383
384 /// Applies the function `f` to both the width and height
385 /// of this value and another passed value
386 pub fn zip_map<Other, Ret, Func>(self, other: Size<Other>, f: Func) -> Size<Ret>
387 where
388 Func: Fn(T, Other) -> Ret,
389 {
390 Size { width: f(self.width, other.width), height: f(self.height, other.height) }
391 }
392
393 /// Sets the extent of the main layout axis
394 ///
395 /// Whether this is the width or height depends on the `direction` provided
396 #[cfg(feature = "flexbox")]
397 pub(crate) fn set_main(&mut self, direction: FlexDirection, value: T) {
398 if direction.is_row() {
399 self.width = value
400 } else {
401 self.height = value
402 }
403 }
404
405 /// Sets the extent of the cross layout axis
406 ///
407 /// Whether this is the width or height depends on the `direction` provided
408 #[cfg(feature = "flexbox")]
409 pub(crate) fn set_cross(&mut self, direction: FlexDirection, value: T) {
410 if direction.is_row() {
411 self.height = value
412 } else {
413 self.width = value
414 }
415 }
416
417 /// Creates a new value of type Self with the main axis set to value provided
418 ///
419 /// Whether this is the width or height depends on the `direction` provided
420 #[cfg(feature = "flexbox")]
421 pub(crate) fn with_main(self, direction: FlexDirection, value: T) -> Self {
422 let mut new = self;
423 if direction.is_row() {
424 new.width = value
425 } else {
426 new.height = value
427 }
428 new
429 }
430
431 /// Creates a new value of type Self with the cross axis set to value provided
432 ///
433 /// Whether this is the width or height depends on the `direction` provided
434 #[cfg(feature = "flexbox")]
435 pub(crate) fn with_cross(self, direction: FlexDirection, value: T) -> Self {
436 let mut new = self;
437 if direction.is_row() {
438 new.height = value
439 } else {
440 new.width = value
441 }
442 new
443 }
444
445 /// Creates a new value of type Self with the main axis modified by the callback provided
446 ///
447 /// Whether this is the width or height depends on the `direction` provided
448 #[cfg(feature = "flexbox")]
449 pub(crate) fn map_main(self, direction: FlexDirection, mapper: impl FnOnce(T) -> T) -> Self {
450 let mut new = self;
451 if direction.is_row() {
452 new.width = mapper(new.width);
453 } else {
454 new.height = mapper(new.height);
455 }
456 new
457 }
458
459 /// Creates a new value of type Self with the cross axis modified by the callback provided
460 ///
461 /// Whether this is the width or height depends on the `direction` provided
462 #[cfg(feature = "flexbox")]
463 pub(crate) fn map_cross(self, direction: FlexDirection, mapper: impl FnOnce(T) -> T) -> Self {
464 let mut new = self;
465 if direction.is_row() {
466 new.height = mapper(new.height);
467 } else {
468 new.width = mapper(new.width);
469 }
470 new
471 }
472
473 /// Gets the extent of the main layout axis
474 ///
475 /// Whether this is the width or height depends on the `direction` provided
476 #[cfg(feature = "flexbox")]
477 pub(crate) fn main(self, direction: FlexDirection) -> T {
478 if direction.is_row() {
479 self.width
480 } else {
481 self.height
482 }
483 }
484
485 /// Gets the extent of the cross layout axis
486 ///
487 /// Whether this is the width or height depends on the `direction` provided
488 #[cfg(feature = "flexbox")]
489 pub(crate) fn cross(self, direction: FlexDirection) -> T {
490 if direction.is_row() {
491 self.height
492 } else {
493 self.width
494 }
495 }
496
497 /// Gets the extent of the specified layout axis
498 /// Whether this is the width or height depends on the `GridAxis` provided
499 #[cfg(feature = "grid")]
500 pub(crate) fn get(self, axis: AbstractAxis) -> T {
501 match axis {
502 AbstractAxis::Inline => self.width,
503 AbstractAxis::Block => self.height,
504 }
505 }
506
507 /// Sets the extent of the specified layout axis
508 /// Whether this is the width or height depends on the `GridAxis` provided
509 #[cfg(feature = "grid")]
510 pub(crate) fn set(&mut self, axis: AbstractAxis, value: T) {
511 match axis {
512 AbstractAxis::Inline => self.width = value,
513 AbstractAxis::Block => self.height = value,
514 }
515 }
516}
517
518impl Size<f32> {
519 /// A [`Size`] with zero width and height
520 pub const ZERO: Size<f32> = Self { width: 0.0, height: 0.0 };
521
522 /// Applies f32_max to each component separately
523 pub fn f32_max(self, rhs: Size<f32>) -> Size<f32> {
524 Size { width: f32_max(self.width, rhs.width), height: f32_max(self.height, rhs.height) }
525 }
526}
527
528impl Size<Option<f32>> {
529 /// A [`Size`] with `None` width and height
530 pub const NONE: Size<Option<f32>> = Self { width: None, height: None };
531
532 /// A [`Size<Option<f32>>`] with `Some(width)` and `Some(height)` as parameters
533 #[must_use]
534 pub const fn new(width: f32, height: f32) -> Self {
535 Size { width: Some(width), height: Some(height) }
536 }
537
538 /// Applies aspect_ratio (if one is supplied) to the Size:
539 /// - If width is `Some` but height is `None`, then height is computed from width and aspect_ratio
540 /// - If height is `Some` but width is `None`, then width is computed from height and aspect_ratio
541 ///
542 /// If aspect_ratio is `None` then this function simply returns self.
543 pub fn maybe_apply_aspect_ratio(self, aspect_ratio: Option<f32>) -> Size<Option<f32>> {
544 match aspect_ratio {
545 Some(ratio) => match (self.width, self.height) {
546 (Some(width), None) => Size { width: Some(width), height: Some(width / ratio) },
547 (None, Some(height)) => Size { width: Some(height * ratio), height: Some(height) },
548 _ => self,
549 },
550 None => self,
551 }
552 }
553}
554
555impl<T> Size<Option<T>> {
556 /// Performs Option::unwrap_or on each component separately
557 pub fn unwrap_or(self, alt: Size<T>) -> Size<T> {
558 Size { width: self.width.unwrap_or(alt.width), height: self.height.unwrap_or(alt.height) }
559 }
560
561 /// Performs Option::or on each component separately
562 pub fn or(self, alt: Size<Option<T>>) -> Size<Option<T>> {
563 Size { width: self.width.or(alt.width), height: self.height.or(alt.height) }
564 }
565
566 /// Return true if both components are Some, else false.
567 #[inline(always)]
568 pub fn both_axis_defined(&self) -> bool {
569 self.width.is_some() && self.height.is_some()
570 }
571}
572
573impl Size<Dimension> {
574 /// Generates a [`Size<Dimension>`] using [`Dimension::Length`] values
575 #[must_use]
576 pub const fn from_lengths(width: f32, height: f32) -> Self {
577 Size { width: Dimension::Length(width), height: Dimension::Length(height) }
578 }
579
580 /// Generates a [`Size<Dimension>`] using [`Dimension::Percent`] values
581 #[must_use]
582 pub const fn from_percent(width: f32, height: f32) -> Self {
583 Size { width: Dimension::Percent(width), height: Dimension::Percent(height) }
584 }
585}
586
587/// A 2-dimensional coordinate.
588///
589/// When used in association with a [`Rect`], represents the top-left corner.
590#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
591#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
592pub struct Point<T> {
593 /// The x-coordinate
594 pub x: T,
595 /// The y-coordinate
596 pub y: T,
597}
598
599impl Point<f32> {
600 /// A [`Point`] with values (0,0), representing the origin
601 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
602}
603
604impl Point<Option<f32>> {
605 /// A [`Point`] with values (None, None)
606 pub const NONE: Self = Self { x: None, y: None };
607}
608
609// Generic Add impl for Point<T> + Point<U> where T + U has an Add impl
610impl<U, T: Add<U>> Add<Point<U>> for Point<T> {
611 type Output = Point<<T as Add<U>>::Output>;
612
613 fn add(self, rhs: Point<U>) -> Self::Output {
614 Point { x: self.x + rhs.x, y: self.y + rhs.y }
615 }
616}
617
618impl<T> Point<T> {
619 /// Applies the function `f` to both the x and y
620 ///
621 /// This is used to transform a `Point<T>` into a `Point<R>`.
622 pub fn map<R, F>(self, f: F) -> Point<R>
623 where
624 F: Fn(T) -> R,
625 {
626 Point { x: f(self.x), y: f(self.y) }
627 }
628
629 /// Gets the extent of the specified layout axis
630 /// Whether this is the width or height depends on the `GridAxis` provided
631 #[cfg(feature = "grid")]
632 pub fn get(self, axis: AbstractAxis) -> T {
633 match axis {
634 AbstractAxis::Inline => self.x,
635 AbstractAxis::Block => self.y,
636 }
637 }
638
639 /// Swap x and y components
640 pub fn transpose(self) -> Point<T> {
641 Point { x: self.y, y: self.x }
642 }
643
644 /// Sets the extent of the specified layout axis
645 /// Whether this is the width or height depends on the `GridAxis` provided
646 #[cfg(feature = "grid")]
647 pub fn set(&mut self, axis: AbstractAxis, value: T) {
648 match axis {
649 AbstractAxis::Inline => self.x = value,
650 AbstractAxis::Block => self.y = value,
651 }
652 }
653
654 /// Gets the component in the main layout axis
655 ///
656 /// Whether this is the x or y depends on the `direction` provided
657 #[cfg(feature = "flexbox")]
658 pub(crate) fn main(self, direction: FlexDirection) -> T {
659 if direction.is_row() {
660 self.x
661 } else {
662 self.y
663 }
664 }
665
666 /// Gets the component in the cross layout axis
667 ///
668 /// Whether this is the x or y depends on the `direction` provided
669 #[cfg(feature = "flexbox")]
670 pub(crate) fn cross(self, direction: FlexDirection) -> T {
671 if direction.is_row() {
672 self.y
673 } else {
674 self.x
675 }
676 }
677}
678
679impl<T> From<Point<T>> for Size<T> {
680 fn from(value: Point<T>) -> Self {
681 Size { width: value.x, height: value.y }
682 }
683}
684
685/// Generic struct which holds a "min" value and a "max" value
686#[derive(Debug, Copy, Clone, PartialEq, Eq)]
687#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
688pub struct MinMax<Min, Max> {
689 /// The value representing the minimum
690 pub min: Min,
691 /// The value representing the maximum
692 pub max: Max,
693}