Fix flexbox baseline alignment (#325)
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}