tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81412 Definitions

⎇ c2db86dfc600067c6f4d70c8ea398cbcc5d42dbd src/geometry.rs Rust · 491 lines · 15.4 KB History Who touched what raw

Update 0.3.x for 0.3.11 (#455) 1cc940684 · 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))]
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}