tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5364Rust 85Markdown 12TOML 91813 Definitions

⎇ 4b89afd5e54cb867b8b99e9c353f03661ea57e6e src/geometry.rs Rust · 302 lines · 11.3 KB History Who touched what raw

Additional tests for FlexboxLayout and FlexDirection (#174) 95934288c · Marcello Vaz ·

1//! Geometric primitives useful for layout
2
3use crate::style::{Dimension, FlexDirection};
4use core::ops::Add;
5
6/// An axis-aligned UI rectangle
7#[derive(Debug, Copy, Clone, PartialEq, Eq)]
8#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9#[cfg_attr(feature = "serde", serde(default))]
10pub struct Rect<T> {
11    /// This can represent either the x-coordinate of the starting edge,
12    /// or the amount of padding on the starting side.
13    ///
14    /// The starting edge is the left edge when working with LTR text,
15    /// and the right edge when working with RTL text.
16    pub start: T,
17    /// This can represent either the x-coordinate of the ending edge,
18    /// or the amount of padding on the ending side.
19    ///
20    /// The ending edge is the right edge when working with LTR text,
21    /// and the left edge when working with RTL text.
22    pub end: T,
23    /// This can represent either the y-coordinate of the top edge,
24    /// or the amount of padding on the top side.
25    pub top: T,
26    /// This can represent either the y-coordinate of the bottom edge,
27    /// or the amount of padding on the bottom side.
28    pub bottom: T,
29}
30
31impl<T> Rect<T> {
32    /// Applies the function `f` to all four sides of the [`Rect`]
33    ///
34    /// This is used to transform a `Rect<T>` into a `Rect<R>`.
35    pub(crate) fn map<R, F>(self, f: F) -> Rect<R>
36    where
37        F: Fn(T) -> R,
38    {
39        Rect { start: f(self.start), end: f(self.end), top: f(self.top), bottom: f(self.bottom) }
40    }
41
42    /// Applies the function `f` to all four sides of the rect
43    ///
44    /// When applied to the left and right sides, the width is used
45    /// as the second parameter of `f`.
46    /// When applied to the top or bottom sides, the height is used instead.
47    pub(crate) fn zip_size<R, F, U>(self, size: Size<U>, f: F) -> Rect<R>
48    where
49        F: Fn(T, U) -> R,
50        U: Copy,
51    {
52        Rect {
53            start: f(self.start, size.width),
54            end: f(self.end, size.width),
55            top: f(self.top, size.height),
56            bottom: f(self.bottom, size.height),
57        }
58    }
59}
60
61impl<T> Rect<T>
62where
63    T: Add<Output = T> + Copy + Clone,
64{
65    /// The sum of [`Rect.start`](Rect) and [`Rect.end`](Rect)
66    ///
67    /// This is typically used when computing total padding.
68    ///
69    /// **NOTE:** this is *not* the width of the rectangle.
70    pub(crate) fn horizontal_axis_sum(&self) -> T {
71        self.start + self.end
72    }
73
74    /// The sum of [`Rect.top`](Rect) and [`Rect.bottom`](Rect)
75    ///
76    /// This is typically used when computing total padding.
77    ///
78    /// **NOTE:** this is *not* the height of the rectangle.
79    pub(crate) fn vertical_axis_sum(&self) -> T {
80        self.top + self.bottom
81    }
82
83    /// The sum of the two fields of the [`Rect`] representing the main axis.
84    ///
85    /// This is typically used when computing total padding.
86    ///
87    /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::horizontal`].
88    /// Otherwise, this is [`Rect::vertical`].
89    pub(crate) fn main_axis_sum(&self, direction: FlexDirection) -> T {
90        if direction.is_row() {
91            self.horizontal_axis_sum()
92        } else {
93            self.vertical_axis_sum()
94        }
95    }
96
97    /// The sum of the two fields of the [`Rect`] representing the cross axis.
98    ///
99    /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::vertical`].
100    /// Otherwise, this is [`Rect::horizontal`].
101    pub(crate) fn cross_axis_sum(&self, direction: FlexDirection) -> T {
102        if direction.is_row() {
103            self.vertical_axis_sum()
104        } else {
105            self.horizontal_axis_sum()
106        }
107    }
108}
109
110impl<T> Rect<T>
111where
112    T: Copy + Clone,
113{
114    /// The `start` or `top` value of the [`Rect`], from the perspective of the main layout axis
115    pub(crate) fn main_start(&self, direction: FlexDirection) -> T {
116        if direction.is_row() {
117            self.start
118        } else {
119            self.top
120        }
121    }
122
123    /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
124    pub(crate) fn main_end(&self, direction: FlexDirection) -> T {
125        if direction.is_row() {
126            self.end
127        } else {
128            self.bottom
129        }
130    }
131
132    /// The `start` or `top` value of the [`Rect`], from the perspective of the cross layout axis
133    pub(crate) fn cross_start(&self, direction: FlexDirection) -> T {
134        if direction.is_row() {
135            self.top
136        } else {
137            self.start
138        }
139    }
140
141    /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
142    pub(crate) fn cross_end(&self, direction: FlexDirection) -> T {
143        if direction.is_row() {
144            self.bottom
145        } else {
146            self.end
147        }
148    }
149}
150
151/// The width and height of a [`Rect`]
152#[derive(Debug, Copy, Clone, PartialEq, Eq)]
153#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
154#[cfg_attr(feature = "serde", serde(default))]
155pub struct Size<T> {
156    /// The x extent of the rectangle
157    pub width: T,
158    /// The y extent of the rectangle
159    pub height: T,
160}
161
162impl Size<()> {
163    /// Generates a `Size<Option<f32>>` with undefined width and height
164    #[must_use]
165    pub fn undefined() -> Size<Option<f32>> {
166        Size { width: None, height: None }
167    }
168}
169
170impl<T> Size<T> {
171    /// Applies the function `f` to both the width and height
172    ///
173    /// This is used to transform a `Rect<T>` into a `Rect<R>`.
174    pub fn map<R, F>(self, f: F) -> Size<R>
175    where
176        F: Fn(T) -> R,
177    {
178        Size { width: f(self.width), height: f(self.height) }
179    }
180
181    /// Sets the extent of the main layout axis
182    ///
183    /// Whether this is the width or height depends on the `direction` provided
184    pub(crate) fn set_main(&mut self, direction: FlexDirection, value: T) {
185        if direction.is_row() {
186            self.width = value
187        } else {
188            self.height = value
189        }
190    }
191
192    /// Sets the extent of the cross layout axis
193    ///
194    /// Whether this is the width or height depends on the `direction` provided
195    pub(crate) fn set_cross(&mut self, direction: FlexDirection, value: T) {
196        if direction.is_row() {
197            self.height = value
198        } else {
199            self.width = value
200        }
201    }
202
203    /// Gets the extent of the main layout axis
204    ///
205    /// Whether this is the width or height depends on the `direction` provided
206    pub(crate) fn main(self, direction: FlexDirection) -> T {
207        if direction.is_row() {
208            self.width
209        } else {
210            self.height
211        }
212    }
213
214    /// Gets the extent of the cross layout axis
215    ///
216    /// Whether this is the width or height depends on the `direction` provided
217    pub(crate) fn cross(self, direction: FlexDirection) -> T {
218        if direction.is_row() {
219            self.height
220        } else {
221            self.width
222        }
223    }
224}
225
226impl Size<f32> {
227    /// A [`Size`] with zero width and height
228    #[must_use]
229    pub fn zero() -> Self {
230        Self { width: 0.0, height: 0.0 }
231    }
232}
233
234impl Size<Dimension> {
235    /// Generates a [`Size<Dimension>`] using [`Dimension::Points`] values
236    pub fn from_points(width: f32, height: f32) -> Self {
237        Size { width: Dimension::Points(width), height: Dimension::Points(height) }
238    }
239
240    /// Generates a [`Size<Dimension>`] using [`Dimension::Percent`] values
241    pub fn from_percent(width: f32, height: f32) -> Self {
242        Size { width: Dimension::Percent(width), height: Dimension::Percent(height) }
243    }
244
245    /// Converts any `parent`-relative values for size into an absolute size
246    pub(crate) fn resolve(&self, parent: Size<Option<f32>>) -> Size<Option<f32>> {
247        Size { width: self.width.resolve(parent.width), height: self.height.resolve(parent.height) }
248    }
249}
250
251/// A 2-dimensional coordinate.
252///
253/// When used in association with a [`Rect`], represents the bottom-left corner.
254#[derive(Debug, Copy, Clone, PartialEq, Eq)]
255pub struct Point<T> {
256    /// The x-coordinate
257    pub x: T,
258    /// The y-coordinate
259    pub y: T,
260}
261
262impl Point<f32> {
263    /// A [`Point`] with values (0,0), representing the origin
264    #[must_use]
265    pub fn zero() -> Self {
266        Self { x: 0.0, y: 0.0 }
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    mod test_resolve_size {
273        use crate::geometry::Size;
274        use crate::style::Dimension;
275        use rstest::rstest;
276
277        #[rstest]
278        #[case(Size { width: Dimension::Undefined, height: Dimension::Undefined }, Size { width: None, height: None }, Size { width: None, height: None })]
279        #[case(Size { width: Dimension::Undefined, height: Dimension::Undefined }, Size { width: Some(0.0), height: Some(0.0) }, Size { width: None, height: None })]
280        #[case(Size { width: Dimension::Undefined, height: Dimension::Undefined }, Size { width: Some(1.0), height: Some(1.0) }, Size { width: None, height: None })]
281        #[case(Size { width: Dimension::Undefined, height: Dimension::Undefined }, Size { width: Some(-1.0), height: Some(-1.0) }, Size { width: None, height: None })]
282        #[case(Size { width: Dimension::Auto, height: Dimension::Auto }, Size { width: None, height: None }, Size { width: None, height: None })]
283        #[case(Size { width: Dimension::Auto, height: Dimension::Auto }, Size { width: Some(0.0), height: Some(0.0) }, Size { width: None, height: None })]
284        #[case(Size { width: Dimension::Auto, height: Dimension::Auto }, Size { width: Some(1.0), height: Some(1.0) }, Size { width: None, height: None })]
285        #[case(Size { width: Dimension::Auto, height: Dimension::Auto }, Size { width: Some(-1.0), height: Some(-1.0) }, Size { width: None, height: None })]
286        #[case(Size { width: Dimension::Points(5.0), height: Dimension::Points(5.0) }, Size { width: None, height: None }, Size { width: Some(5.0), height: Some(5.0) })]
287        #[case(Size { width: Dimension::Points(5.0), height: Dimension::Points(5.0) }, Size { width: Some(0.0), height: Some(0.0) }, Size { width: Some(5.0), height: Some(5.0) })]
288        #[case(Size { width: Dimension::Points(5.0), height: Dimension::Points(5.0) }, Size { width: Some(1.0), height: Some(1.0) }, Size { width: Some(5.0), height: Some(5.0) })]
289        #[case(Size { width: Dimension::Points(5.0), height: Dimension::Points(5.0) }, Size { width: Some(-1.0), height: Some(-1.0) }, Size { width: Some(5.0), height: Some(5.0) })]
290        #[case(Size { width: Dimension::Percent(50.0), height: Dimension::Percent(50.0) }, Size { width: None, height: None }, Size { width: None, height: None })]
291        #[case(Size { width: Dimension::Percent(50.0), height: Dimension::Percent(50.0) }, Size { width: Some(0.0), height: Some(0.0) }, Size { width: Some(0.0), height: Some(0.0) })]
292        #[case(Size { width: Dimension::Percent(50.0), height: Dimension::Percent(50.0) }, Size { width: Some(1.0), height: Some(1.0) }, Size { width: Some(50.0), height: Some(50.0) })]
293        #[case(Size { width: Dimension::Percent(50.0), height: Dimension::Percent(50.0) }, Size { width: Some(-1.0), height: Some(-1.0) }, Size { width: Some(-50.0), height: Some(-50.0) })]
294        fn resolve_size(
295            #[case] input: Size<Dimension>,
296            #[case] parent: Size<Option<f32>>,
297            #[case] expected: Size<Option<f32>>,
298        ) {
299            assert_eq!(input.resolve(parent), expected);
300        }
301    }
302}