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