tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 709HTML 662Markdown 12TOML 62348 Definitions

⎇ 0914058db433aaba2d4e4d6b11efc73eb1daf61d src/geometry.rs Rust · 693 lines · 21.9 KB History Who touched what raw

Minor: fixed comment for geometry::Point type (#505) 0914058db · Yurii Nakonechnyi ·

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}