tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81414 Definitions

⎇ b91874d4e34ecc9054b468a338ffcae6f61a1b4a src/geometry.rs Rust · 459 lines · 14.4 KB History Who touched what raw

Fix flexbox baseline alignment (#325) b91874d4e · Nico Burns ·

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