Layout algorithm decoupling, Sizing Constraints & Perf Improvements (#246)
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 /// When applied to the left and right sides, the width is used
35 /// as the second parameter of `f`.
36 /// When applied to the top or bottom sides, the height is used instead.
37 pub(crate) fn zip_size<R, F, U>(self, size: Size<U>, f: F) -> Rect<R>
38 where
39 F: Fn(T, U) -> R,
40 U: Copy,
41 {
42 Rect {
43 start: f(self.start, size.width),
44 end: f(self.end, size.width),
45 top: f(self.top, size.height),
46 bottom: f(self.bottom, size.height),
47 }
48 }
49}
50
51impl<T> Rect<T>
52where
53 T: Add<Output = T> + Copy + Clone,
54{
55 /// The sum of [`Rect.start`](Rect) and [`Rect.end`](Rect)
56 ///
57 /// This is typically used when computing total padding.
58 ///
59 /// **NOTE:** this is *not* the width of the rectangle.
60 pub(crate) fn horizontal_axis_sum(&self) -> T {
61 self.start + self.end
62 }
63
64 /// The sum of [`Rect.top`](Rect) and [`Rect.bottom`](Rect)
65 ///
66 /// This is typically used when computing total padding.
67 ///
68 /// **NOTE:** this is *not* the height of the rectangle.
69 pub(crate) fn vertical_axis_sum(&self) -> T {
70 self.top + self.bottom
71 }
72
73 /// The sum of the two fields of the [`Rect`] representing the main axis.
74 ///
75 /// This is typically used when computing total padding.
76 ///
77 /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::horizontal`].
78 /// Otherwise, this is [`Rect::vertical`].
79 pub(crate) fn main_axis_sum(&self, direction: FlexDirection) -> T {
80 if direction.is_row() {
81 self.horizontal_axis_sum()
82 } else {
83 self.vertical_axis_sum()
84 }
85 }
86
87 /// The sum of the two fields of the [`Rect`] representing the cross axis.
88 ///
89 /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::vertical`].
90 /// Otherwise, this is [`Rect::horizontal`].
91 pub(crate) fn cross_axis_sum(&self, direction: FlexDirection) -> T {
92 if direction.is_row() {
93 self.vertical_axis_sum()
94 } else {
95 self.horizontal_axis_sum()
96 }
97 }
98}
99
100impl<T> Rect<T>
101where
102 T: Copy + Clone,
103{
104 /// The `start` or `top` value of the [`Rect`], from the perspective of the main layout axis
105 pub(crate) fn main_start(&self, direction: FlexDirection) -> T {
106 if direction.is_row() {
107 self.start
108 } else {
109 self.top
110 }
111 }
112
113 /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
114 pub(crate) fn main_end(&self, direction: FlexDirection) -> T {
115 if direction.is_row() {
116 self.end
117 } else {
118 self.bottom
119 }
120 }
121
122 /// The `start` or `top` value of the [`Rect`], from the perspective of the cross layout axis
123 pub(crate) fn cross_start(&self, direction: FlexDirection) -> T {
124 if direction.is_row() {
125 self.top
126 } else {
127 self.start
128 }
129 }
130
131 /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
132 pub(crate) fn cross_end(&self, direction: FlexDirection) -> T {
133 if direction.is_row() {
134 self.bottom
135 } else {
136 self.end
137 }
138 }
139}
140
141impl Rect<f32> {
142 /// Creates a new Rect with `0.0` as all parameters
143 pub const ZERO: Rect<f32> = Self { start: 0.0, end: 0.0, top: 0.0, bottom: 0.0 };
144
145 /// Creates a new Rect
146 #[must_use]
147 pub const fn new(start: f32, end: f32, top: f32, bottom: f32) -> Self {
148 Self { start, end, top, bottom }
149 }
150}
151
152/// The width and height of a [`Rect`]
153#[derive(Debug, Copy, Clone, PartialEq, Eq)]
154#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
155#[cfg_attr(feature = "serde", serde(default))]
156pub struct Size<T> {
157 /// The x extent of the rectangle
158 pub width: T,
159 /// The y extent of the rectangle
160 pub height: T,
161}
162
163impl<T> Size<T> {
164 /// Applies the function `f` to both the width and height
165 ///
166 /// This is used to transform a `Size<T>` into a `Size<R>`.
167 pub fn map<R, F>(self, f: F) -> Size<R>
168 where
169 F: Fn(T) -> R,
170 {
171 Size { width: f(self.width), height: f(self.height) }
172 }
173
174 /// Applies the function `f` to the width
175 pub fn map_width<F>(self, f: F) -> Size<T>
176 where
177 F: Fn(T) -> T,
178 {
179 Size { width: f(self.width), height: self.height }
180 }
181
182 /// Applies the function `f` to the height
183 pub fn map_height<F>(self, f: F) -> Size<T>
184 where
185 F: Fn(T) -> T,
186 {
187 Size { width: self.width, height: f(self.height) }
188 }
189
190 /// Applies the function `f` to both the width and height
191 /// of this value and another passed value
192 pub fn zip_map<Other, Ret, Func>(self, other: Size<Other>, f: Func) -> Size<Ret>
193 where
194 Func: Fn(T, Other) -> Ret,
195 {
196 Size { width: f(self.width, other.width), height: f(self.height, other.height) }
197 }
198
199 /// Sets the extent of the main layout axis
200 ///
201 /// Whether this is the width or height depends on the `direction` provided
202 pub(crate) fn set_main(&mut self, direction: FlexDirection, value: T) {
203 if direction.is_row() {
204 self.width = value
205 } else {
206 self.height = value
207 }
208 }
209
210 /// Sets the extent of the cross layout axis
211 ///
212 /// Whether this is the width or height depends on the `direction` provided
213 pub(crate) fn set_cross(&mut self, direction: FlexDirection, value: T) {
214 if direction.is_row() {
215 self.height = value
216 } else {
217 self.width = value
218 }
219 }
220
221 /// Gets the extent of the main layout axis
222 ///
223 /// Whether this is the width or height depends on the `direction` provided
224 pub(crate) fn main(self, direction: FlexDirection) -> T {
225 if direction.is_row() {
226 self.width
227 } else {
228 self.height
229 }
230 }
231
232 /// Gets the extent of the cross layout axis
233 ///
234 /// Whether this is the width or height depends on the `direction` provided
235 pub(crate) fn cross(self, direction: FlexDirection) -> T {
236 if direction.is_row() {
237 self.height
238 } else {
239 self.width
240 }
241 }
242}
243
244impl Size<f32> {
245 /// A [`Size`] with zero width and height
246 pub const ZERO: Size<f32> = Self { width: 0.0, height: 0.0 };
247}
248
249impl Size<Option<f32>> {
250 /// A [`Size`] with `None` width and height
251 pub const NONE: Size<Option<f32>> = Self { width: None, height: None };
252
253 /// A [`Size<Option<f32>>`] with `Some(width)` and `Some(height)` as parameters
254 #[must_use]
255 pub const fn new(width: f32, height: f32) -> Self {
256 Size { width: Some(width), height: Some(height) }
257 }
258
259 /// Performs Option::unwrap_or on each component separately
260 pub fn unwrap_or(self, alt: Size<f32>) -> Size<f32> {
261 Size { width: self.width.unwrap_or(alt.width), height: self.height.unwrap_or(alt.height) }
262 }
263
264 /// Performs Option::or on each component separately
265 pub fn or(self, alt: Size<Option<f32>>) -> Size<Option<f32>> {
266 Size { width: self.width.or(alt.width), height: self.height.or(alt.height) }
267 }
268}
269
270impl Size<Dimension> {
271 /// Generates a [`Size<Dimension>`] using [`Dimension::Points`] values
272 #[must_use]
273 pub const fn from_points(width: f32, height: f32) -> Self {
274 Size { width: Dimension::Points(width), height: Dimension::Points(height) }
275 }
276
277 /// Generates a [`Size<Dimension>`] using [`Dimension::Percent`] values
278 #[must_use]
279 pub const fn from_percent(width: f32, height: f32) -> Self {
280 Size { width: Dimension::Percent(width), height: Dimension::Percent(height) }
281 }
282
283 /// Generates a [`Size<Dimension>`] using [`Dimension::Auto`] in both width and height
284 pub const AUTO: Size<Dimension> = Self { width: Dimension::Auto, height: Dimension::Auto };
285
286 /// Generates a [`Size<Dimension>`] using [`Dimension::Undefined`] in both width and height
287 pub const UNDEFINED: Size<Dimension> = Self { width: Dimension::Undefined, height: Dimension::Undefined };
288}
289
290/// A 2-dimensional coordinate.
291///
292/// When used in association with a [`Rect`], represents the bottom-left corner.
293#[derive(Debug, Copy, Clone, PartialEq, Eq)]
294pub struct Point<T> {
295 /// The x-coordinate
296 pub x: T,
297 /// The y-coordinate
298 pub y: T,
299}
300
301impl Point<f32> {
302 /// A [`Point`] with values (0,0), representing the origin
303 pub const ZERO: Point<f32> = Self { x: 0.0, y: 0.0 };
304}