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