• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Helper trait to calculate dimensions during layout resolution
2 
3 use crate::geometry::{Rect, Size};
4 use crate::style::{Dimension, LengthPercentage, LengthPercentageAuto};
5 use 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.
12 pub trait MaybeResolve<In, Out> {
13     /// Resolve a dimension that might be dependent on a context, with `None` as fallback value
maybe_resolve(self, context: In) -> Out14     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.
22 pub trait ResolveOrZero<TContext, TOutput: TaffyZero> {
23     /// Resolve a dimension that might be dependent on a context, with a default fallback value
resolve_or_zero(self, context: TContext) -> TOutput24     fn resolve_or_zero(self, context: TContext) -> TOutput;
25 }
26 
27 impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentage {
28     /// Converts the given [`LengthPercentage`] into an absolute length
29     /// Can return `None`
maybe_resolve(self, context: Option<f32>) -> Option<f32>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 
38 impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentageAuto {
39     /// Converts the given [`LengthPercentageAuto`] into an absolute length
40     /// Can return `None`
maybe_resolve(self, context: Option<f32>) -> Option<f32>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 
50 impl MaybeResolve<Option<f32>, Option<f32>> for Dimension {
51     /// Converts the given [`Dimension`] into an absolute length
52     ///
53     /// Can return `None`
maybe_resolve(self, context: Option<f32>) -> Option<f32>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
65 impl<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`
maybe_resolve(self, context: f32) -> Option<f32>68     fn maybe_resolve(self, context: f32) -> Option<f32> {
69         self.maybe_resolve(Some(context))
70     }
71 }
72 
73 // Generic MaybeResolve for Size
74 impl<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
maybe_resolve(self, context: Size<In>) -> Size<Out>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 
81 impl ResolveOrZero<Option<f32>, f32> for LengthPercentage {
82     /// Will return a default value of result is evaluated to `None`
resolve_or_zero(self, context: Option<f32>) -> f3283     fn resolve_or_zero(self, context: Option<f32>) -> f32 {
84         self.maybe_resolve(context).unwrap_or(0.0)
85     }
86 }
87 
88 impl ResolveOrZero<Option<f32>, f32> for LengthPercentageAuto {
89     /// Will return a default value of result is evaluated to `None`
resolve_or_zero(self, context: Option<f32>) -> f3290     fn resolve_or_zero(self, context: Option<f32>) -> f32 {
91         self.maybe_resolve(context).unwrap_or(0.0)
92     }
93 }
94 
95 impl ResolveOrZero<Option<f32>, f32> for Dimension {
96     /// Will return a default value of result is evaluated to `None`
resolve_or_zero(self, context: Option<f32>) -> f3297     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
103 impl<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
resolve_or_zero(self, context: Size<In>) -> Size<Out>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
111 impl<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
resolve_or_zero(self, context: Size<In>) -> Rect<Out>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
124 impl<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
resolve_or_zero(self, context: Option<f32>) -> Rect<Out>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)]
137 mod tests {
138     use super::{MaybeResolve, ResolveOrZero};
139     use crate::style_helpers::TaffyZero;
140     use core::fmt::Debug;
141 
142     // MaybeResolve test runner
mr_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out) where Lhs: MaybeResolve<Rhs, Out>, Out: PartialEq + Debug,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
roz_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out) where Lhs: ResolveOrZero<Rhs, Out>, Out: PartialEq + Debug + TaffyZero,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]
resolve_auto()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]
resolve_length()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]
resolve_percent()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]
maybe_resolve_auto()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]
maybe_resolve_length()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]
maybe_resolve_percent()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]
resolve_or_zero_auto()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]
resolve_or_zero_length()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]
resolve_or_zero_percent()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]
resolve_or_zero_auto()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]
resolve_or_zero_length()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]
resolve_or_zero_percent()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]
resolve_or_zero_auto()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]
resolve_or_zero_length()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]
resolve_or_zero_percent()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 }
334