Rename Rect field start->left end->right (#257)
1//! Helper trait to calculate dimensions during layout resolution
2
3use crate::prelude::{Dimension, Rect, Size};
4
5/// Trait to encapsulate behaviour where we need to resolve from a
6/// potentially context-dependent size or dimension into
7/// a context-independent size or dimension.
8///
9/// Will return a default value if it unable to resolve.
10pub(crate) trait ResolveOrDefault<TContext, TOutput> {
11 /// Resolve a dimension that might be dependent on a context, with a default fallback value
12 fn resolve_or_default(self, context: TContext) -> TOutput;
13}
14
15/// Trait to encapsulate behaviour where we need to resolve from a
16/// potentially context-dependent size or dimension into
17/// a context-independent size or dimension.
18///
19/// Will return a `None` if it unable to resolve.
20pub(crate) trait MaybeResolve<In, Out> {
21 /// Resolve a dimension that might be dependent on a context, with `None` as fallback value
22 fn maybe_resolve(self, context: In) -> Out;
23}
24
25impl MaybeResolve<Option<f32>, Option<f32>> for Dimension {
26 /// Converts the given [`Dimension`] into a concrete value of points
27 ///
28 /// Can return `None`
29 fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
30 match self {
31 Dimension::Points(points) => Some(points),
32 // parent_dim * percent
33 Dimension::Percent(percent) => context.map(|dim| dim * percent),
34 _ => None,
35 }
36 }
37}
38
39impl MaybeResolve<f32, Option<f32>> for Dimension {
40 /// Converts the given [`Dimension`] into a concrete value of points
41 ///
42 /// Can return `None`
43 fn maybe_resolve(self, context: f32) -> Option<f32> {
44 match self {
45 Dimension::Points(points) => Some(points),
46 // parent_dim * percent
47 Dimension::Percent(percent) => Some(context * percent),
48 _ => None,
49 }
50 }
51}
52
53// Generic MaybeResolve for Size
54impl<In, Out, T: MaybeResolve<In, Out>> MaybeResolve<Size<In>, Size<Out>> for Size<T> {
55 /// Converts any `parent`-relative values for size into an absolute size
56 fn maybe_resolve(self, context: Size<In>) -> Size<Out> {
57 Size { width: self.width.maybe_resolve(context.width), height: self.height.maybe_resolve(context.height) }
58 }
59}
60
61impl ResolveOrDefault<Option<f32>, f32> for Dimension {
62 /// Will return a default value of result is evaluated to `None`
63 fn resolve_or_default(self, context: Option<f32>) -> f32 {
64 self.maybe_resolve(context).unwrap_or(0.0)
65 }
66}
67
68impl ResolveOrDefault<Size<Option<f32>>, Size<f32>> for Size<Dimension> {
69 fn resolve_or_default(self, context: Size<Option<f32>>) -> Size<f32> {
70 Size {
71 width: self.width.resolve_or_default(context.width),
72 height: self.height.resolve_or_default(context.height),
73 }
74 }
75}
76
77impl ResolveOrDefault<Size<Option<f32>>, Rect<f32>> for Rect<Dimension> {
78 fn resolve_or_default(self, context: Size<Option<f32>>) -> Rect<f32> {
79 Rect {
80 left: self.left.resolve_or_default(context.width),
81 right: self.right.resolve_or_default(context.width),
82 top: self.top.resolve_or_default(context.height),
83 bottom: self.bottom.resolve_or_default(context.height),
84 }
85 }
86}
87
88impl ResolveOrDefault<Option<f32>, Rect<f32>> for Rect<Dimension> {
89 fn resolve_or_default(self, context: Option<f32>) -> Rect<f32> {
90 Rect {
91 left: self.left.resolve_or_default(context),
92 right: self.right.resolve_or_default(context),
93 top: self.top.resolve_or_default(context),
94 bottom: self.bottom.resolve_or_default(context),
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 mod maybe_resolve_dimension {
102
103 use crate::resolve::MaybeResolve;
104 use crate::style::Dimension;
105 use rstest::rstest;
106
107 /// `Dimension::Undefined` should always return `None`
108 ///
109 /// The parent / context should not affect the outcome.
110 #[rstest]
111 #[case(Dimension::Undefined, None, None)]
112 #[case(Dimension::Undefined, Some(5.0), None)]
113 #[case(Dimension::Undefined, Some(-5.0), None)]
114 #[case(Dimension::Undefined, Some(0.), None)]
115 fn resolve_undefined(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
116 assert_eq!(input.maybe_resolve(context), expected);
117 }
118
119 /// `Dimension::Auto` should always return `None`
120 ///
121 /// The parent / context should not affect the outcome.
122 #[rstest]
123 #[case(Dimension::Auto, None, None)]
124 #[case(Dimension::Auto, Some(5.0), None)]
125 #[case(Dimension::Auto, Some(-5.0), None)]
126 #[case(Dimension::Auto, Some(0.), None)]
127 fn resolve_auto(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
128 assert_eq!(input.maybe_resolve(context), expected);
129 }
130
131 /// `Dimension::Points` should always return `Some(f32)`
132 /// where the f32 value is the inner value of the points.
133 ///
134 /// The parent / context should not affect the outcome.
135 #[rstest]
136 #[case(Dimension::Points(1.0), None, Some(1.0))]
137 #[case(Dimension::Points(1.0), Some(5.0), Some(1.0))]
138 #[case(Dimension::Points(1.0), Some(-5.0), Some(1.0))]
139 #[case(Dimension::Points(1.0), Some(0.), Some(1.0))]
140 fn resolve_points(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
141 assert_eq!(input.maybe_resolve(context), expected);
142 }
143
144 /// `Dimension::Percent` should return `None` if context is `None`.
145 /// Otherwise it should return `Some(f32)`
146 /// where the f32 value is the inner value of the percent * context value.
147 ///
148 /// The parent / context __should__ affect the outcome.
149 #[rstest]
150 #[case(Dimension::Percent(1.0), None, None)]
151 #[case(Dimension::Percent(1.0), Some(5.0), Some(5.0))]
152 #[case(Dimension::Percent(1.0), Some(-5.0), Some(-5.0))]
153 #[case(Dimension::Percent(1.0), Some(50.0), Some(50.0))]
154 fn resolve_percent(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
155 assert_eq!(input.maybe_resolve(context), expected);
156 }
157 }
158
159 mod maybe_resolve_size_dimension {
160 use crate::{prelude::Size, resolve::MaybeResolve, style::Dimension};
161 use rstest::rstest;
162
163 /// Size<Dimension::Undefined> should always return Size<None>
164 ///
165 /// The parent / context should not affect the outcome.
166 #[rstest]
167 #[case(Size::UNDEFINED, Size::NONE, Size::NONE)]
168 #[case(Size::UNDEFINED, Size::new(5.0, 5.0), Size::NONE)]
169 #[case(Size::UNDEFINED, Size::new(-5.0, -5.0), Size::NONE)]
170 #[case(Size::UNDEFINED, Size::new(0.0, 0.0), Size::NONE)]
171 fn maybe_resolve_undefined(
172 #[case] input: Size<Dimension>,
173 #[case] context: Size<Option<f32>>,
174 #[case] expected: Size<Option<f32>>,
175 ) {
176 assert_eq!(input.maybe_resolve(context), expected);
177 }
178
179 /// Size<Dimension::Auto> should always return Size<None>
180 ///
181 /// The parent / context should not affect the outcome.
182 #[rstest]
183 #[case(Size::AUTO, Size::NONE, Size::NONE)]
184 #[case(Size::AUTO, Size::new(5.0, 5.0), Size::NONE)]
185 #[case(Size::AUTO, Size::new(-5.0, -5.0), Size::NONE)]
186 #[case(Size::AUTO, Size::new(0.0, 0.0), Size::NONE)]
187 fn maybe_resolve_auto(
188 #[case] input: Size<Dimension>,
189 #[case] context: Size<Option<f32>>,
190 #[case] expected: Size<Option<f32>>,
191 ) {
192 assert_eq!(input.maybe_resolve(context), expected);
193 }
194
195 /// Size<Dimension::Points> should always return a Size<Some(f32)>
196 /// where the f32 values are the inner value of the points.
197 ///
198 /// The parent / context should not affect the outcome.
199 #[rstest]
200 #[case(Size::from_points(5.0, 5.0), Size::NONE, Size::new(5.0, 5.0))]
201 #[case(Size::from_points(5.0, 5.0), Size::new(5.0, 5.0), Size::new(5.0, 5.0))]
202 #[case(Size::from_points(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(5.0, 5.0))]
203 #[case(Size::from_points(5.0, 5.0), Size::new(0.0, 0.0), Size::new(5.0, 5.0))]
204 fn maybe_resolve_points(
205 #[case] input: Size<Dimension>,
206 #[case] context: Size<Option<f32>>,
207 #[case] expected: Size<Option<f32>>,
208 ) {
209 assert_eq!(input.maybe_resolve(context), expected);
210 }
211
212 /// `Size<Dimension::Percent>` should return `Size<None>` if context is `Size<None>`.
213 /// Otherwise it should return `Size<Some(f32)>`
214 /// where the f32 value is the inner value of the percent * context value.
215 ///
216 /// The context __should__ affect the outcome.
217 #[rstest]
218 #[case(Size::from_percent(5.0, 5.0), Size::NONE, Size::NONE)]
219 #[case(Size::from_percent(5.0, 5.0), Size::new(5.0, 5.0), Size::new(25.0, 25.0))]
220 #[case(Size::from_percent(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(-25.0, -25.0))]
221 #[case(Size::from_percent(5.0, 5.0), Size::new(0.0, 0.0), Size::new(0.0, 0.0))]
222 fn maybe_resolve_percent(
223 #[case] input: Size<Dimension>,
224 #[case] context: Size<Option<f32>>,
225 #[case] expected: Size<Option<f32>>,
226 ) {
227 assert_eq!(input.maybe_resolve(context), expected);
228 }
229 }
230
231 mod resolve_or_default_dimension_to_option_f32 {
232 use crate::resolve::ResolveOrDefault;
233 use crate::style::Dimension;
234 use rstest::rstest;
235
236 #[rstest]
237 #[case(Dimension::Undefined, None, 0.0)]
238 #[case(Dimension::Undefined, Some(5.0), 0.0)]
239 #[case(Dimension::Undefined, Some(-5.0), 0.0)]
240 #[case(Dimension::Undefined, Some(0.0), 0.0)]
241 fn resolve_or_default_undefined(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
242 assert_eq!(input.resolve_or_default(context), expected);
243 }
244 #[rstest]
245 #[case(Dimension::Auto, None, 0.0)]
246 #[case(Dimension::Auto, Some(5.0), 0.0)]
247 #[case(Dimension::Auto, Some(-5.0), 0.0)]
248 #[case(Dimension::Auto, Some(0.0), 0.0)]
249 fn resolve_or_default_auto(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
250 assert_eq!(input.resolve_or_default(context), expected);
251 }
252 #[rstest]
253 #[case(Dimension::Points(5.0), None, 5.0)]
254 #[case(Dimension::Points(5.0), Some(5.0), 5.0)]
255 #[case(Dimension::Points(5.0), Some(-5.0), 5.0)]
256 #[case(Dimension::Points(5.0), Some(0.0), 5.0)]
257 fn resolve_or_default_points(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
258 assert_eq!(input.resolve_or_default(context), expected);
259 }
260 #[rstest]
261 #[case(Dimension::Percent(5.0), None, 0.0)]
262 #[case(Dimension::Percent(5.0), Some(5.0), 25.0)]
263 #[case(Dimension::Percent(5.0), Some(-5.0), -25.0)]
264 #[case(Dimension::Percent(5.0), Some(0.0), 0.0)]
265 fn resolve_or_default_percent(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
266 assert_eq!(input.resolve_or_default(context), expected);
267 }
268 }
269
270 mod resolve_or_default_rect_dimension_to_rect {
271 use crate::geometry::{Rect, Size};
272 use crate::resolve::ResolveOrDefault;
273 use crate::style::Dimension;
274 use rstest::rstest;
275
276 #[rstest]
277 #[case(Rect::UNDEFINED, Size::NONE, Rect::ZERO)]
278 #[case(Rect::UNDEFINED, Size::new(5.0, 5.0), Rect::ZERO)]
279 #[case(Rect::UNDEFINED, Size::new(-5.0, -5.0), Rect::ZERO)]
280 #[case(Rect::UNDEFINED, Size::new(0.0, 0.0), Rect::ZERO)]
281 fn resolve_or_default_undefined(
282 #[case] input: Rect<Dimension>,
283 #[case] context: Size<Option<f32>>,
284 #[case] expected: Rect<f32>,
285 ) {
286 assert_eq!(input.resolve_or_default(context), expected);
287 }
288
289 #[rstest]
290 #[case(Rect::AUTO, Size::NONE, Rect::ZERO)]
291 #[case(Rect::AUTO, Size::new(5.0, 5.0), Rect::ZERO)]
292 #[case(Rect::AUTO, Size::new(-5.0, -5.0), Rect::ZERO)]
293 #[case(Rect::AUTO, Size::new(0.0, 0.0), Rect::ZERO)]
294 fn resolve_or_default_auto(
295 #[case] input: Rect<Dimension>,
296 #[case] context: Size<Option<f32>>,
297 #[case] expected: Rect<f32>,
298 ) {
299 assert_eq!(input.resolve_or_default(context), expected);
300 }
301
302 #[rstest]
303 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::new(5.0, 5.0, 5.0, 5.0))]
304 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::new(5.0, 5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
305 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::new(-5.0, -5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
306 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
307 fn resolve_or_default_points(
308 #[case] input: Rect<Dimension>,
309 #[case] context: Size<Option<f32>>,
310 #[case] expected: Rect<f32>,
311 ) {
312 assert_eq!(input.resolve_or_default(context), expected);
313 }
314
315 #[rstest]
316 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::ZERO)]
317 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(5.0, 5.0), Rect::new(25.0, 25.0, 25.0, 25.0))]
318 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(-5.0, -5.0), Rect::new(-25.0, -25.0, -25.0, -25.0))]
319 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::ZERO)]
320 fn resolve_or_default_percent(
321 #[case] input: Rect<Dimension>,
322 #[case] context: Size<Option<f32>>,
323 #[case] expected: Rect<f32>,
324 ) {
325 assert_eq!(input.resolve_or_default(context), expected);
326 }
327 }
328
329 mod resolve_or_default_rect_dimension_to_rect_f32_via_option {
330 use crate::geometry::Rect;
331 use crate::resolve::ResolveOrDefault;
332 use crate::style::Dimension;
333 use rstest::rstest;
334
335 #[rstest]
336 #[case(Rect::UNDEFINED, None, Rect::ZERO)]
337 #[case(Rect::UNDEFINED, Some(5.0), Rect::ZERO)]
338 #[case(Rect::UNDEFINED, Some(-5.0), Rect::ZERO)]
339 #[case(Rect::UNDEFINED, Some(0.0), Rect::ZERO)]
340 fn resolve_or_default_undefined(
341 #[case] input: Rect<Dimension>,
342 #[case] context: Option<f32>,
343 #[case] expected: Rect<f32>,
344 ) {
345 assert_eq!(input.resolve_or_default(context), expected);
346 }
347
348 #[rstest]
349 #[case(Rect::AUTO, None, Rect::ZERO)]
350 #[case(Rect::AUTO, Some(5.0), Rect::ZERO)]
351 #[case(Rect::AUTO, Some(-5.0), Rect::ZERO)]
352 #[case(Rect::AUTO, Some(0.0), Rect::ZERO)]
353 fn resolve_or_default_auto(
354 #[case] input: Rect<Dimension>,
355 #[case] context: Option<f32>,
356 #[case] expected: Rect<f32>,
357 ) {
358 assert_eq!(input.resolve_or_default(context), expected);
359 }
360
361 #[rstest]
362 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), None, Rect::new(5.0, 5.0, 5.0, 5.0))]
363 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Some(5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
364 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Some(-5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
365 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
366 fn resolve_or_default_points(
367 #[case] input: Rect<Dimension>,
368 #[case] context: Option<f32>,
369 #[case] expected: Rect<f32>,
370 ) {
371 assert_eq!(input.resolve_or_default(context), expected);
372 }
373
374 #[rstest]
375 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), None, Rect::ZERO)]
376 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(5.0), Rect::new(25.0, 25.0, 25.0, 25.0))]
377 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(-5.0), Rect::new(-25.0, -25.0, -25.0, -25.0))]
378 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::ZERO)]
379 fn resolve_or_default_percent(
380 #[case] input: Rect<Dimension>,
381 #[case] context: Option<f32>,
382 #[case] expected: Rect<f32>,
383 ) {
384 assert_eq!(input.resolve_or_default(context), expected);
385 }
386 }
387}