tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 520HTML 244Markdown 7TOML 41339 Definiciones

⎇ 0.3.x src/resolve.rs Rust · 361 líneas · 15.4 KB Historia Quién tocó qué crudo

Strict types for style values (#269) 0d8c198a9 · Nico Burns ·

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