tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 10TOML 3CSS 1350 Definitions

⎇ 2e0d1bd2f08d3e477e564acfbed30c9caa2e0440 src/resolve.rs Rust · 363 lines · 15.4 KB History Who touched what raw

Unit test `compute_constants()` (#184) 7762c2490 · Andreas Weibye ·

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