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}