tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 10TOML 3CSS 1348 Definitions

⎇ 2f5d0d1df229df22613a9cd7cd1bd74e3f192d47 src/util/resolve.rs Rust · 364 lines · 15.4 KB History Who touched what raw

Refactor/module hierarchy (#443) 628d7e1bc · Nico Burns ·

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