gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 709HTML 662Markdown 12TOML 62348 Sutichaykuna

⎇ 77a309162f0011d9ddb563f435f3cbafc5b1775f src/geometry.rs Rust · 559 siq'ikuna · 17.8 KB Ñawpa willay Pi imata llamkarqan ch'uya

Minor: reordered constrcutor aguments in Rect<T>::map() (#430) 77a309162 · Yurii Nakonechnyi ·

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