taffy/util/
resolve.rs

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 an absolute length
29    /// Can return `None`
30    fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
31        match self {
32            LengthPercentage::Length(length) => Some(length),
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 an absolute length
40    /// Can return `None`
41    fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
42        match self {
43            LengthPercentageAuto::Length(length) => Some(length),
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 an absolute length
52    ///
53    /// Can return `None`
54    fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
55        match self {
56            Dimension::Length(length) => Some(length),
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 an absolute length
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    use super::{MaybeResolve, ResolveOrZero};
139    use crate::style_helpers::TaffyZero;
140    use core::fmt::Debug;
141
142    // MaybeResolve test runner
143    fn mr_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out)
144    where
145        Lhs: MaybeResolve<Rhs, Out>,
146        Out: PartialEq + Debug,
147    {
148        assert_eq!(input.maybe_resolve(context), expected);
149    }
150
151    // ResolveOrZero test runner
152    fn roz_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out)
153    where
154        Lhs: ResolveOrZero<Rhs, Out>,
155        Out: PartialEq + Debug + TaffyZero,
156    {
157        assert_eq!(input.resolve_or_zero(context), expected);
158    }
159
160    mod maybe_resolve_dimension {
161        use super::mr_case;
162        use crate::style::Dimension;
163
164        /// `Dimension::Auto` should always return `None`
165        ///
166        /// The parent / context should not affect the outcome.
167        #[test]
168        fn resolve_auto() {
169            mr_case(Dimension::Auto, None, None);
170            mr_case(Dimension::Auto, Some(5.0), None);
171            mr_case(Dimension::Auto, Some(-5.0), None);
172            mr_case(Dimension::Auto, Some(0.), None);
173        }
174
175        /// `Dimension::Length` should always return `Some(f32)`
176        /// where the f32 value is the inner absolute length.
177        ///
178        /// The parent / context should not affect the outcome.
179        #[test]
180        fn resolve_length() {
181            mr_case(Dimension::Length(1.0), None, Some(1.0));
182            mr_case(Dimension::Length(1.0), Some(5.0), Some(1.0));
183            mr_case(Dimension::Length(1.0), Some(-5.0), Some(1.0));
184            mr_case(Dimension::Length(1.0), Some(0.), Some(1.0));
185        }
186
187        /// `Dimension::Percent` should return `None` if context is  `None`.
188        /// Otherwise it should return `Some(f32)`
189        /// where the f32 value is the inner value of the percent * context value.
190        ///
191        /// The parent / context __should__ affect the outcome.
192        #[test]
193        fn resolve_percent() {
194            mr_case(Dimension::Percent(1.0), None, None);
195            mr_case(Dimension::Percent(1.0), Some(5.0), Some(5.0));
196            mr_case(Dimension::Percent(1.0), Some(-5.0), Some(-5.0));
197            mr_case(Dimension::Percent(1.0), Some(50.0), Some(50.0));
198        }
199    }
200
201    mod maybe_resolve_size_dimension {
202        use super::mr_case;
203        use crate::geometry::Size;
204        use crate::style::Dimension;
205
206        /// Size<Dimension::Auto> should always return Size<None>
207        ///
208        /// The parent / context should not affect the outcome.
209        #[test]
210        fn maybe_resolve_auto() {
211            mr_case(Size::<Dimension>::auto(), Size::NONE, Size::NONE);
212            mr_case(Size::<Dimension>::auto(), Size::new(5.0, 5.0), Size::NONE);
213            mr_case(Size::<Dimension>::auto(), Size::new(-5.0, -5.0), Size::NONE);
214            mr_case(Size::<Dimension>::auto(), Size::new(0.0, 0.0), Size::NONE);
215        }
216
217        /// Size<Dimension::Length> should always return a Size<Some(f32)>
218        /// where the f32 values are the absolute length.
219        ///
220        /// The parent / context should not affect the outcome.
221        #[test]
222        fn maybe_resolve_length() {
223            mr_case(Size::from_lengths(5.0, 5.0), Size::NONE, Size::new(5.0, 5.0));
224            mr_case(Size::from_lengths(5.0, 5.0), Size::new(5.0, 5.0), Size::new(5.0, 5.0));
225            mr_case(Size::from_lengths(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(5.0, 5.0));
226            mr_case(Size::from_lengths(5.0, 5.0), Size::new(0.0, 0.0), Size::new(5.0, 5.0));
227        }
228
229        /// `Size<Dimension::Percent>` should return `Size<None>` if context is `Size<None>`.
230        /// Otherwise it should return `Size<Some(f32)>`
231        /// where the f32 value is the inner value of the percent * context value.
232        ///
233        /// The context __should__ affect the outcome.
234        #[test]
235        fn maybe_resolve_percent() {
236            mr_case(Size::from_percent(5.0, 5.0), Size::NONE, Size::NONE);
237            mr_case(Size::from_percent(5.0, 5.0), Size::new(5.0, 5.0), Size::new(25.0, 25.0));
238            mr_case(Size::from_percent(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(-25.0, -25.0));
239            mr_case(Size::from_percent(5.0, 5.0), Size::new(0.0, 0.0), Size::new(0.0, 0.0));
240        }
241    }
242
243    mod resolve_or_zero_dimension_to_option_f32 {
244        use super::roz_case;
245        use crate::style::Dimension;
246
247        #[test]
248        fn resolve_or_zero_auto() {
249            roz_case(Dimension::Auto, None, 0.0);
250            roz_case(Dimension::Auto, Some(5.0), 0.0);
251            roz_case(Dimension::Auto, Some(-5.0), 0.0);
252            roz_case(Dimension::Auto, Some(0.0), 0.0);
253        }
254        #[test]
255        fn resolve_or_zero_length() {
256            roz_case(Dimension::Length(5.0), None, 5.0);
257            roz_case(Dimension::Length(5.0), Some(5.0), 5.0);
258            roz_case(Dimension::Length(5.0), Some(-5.0), 5.0);
259            roz_case(Dimension::Length(5.0), Some(0.0), 5.0);
260        }
261        #[test]
262        fn resolve_or_zero_percent() {
263            roz_case(Dimension::Percent(5.0), None, 0.0);
264            roz_case(Dimension::Percent(5.0), Some(5.0), 25.0);
265            roz_case(Dimension::Percent(5.0), Some(-5.0), -25.0);
266            roz_case(Dimension::Percent(5.0), Some(0.0), 0.0);
267        }
268    }
269
270    mod resolve_or_zero_rect_dimension_to_rect {
271        use super::roz_case;
272        use crate::geometry::{Rect, Size};
273        use crate::style::Dimension;
274
275        #[test]
276        fn resolve_or_zero_auto() {
277            roz_case(Rect::<Dimension>::auto(), Size::NONE, Rect::zero());
278            roz_case(Rect::<Dimension>::auto(), Size::new(5.0, 5.0), Rect::zero());
279            roz_case(Rect::<Dimension>::auto(), Size::new(-5.0, -5.0), Rect::zero());
280            roz_case(Rect::<Dimension>::auto(), Size::new(0.0, 0.0), Rect::zero());
281        }
282
283        #[test]
284        fn resolve_or_zero_length() {
285            roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::new(5.0, 5.0, 5.0, 5.0));
286            roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Size::new(5.0, 5.0), Rect::new(5.0, 5.0, 5.0, 5.0));
287            roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Size::new(-5.0, -5.0), Rect::new(5.0, 5.0, 5.0, 5.0));
288            roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::new(5.0, 5.0, 5.0, 5.0));
289        }
290
291        #[test]
292        fn resolve_or_zero_percent() {
293            roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::zero());
294            roz_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            roz_case(
296                Rect::from_percent(5.0, 5.0, 5.0, 5.0),
297                Size::new(-5.0, -5.0),
298                Rect::new(-25.0, -25.0, -25.0, -25.0),
299            );
300            roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::zero());
301        }
302    }
303
304    mod resolve_or_zero_rect_dimension_to_rect_f32_via_option {
305        use super::roz_case;
306        use crate::geometry::Rect;
307        use crate::style::Dimension;
308
309        #[test]
310        fn resolve_or_zero_auto() {
311            roz_case(Rect::<Dimension>::auto(), None, Rect::zero());
312            roz_case(Rect::<Dimension>::auto(), Some(5.0), Rect::zero());
313            roz_case(Rect::<Dimension>::auto(), Some(-5.0), Rect::zero());
314            roz_case(Rect::<Dimension>::auto(), Some(0.0), Rect::zero());
315        }
316
317        #[test]
318        fn resolve_or_zero_length() {
319            roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), None, Rect::new(5.0, 5.0, 5.0, 5.0));
320            roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Some(5.0), Rect::new(5.0, 5.0, 5.0, 5.0));
321            roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Some(-5.0), Rect::new(5.0, 5.0, 5.0, 5.0));
322            roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::new(5.0, 5.0, 5.0, 5.0));
323        }
324
325        #[test]
326        fn resolve_or_zero_percent() {
327            roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), None, Rect::zero());
328            roz_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));
329            roz_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));
330            roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::zero());
331        }
332    }
333}