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}