gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 496HTML 245JavaScript 8CSS 61215 Definitions

⎇ 936ce7c79678915766c6bd374401807909026bf9 src/util/resolve.rs Rust · 335 lines · 13.7 KB History Who touched what raw

Add morphorm styles 936ce7c79 · Nico Burns ·

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