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}