1//! Geometric primitives useful for layout
2
3use core::ops::Add;
4
5use crate::number::Number;
6use crate::style::{Dimension, FlexDirection};
7
8/// An axis-aligned UI rectangle
9#[derive(Debug, Copy, Clone, PartialEq)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11#[cfg_attr(feature = "serde", serde(default))]
12pub struct Rect<T> {
13 /// This can represent either the x-coordinate of the starting edge,
14 /// or the amount of padding on the starting side.
15 ///
16 /// The starting edge is the left edge when working with LTR text,
17 /// and the right edge when working with RTL text.
18 pub start: T,
19 /// This can represent either the x-coordinate of the ending edge,
20 /// or the amount of padding on the ending side.
21 ///
22 /// The ending edge is the right edge when working with LTR text,
23 /// and the left edge when working with RTL text.
24 pub end: T,
25 /// This can represent either the y-coordinate of the top edge,
26 /// or the amount of padding on the top side.
27 pub top: T,
28 /// This can represent either the y-coordinate of the bottom edge,
29 /// or the amount of padding on the bottom side.
30 pub bottom: T,
31}
32
33impl<T> Rect<T> {
34 /// Applies the function `f` to all four sides of the [`Rect`]
35 ///
36 /// This is used to transform a `Rect<T>` into a `Rect<R>`.
37 pub(crate) fn map<R, F>(self, f: F) -> Rect<R>
38 where
39 F: Fn(T) -> R,
40 {
41 Rect { start: f(self.start), end: f(self.end), top: f(self.top), bottom: f(self.bottom) }
42 }
43
44 /// Applies the function `f` to all four sides of the rect
45 ///
46 /// When applied to the left and right sides, the width is used
47 /// as the second parameter of `f`.
48 /// When applied to the top or bottom sides, the height is used instead.
49 pub(crate) fn zip_size<R, F, U>(self, size: Size<U>, f: F) -> Rect<R>
50 where
51 F: Fn(T, U) -> R,
52 U: Copy,
53 {
54 Rect {
55 start: f(self.start, size.width),
56 end: f(self.end, size.width),
57 top: f(self.top, size.height),
58 bottom: f(self.bottom, size.height),
59 }
60 }
61}
62
63impl<T> Rect<T>
64where
65 T: Add<Output = T> + Copy + Clone,
66{
67 /// The sum of [`Rect.start`](Rect) and [`Rect.end`](Rect)
68 ///
69 /// This is typically used when computing total padding.
70 ///
71 /// **NOTE:** this is *not* the width of the rectangle.
72 pub(crate) fn horizontal_axis_sum(&self) -> T {
73 self.start + self.end
74 }
75
76 /// The sum of [`Rect.top`](Rect) and [`Rect.bottom`](Rect)
77 ///
78 /// This is typically used when computing total padding.
79 ///
80 /// **NOTE:** this is *not* the height of the rectangle.
81 pub(crate) fn vertical_axis_sum(&self) -> T {
82 self.top + self.bottom
83 }
84
85 /// The sum of the two fields of the [`Rect`] representing the main axis.
86 ///
87 /// This is typically used when computing total padding.
88 ///
89 /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::horizontal`].
90 /// Otherwise, this is [`Rect::vertical`].
91 pub(crate) fn main_axis_sum(&self, direction: FlexDirection) -> T {
92 if direction.is_row() {
93 self.horizontal_axis_sum()
94 } else {
95 self.vertical_axis_sum()
96 }
97 }
98
99 /// The sum of the two fields of the [`Rect`] representing the cross axis.
100 ///
101 /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::vertical`].
102 /// Otherwise, this is [`Rect::horizontal`].
103 pub(crate) fn cross_axis_sum(&self, direction: FlexDirection) -> T {
104 if direction.is_row() {
105 self.vertical_axis_sum()
106 } else {
107 self.horizontal_axis_sum()
108 }
109 }
110}
111
112impl<T> Rect<T>
113where
114 T: Copy + Clone,
115{
116 /// The `start` or `top` value of the [`Rect`], from the perspective of the main layout axis
117 pub(crate) fn main_start(&self, direction: FlexDirection) -> T {
118 if direction.is_row() {
119 self.start
120 } else {
121 self.top
122 }
123 }
124
125 /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
126 pub(crate) fn main_end(&self, direction: FlexDirection) -> T {
127 if direction.is_row() {
128 self.end
129 } else {
130 self.bottom
131 }
132 }
133
134 /// The `start` or `top` value of the [`Rect`], from the perspective of the cross layout axis
135 pub(crate) fn cross_start(&self, direction: FlexDirection) -> T {
136 if direction.is_row() {
137 self.top
138 } else {
139 self.start
140 }
141 }
142
143 /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
144 pub(crate) fn cross_end(&self, direction: FlexDirection) -> T {
145 if direction.is_row() {
146 self.bottom
147 } else {
148 self.end
149 }
150 }
151}
152
153/// The width and height of a [`Rect`]
154#[derive(Debug, Copy, Clone, PartialEq)]
155#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
156#[cfg_attr(feature = "serde", serde(default))]
157pub struct Size<T> {
158 /// The x extent of the rectangle
159 pub width: T,
160 /// The y extent of the rectangle
161 pub height: T,
162}
163
164impl Size<()> {
165 /// Generates a `Size<Number>` with undefined width and height
166 #[must_use]
167 pub fn undefined() -> Size<Number> {
168 Size { width: Number::Undefined, height: Number::Undefined }
169 }
170}
171
172impl<T> Size<T> {
173 /// Applies the function `f` to both the width and height
174 ///
175 /// This is used to transform a `Rect<T>` into a `Rect<R>`.
176 pub fn map<R, F>(self, f: F) -> Size<R>
177 where
178 F: Fn(T) -> R,
179 {
180 Size { width: f(self.width), height: f(self.height) }
181 }
182
183 /// Sets the extent of the main layout axis
184 ///
185 /// Whether this is the width or height depends on the `direction` provided
186 pub(crate) fn set_main(&mut self, direction: FlexDirection, value: T) {
187 if direction.is_row() {
188 self.width = value
189 } else {
190 self.height = value
191 }
192 }
193
194 /// Sets the extent of the cross layout axis
195 ///
196 /// Whether this is the width or height depends on the `direction` provided
197 pub(crate) fn set_cross(&mut self, direction: FlexDirection, value: T) {
198 if direction.is_row() {
199 self.height = value
200 } else {
201 self.width = value
202 }
203 }
204
205 /// Gets the extent of the main layout axis
206 ///
207 /// Whether this is the width or height depends on the `direction` provided
208 pub(crate) fn main(self, direction: FlexDirection) -> T {
209 if direction.is_row() {
210 self.width
211 } else {
212 self.height
213 }
214 }
215
216 /// Gets the extent of the cross layout axis
217 ///
218 /// Whether this is the width or height depends on the `direction` provided
219 pub(crate) fn cross(self, direction: FlexDirection) -> T {
220 if direction.is_row() {
221 self.height
222 } else {
223 self.width
224 }
225 }
226}
227
228impl Size<f32> {
229 /// A [`Size`] with zero width and height
230 #[must_use]
231 pub fn zero() -> Self {
232 Self { width: 0.0, height: 0.0 }
233 }
234}
235
236impl Size<Dimension> {
237 /// Converts any `parent`-relative values for size into an absolute size
238 pub(crate) fn resolve(&self, parent: Size<Number>) -> Size<Number> {
239 Size { width: self.width.resolve(parent.width), height: self.height.resolve(parent.height) }
240 }
241}
242
243/// A 2-dimensional coordinate.
244///
245/// When used in association with a [`Rect`], represents the bottom-left corner.
246#[derive(Debug, Copy, Clone, PartialEq)]
247pub struct Point<T> {
248 /// The x-coordinate
249 pub x: T,
250 /// The y-coordinate
251 pub y: T,
252}
253
254impl Point<f32> {
255 /// A [`Point`] with values (0,0), representing the origin
256 #[must_use]
257 pub fn zero() -> Self {
258 Self { x: 0.0, y: 0.0 }
259 }
260}