tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 788HTML 740Markdown 12TOML 62536 Definiciones

⎇ morphorm src/resolve.rs Rust · 363 líneas · 15.5 KB Historia Quién tocó qué crudo

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