Minor: reordered constrcutor aguments in Rect<T>::map() (#430)
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}