Skip to main content

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_plain_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_plain_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            // The content keyword is only valid for flex-basis. In any other context it behaves as auto.
65            CompactLength::CONTENT_TAG => None,
66            CompactLength::LENGTH_TAG => Some(self.0.value()),
67            CompactLength::PERCENT_TAG => context.map(|dim| dim * self.0.value()),
68            #[cfg(feature = "calc")]
69            _ if self.0.is_plain_calc() => context.map(|dim| calc(self.0.calc_value(), dim)),
70            // Intrinsic sizing keywords cannot be resolved to a definite size out of context.
71            // Layout algorithms that support them must handle them explicitly.
72            _ if self.0.is_sizing_keyword() => None,
73            _ => unreachable!(),
74        }
75    }
76}
77
78// Generic implementation of MaybeResolve for f32 context where MaybeResolve is implemented
79// for Option<f32> context
80impl<T: MaybeResolve<Option<f32>, Option<f32>>> MaybeResolve<f32, Option<f32>> for T {
81    /// Converts the given MaybeResolve value into an absolute length
82    /// Can return `None`
83    fn maybe_resolve(self, context: f32, calc: impl Fn(*const (), f32) -> f32) -> Option<f32> {
84        self.maybe_resolve(Some(context), calc)
85    }
86}
87
88// Generic MaybeResolve for Size
89impl<In, Out, T: MaybeResolve<In, Out>> MaybeResolve<Size<In>, Size<Out>> for Size<T> {
90    /// Converts any `parent`-relative values for size into an absolute size
91    #[inline(always)]
92    fn maybe_resolve(self, context: Size<In>, calc: impl Fn(*const (), f32) -> f32) -> Size<Out> {
93        Size {
94            width: self.width.maybe_resolve(context.width, &calc),
95            height: self.height.maybe_resolve(context.height, &calc),
96        }
97    }
98}
99
100impl ResolveOrZero<Option<f32>, f32> for LengthPercentage {
101    /// Will return a default value of result is evaluated to `None`
102    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> f32 {
103        self.maybe_resolve(context, calc).unwrap_or(0.0)
104    }
105}
106
107impl ResolveOrZero<Option<f32>, f32> for LengthPercentageAuto {
108    /// Will return a default value of result is evaluated to `None`
109    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> f32 {
110        self.maybe_resolve(context, calc).unwrap_or(0.0)
111    }
112}
113
114impl ResolveOrZero<Option<f32>, f32> for Dimension {
115    /// Will return a default value of result is evaluated to `None`
116    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> f32 {
117        self.maybe_resolve(context, calc).unwrap_or(0.0)
118    }
119}
120
121// Generic ResolveOrZero for Size
122impl<In, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Size<Out>> for Size<T> {
123    /// Converts any `parent`-relative values for size into an absolute size
124    fn resolve_or_zero(self, context: Size<In>, calc: impl Fn(*const (), f32) -> f32) -> Size<Out> {
125        Size {
126            width: self.width.resolve_or_zero(context.width, &calc),
127            height: self.height.resolve_or_zero(context.height, &calc),
128        }
129    }
130}
131
132// Generic ResolveOrZero for resolving Rect against Size
133impl<In: Copy, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Rect<Out>> for Rect<T> {
134    /// Converts any `parent`-relative values for Rect into an absolute Rect
135    fn resolve_or_zero(self, context: Size<In>, calc: impl Fn(*const (), f32) -> f32) -> Rect<Out> {
136        Rect {
137            left: self.left.resolve_or_zero(context.width, &calc),
138            right: self.right.resolve_or_zero(context.width, &calc),
139            top: self.top.resolve_or_zero(context.height, &calc),
140            bottom: self.bottom.resolve_or_zero(context.height, &calc),
141        }
142    }
143}
144
145// Generic ResolveOrZero for resolving Rect against Option
146impl<Out: TaffyZero, T: ResolveOrZero<Option<f32>, Out>> ResolveOrZero<Option<f32>, Rect<Out>> for Rect<T> {
147    /// Converts any `parent`-relative values for Rect into an absolute Rect
148    #[inline(always)]
149    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> Rect<Out> {
150        Rect {
151            left: self.left.resolve_or_zero(context, &calc),
152            right: self.right.resolve_or_zero(context, &calc),
153            top: self.top.resolve_or_zero(context, &calc),
154            bottom: self.bottom.resolve_or_zero(context, &calc),
155        }
156    }
157}
158
159// Generic ResolveOrZero for resolving Rect against f32
160impl<Out: TaffyZero, T: ResolveOrZero<f32, Out>> ResolveOrZero<f32, Rect<Out>> for Rect<T> {
161    /// Converts any `parent`-relative values for Rect into an absolute Rect
162    fn resolve_or_zero(self, context: f32, calc: impl Fn(*const (), f32) -> f32) -> Rect<Out> {
163        Rect {
164            left: self.left.resolve_or_zero(context, &calc),
165            right: self.right.resolve_or_zero(context, &calc),
166            top: self.top.resolve_or_zero(context, &calc),
167            bottom: self.bottom.resolve_or_zero(context, &calc),
168        }
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::{MaybeResolve, ResolveOrZero};
175    use crate::style_helpers::TaffyZero;
176    use core::fmt::Debug;
177
178    // MaybeResolve test runner
179    fn mr_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out)
180    where
181        Lhs: MaybeResolve<Rhs, Out>,
182        Out: PartialEq + Debug,
183    {
184        assert_eq!(input.maybe_resolve(context, |_, _| 42.42), expected);
185    }
186
187    // ResolveOrZero test runner
188    fn roz_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out)
189    where
190        Lhs: ResolveOrZero<Rhs, Out>,
191        Out: PartialEq + Debug + TaffyZero,
192    {
193        assert_eq!(input.resolve_or_zero(context, |_, _| 42.42), expected);
194    }
195
196    mod maybe_resolve_dimension {
197        use super::mr_case;
198        use crate::style::Dimension;
199        use crate::style_helpers::*;
200
201        /// `Dimension::Auto` should always return `None`
202        ///
203        /// The parent / context should not affect the outcome.
204        #[test]
205        fn resolve_auto() {
206            mr_case(Dimension::AUTO, None, None);
207            mr_case(Dimension::AUTO, Some(5.0), None);
208            mr_case(Dimension::AUTO, Some(-5.0), None);
209            mr_case(Dimension::AUTO, Some(0.), None);
210        }
211
212        /// `Dimension::Length` should always return `Some(f32)`
213        /// where the f32 value is the inner absolute length.
214        ///
215        /// The parent / context should not affect the outcome.
216        #[test]
217        fn resolve_length() {
218            mr_case(Dimension::from_length(1.0), None, Some(1.0));
219            mr_case(Dimension::from_length(1.0), Some(5.0), Some(1.0));
220            mr_case(Dimension::from_length(1.0), Some(-5.0), Some(1.0));
221            mr_case(Dimension::from_length(1.0), Some(0.), Some(1.0));
222        }
223
224        /// `Dimension::Percent` should return `None` if context is  `None`.
225        /// Otherwise it should return `Some(f32)`
226        /// where the f32 value is the inner value of the percent * context value.
227        ///
228        /// The parent / context __should__ affect the outcome.
229        #[test]
230        fn resolve_percent() {
231            mr_case(Dimension::from_percent(1.0), None, None);
232            mr_case(Dimension::from_percent(1.0), Some(5.0), Some(5.0));
233            mr_case(Dimension::from_percent(1.0), Some(-5.0), Some(-5.0));
234            mr_case(Dimension::from_percent(1.0), Some(50.0), Some(50.0));
235        }
236    }
237
238    mod maybe_resolve_size_dimension {
239        use super::mr_case;
240        use crate::geometry::Size;
241        use crate::style::Dimension;
242
243        /// Size<Dimension::Auto> should always return Size<None>
244        ///
245        /// The parent / context should not affect the outcome.
246        #[test]
247        fn maybe_resolve_auto() {
248            mr_case(Size::<Dimension>::auto(), Size::NONE, Size::NONE);
249            mr_case(Size::<Dimension>::auto(), Size::new(5.0, 5.0), Size::NONE);
250            mr_case(Size::<Dimension>::auto(), Size::new(-5.0, -5.0), Size::NONE);
251            mr_case(Size::<Dimension>::auto(), Size::new(0.0, 0.0), Size::NONE);
252        }
253
254        /// Size<Dimension::Length> should always return a Size<Some(f32)>
255        /// where the f32 values are the absolute length.
256        ///
257        /// The parent / context should not affect the outcome.
258        #[test]
259        fn maybe_resolve_length() {
260            mr_case(Size::from_lengths(5.0, 5.0), Size::NONE, Size::new(5.0, 5.0));
261            mr_case(Size::from_lengths(5.0, 5.0), Size::new(5.0, 5.0), Size::new(5.0, 5.0));
262            mr_case(Size::from_lengths(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(5.0, 5.0));
263            mr_case(Size::from_lengths(5.0, 5.0), Size::new(0.0, 0.0), Size::new(5.0, 5.0));
264        }
265
266        /// `Size<Dimension::Percent>` should return `Size<None>` if context is `Size<None>`.
267        /// Otherwise it should return `Size<Some(f32)>`
268        /// where the f32 value is the inner value of the percent * context value.
269        ///
270        /// The context __should__ affect the outcome.
271        #[test]
272        fn maybe_resolve_percent() {
273            mr_case(Size::from_percent(5.0, 5.0), Size::NONE, Size::NONE);
274            mr_case(Size::from_percent(5.0, 5.0), Size::new(5.0, 5.0), Size::new(25.0, 25.0));
275            mr_case(Size::from_percent(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(-25.0, -25.0));
276            mr_case(Size::from_percent(5.0, 5.0), Size::new(0.0, 0.0), Size::new(0.0, 0.0));
277        }
278    }
279
280    mod resolve_or_zero_dimension_to_option_f32 {
281        use super::roz_case;
282        use crate::style::Dimension;
283        use crate::style_helpers::*;
284
285        #[test]
286        fn resolve_or_zero_auto() {
287            roz_case(Dimension::AUTO, None, 0.0);
288            roz_case(Dimension::AUTO, Some(5.0), 0.0);
289            roz_case(Dimension::AUTO, Some(-5.0), 0.0);
290            roz_case(Dimension::AUTO, Some(0.0), 0.0);
291        }
292        #[test]
293        fn resolve_or_zero_length() {
294            roz_case(Dimension::from_length(5.0), None, 5.0);
295            roz_case(Dimension::from_length(5.0), Some(5.0), 5.0);
296            roz_case(Dimension::from_length(5.0), Some(-5.0), 5.0);
297            roz_case(Dimension::from_length(5.0), Some(0.0), 5.0);
298        }
299        #[test]
300        fn resolve_or_zero_percent() {
301            roz_case(Dimension::from_percent(5.0), None, 0.0);
302            roz_case(Dimension::from_percent(5.0), Some(5.0), 25.0);
303            roz_case(Dimension::from_percent(5.0), Some(-5.0), -25.0);
304            roz_case(Dimension::from_percent(5.0), Some(0.0), 0.0);
305        }
306    }
307
308    mod resolve_or_zero_rect_dimension_to_rect {
309        use super::roz_case;
310        use crate::geometry::{Rect, Size};
311        use crate::style::Dimension;
312
313        #[test]
314        fn resolve_or_zero_auto() {
315            roz_case(Rect::<Dimension>::auto(), Size::NONE, Rect::zero());
316            roz_case(Rect::<Dimension>::auto(), Size::new(5.0, 5.0), Rect::zero());
317            roz_case(Rect::<Dimension>::auto(), Size::new(-5.0, -5.0), Rect::zero());
318            roz_case(Rect::<Dimension>::auto(), Size::new(0.0, 0.0), Rect::zero());
319        }
320
321        #[test]
322        fn resolve_or_zero_length() {
323            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));
324            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));
325            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));
326            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));
327        }
328
329        #[test]
330        fn resolve_or_zero_percent() {
331            roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::zero());
332            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));
333            roz_case(
334                Rect::from_percent(5.0, 5.0, 5.0, 5.0),
335                Size::new(-5.0, -5.0),
336                Rect::new(-25.0, -25.0, -25.0, -25.0),
337            );
338            roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::zero());
339        }
340    }
341
342    mod resolve_or_zero_rect_dimension_to_rect_f32_via_option {
343        use super::roz_case;
344        use crate::geometry::Rect;
345        use crate::style::Dimension;
346
347        #[test]
348        fn resolve_or_zero_auto() {
349            roz_case(Rect::<Dimension>::auto(), None, Rect::zero());
350            roz_case(Rect::<Dimension>::auto(), Some(5.0), Rect::zero());
351            roz_case(Rect::<Dimension>::auto(), Some(-5.0), Rect::zero());
352            roz_case(Rect::<Dimension>::auto(), Some(0.0), Rect::zero());
353        }
354
355        #[test]
356        fn resolve_or_zero_length() {
357            roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), None, Rect::new(5.0, 5.0, 5.0, 5.0));
358            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));
359            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));
360            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));
361        }
362
363        #[test]
364        fn resolve_or_zero_percent() {
365            roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), None, Rect::zero());
366            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));
367            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));
368            roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::zero());
369        }
370    }
371}