gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Sutichaykuna

⎇ 303768a02ae67d8e068c0ff853da0dc08e6f8618 src/util/resolve.rs Rust · 383 siq'ikuna · 16.5 KB Ñawpa willay Pi imata llamkarqan ch'uya

fit content 303768a02 · Jonathan Kelley ·

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