tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Definitions

⎇ 0.2.x src/resolve.rs Rust · 387 lines · 16.2 KB History Who touched what raw

Rename Rect field start->left end->right (#257) ca514f6c8 · Nico Burns ·

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}