Refactor/module hierarchy (#443)
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 a concrete value of points
29 /// Can return `None`
30 fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
31 match self {
32 LengthPercentage::Points(points) => Some(points),
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 a concrete value of points
40 /// Can return `None`
41 fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
42 match self {
43 LengthPercentageAuto::Points(points) => Some(points),
44 LengthPercentageAuto::Percent(percent) => context.map(|dim| dim * percent),
45 LengthPercentageAuto::Auto => 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 }
60 }
61}
62
63// Generic implementation of MaybeResolve for f32 context where MaybeResolve is implemented
64// for Option<f32> context
65impl<T: MaybeResolve<Option<f32>, Option<f32>>> MaybeResolve<f32, Option<f32>> for T {
66 /// Converts the given MaybeResolve value into a concrete value of points
67 /// Can return `None`
68 fn maybe_resolve(self, context: f32) -> Option<f32> {
69 self.maybe_resolve(Some(context))
70 }
71}
72
73// Generic MaybeResolve for Size
74impl<In, Out, T: MaybeResolve<In, Out>> MaybeResolve<Size<In>, Size<Out>> for Size<T> {
75 /// Converts any `parent`-relative values for size into an absolute size
76 fn maybe_resolve(self, context: Size<In>) -> Size<Out> {
77 Size { width: self.width.maybe_resolve(context.width), height: self.height.maybe_resolve(context.height) }
78 }
79}
80
81impl ResolveOrZero<Option<f32>, f32> for LengthPercentage {
82 /// Will return a default value of result is evaluated to `None`
83 fn resolve_or_zero(self, context: Option<f32>) -> f32 {
84 self.maybe_resolve(context).unwrap_or(0.0)
85 }
86}
87
88impl ResolveOrZero<Option<f32>, f32> for LengthPercentageAuto {
89 /// Will return a default value of result is evaluated to `None`
90 fn resolve_or_zero(self, context: Option<f32>) -> f32 {
91 self.maybe_resolve(context).unwrap_or(0.0)
92 }
93}
94
95impl ResolveOrZero<Option<f32>, f32> for Dimension {
96 /// Will return a default value of result is evaluated to `None`
97 fn resolve_or_zero(self, context: Option<f32>) -> f32 {
98 self.maybe_resolve(context).unwrap_or(0.0)
99 }
100}
101
102// Generic ResolveOrZero for Size
103impl<In, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Size<Out>> for Size<T> {
104 /// Converts any `parent`-relative values for size into an absolute size
105 fn resolve_or_zero(self, context: Size<In>) -> Size<Out> {
106 Size { width: self.width.resolve_or_zero(context.width), height: self.height.resolve_or_zero(context.height) }
107 }
108}
109
110// Generic ResolveOrZero for resolving Rect against Size
111impl<In: Copy, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Rect<Out>> for Rect<T> {
112 /// Converts any `parent`-relative values for Rect into an absolute Rect
113 fn resolve_or_zero(self, context: Size<In>) -> Rect<Out> {
114 Rect {
115 left: self.left.resolve_or_zero(context.width),
116 right: self.right.resolve_or_zero(context.width),
117 top: self.top.resolve_or_zero(context.height),
118 bottom: self.bottom.resolve_or_zero(context.height),
119 }
120 }
121}
122
123// Generic ResolveOrZero for resolving Rect against Option
124impl<Out: TaffyZero, T: ResolveOrZero<Option<f32>, Out>> ResolveOrZero<Option<f32>, Rect<Out>> for Rect<T> {
125 /// Converts any `parent`-relative values for Rect into an absolute Rect
126 fn resolve_or_zero(self, context: Option<f32>) -> Rect<Out> {
127 Rect {
128 left: self.left.resolve_or_zero(context),
129 right: self.right.resolve_or_zero(context),
130 top: self.top.resolve_or_zero(context),
131 bottom: self.bottom.resolve_or_zero(context),
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 mod maybe_resolve_dimension {
139
140 use crate::style::Dimension;
141 use crate::util::MaybeResolve;
142 use rstest::rstest;
143
144 /// `Dimension::Auto` should always return `None`
145 ///
146 /// The parent / context should not affect the outcome.
147 #[rstest]
148 #[case(Dimension::Auto, None, None)]
149 #[case(Dimension::Auto, Some(5.0), None)]
150 #[case(Dimension::Auto, Some(-5.0), None)]
151 #[case(Dimension::Auto, Some(0.), None)]
152 fn resolve_auto(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
153 assert_eq!(input.maybe_resolve(context), expected);
154 }
155
156 /// `Dimension::Points` should always return `Some(f32)`
157 /// where the f32 value is the inner value of the points.
158 ///
159 /// The parent / context should not affect the outcome.
160 #[rstest]
161 #[case(Dimension::Points(1.0), None, Some(1.0))]
162 #[case(Dimension::Points(1.0), Some(5.0), Some(1.0))]
163 #[case(Dimension::Points(1.0), Some(-5.0), Some(1.0))]
164 #[case(Dimension::Points(1.0), Some(0.), Some(1.0))]
165 fn resolve_points(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
166 assert_eq!(input.maybe_resolve(context), expected);
167 }
168
169 /// `Dimension::Percent` should return `None` if context is `None`.
170 /// Otherwise it should return `Some(f32)`
171 /// where the f32 value is the inner value of the percent * context value.
172 ///
173 /// The parent / context __should__ affect the outcome.
174 #[rstest]
175 #[case(Dimension::Percent(1.0), None, None)]
176 #[case(Dimension::Percent(1.0), Some(5.0), Some(5.0))]
177 #[case(Dimension::Percent(1.0), Some(-5.0), Some(-5.0))]
178 #[case(Dimension::Percent(1.0), Some(50.0), Some(50.0))]
179 fn resolve_percent(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
180 assert_eq!(input.maybe_resolve(context), expected);
181 }
182 }
183
184 mod maybe_resolve_size_dimension {
185 use super::super::MaybeResolve;
186 use crate::geometry::Size;
187 use crate::style::Dimension;
188 use rstest::rstest;
189
190 /// Size<Dimension::Auto> should always return Size<None>
191 ///
192 /// The parent / context should not affect the outcome.
193 #[rstest]
194 #[case(Size::auto(), Size::NONE, Size::NONE)]
195 #[case(Size::auto(), Size::new(5.0, 5.0), Size::NONE)]
196 #[case(Size::auto(), Size::new(-5.0, -5.0), Size::NONE)]
197 #[case(Size::auto(), Size::new(0.0, 0.0), Size::NONE)]
198 fn maybe_resolve_auto(
199 #[case] input: Size<Dimension>,
200 #[case] context: Size<Option<f32>>,
201 #[case] expected: Size<Option<f32>>,
202 ) {
203 assert_eq!(input.maybe_resolve(context), expected);
204 }
205
206 /// Size<Dimension::Points> should always return a Size<Some(f32)>
207 /// where the f32 values are the inner value of the points.
208 ///
209 /// The parent / context should not affect the outcome.
210 #[rstest]
211 #[case(Size::from_points(5.0, 5.0), Size::NONE, 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(-5.0, -5.0), Size::new(5.0, 5.0))]
214 #[case(Size::from_points(5.0, 5.0), Size::new(0.0, 0.0), Size::new(5.0, 5.0))]
215 fn maybe_resolve_points(
216 #[case] input: Size<Dimension>,
217 #[case] context: Size<Option<f32>>,
218 #[case] expected: Size<Option<f32>>,
219 ) {
220 assert_eq!(input.maybe_resolve(context), expected);
221 }
222
223 /// `Size<Dimension::Percent>` should return `Size<None>` if context is `Size<None>`.
224 /// Otherwise it should return `Size<Some(f32)>`
225 /// where the f32 value is the inner value of the percent * context value.
226 ///
227 /// The context __should__ affect the outcome.
228 #[rstest]
229 #[case(Size::from_percent(5.0, 5.0), Size::NONE, Size::NONE)]
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(-5.0, -5.0), Size::new(-25.0, -25.0))]
232 #[case(Size::from_percent(5.0, 5.0), Size::new(0.0, 0.0), Size::new(0.0, 0.0))]
233 fn maybe_resolve_percent(
234 #[case] input: Size<Dimension>,
235 #[case] context: Size<Option<f32>>,
236 #[case] expected: Size<Option<f32>>,
237 ) {
238 assert_eq!(input.maybe_resolve(context), expected);
239 }
240 }
241
242 mod resolve_or_zero_dimension_to_option_f32 {
243 use crate::style::Dimension;
244 use crate::util::ResolveOrZero;
245 use rstest::rstest;
246
247 #[rstest]
248 #[case(Dimension::Auto, None, 0.0)]
249 #[case(Dimension::Auto, Some(5.0), 0.0)]
250 #[case(Dimension::Auto, Some(-5.0), 0.0)]
251 #[case(Dimension::Auto, Some(0.0), 0.0)]
252 fn resolve_or_zero_auto(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
253 assert_eq!(input.resolve_or_zero(context), expected);
254 }
255 #[rstest]
256 #[case(Dimension::Points(5.0), None, 5.0)]
257 #[case(Dimension::Points(5.0), Some(5.0), 5.0)]
258 #[case(Dimension::Points(5.0), Some(-5.0), 5.0)]
259 #[case(Dimension::Points(5.0), Some(0.0), 5.0)]
260 fn resolve_or_zero_points(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
261 assert_eq!(input.resolve_or_zero(context), expected);
262 }
263 #[rstest]
264 #[case(Dimension::Percent(5.0), None, 0.0)]
265 #[case(Dimension::Percent(5.0), Some(5.0), 25.0)]
266 #[case(Dimension::Percent(5.0), Some(-5.0), -25.0)]
267 #[case(Dimension::Percent(5.0), Some(0.0), 0.0)]
268 fn resolve_or_zero_percent(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
269 assert_eq!(input.resolve_or_zero(context), expected);
270 }
271 }
272
273 mod resolve_or_zero_rect_dimension_to_rect {
274 use crate::geometry::{Rect, Size};
275 use crate::style::Dimension;
276 use crate::util::ResolveOrZero;
277 use rstest::rstest;
278
279 #[rstest]
280 #[case(Rect::auto(), Size::NONE, Rect::zero())]
281 #[case(Rect::auto(), Size::new(5.0, 5.0), Rect::zero())]
282 #[case(Rect::auto(), Size::new(-5.0, -5.0), Rect::zero())]
283 #[case(Rect::auto(), Size::new(0.0, 0.0), Rect::zero())]
284 fn resolve_or_zero_auto(
285 #[case] input: Rect<Dimension>,
286 #[case] context: Size<Option<f32>>,
287 #[case] expected: Rect<f32>,
288 ) {
289 assert_eq!(input.resolve_or_zero(context), expected);
290 }
291
292 #[rstest]
293 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::NONE, 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(-5.0, -5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
296 #[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))]
297 fn resolve_or_zero_points(
298 #[case] input: Rect<Dimension>,
299 #[case] context: Size<Option<f32>>,
300 #[case] expected: Rect<f32>,
301 ) {
302 assert_eq!(input.resolve_or_zero(context), expected);
303 }
304
305 #[rstest]
306 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::zero())]
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(-5.0, -5.0), Rect::new(-25.0, -25.0, -25.0, -25.0))]
309 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::zero())]
310 fn resolve_or_zero_percent(
311 #[case] input: Rect<Dimension>,
312 #[case] context: Size<Option<f32>>,
313 #[case] expected: Rect<f32>,
314 ) {
315 assert_eq!(input.resolve_or_zero(context), expected);
316 }
317 }
318
319 mod resolve_or_zero_rect_dimension_to_rect_f32_via_option {
320 use crate::geometry::Rect;
321 use crate::style::Dimension;
322 use crate::util::ResolveOrZero;
323 use rstest::rstest;
324
325 #[rstest]
326 #[case(Rect::auto(), None, Rect::zero())]
327 #[case(Rect::auto(), Some(5.0), Rect::zero())]
328 #[case(Rect::auto(), Some(-5.0), Rect::zero())]
329 #[case(Rect::auto(), Some(0.0), Rect::zero())]
330 fn resolve_or_zero_auto(
331 #[case] input: Rect<Dimension>,
332 #[case] context: Option<f32>,
333 #[case] expected: Rect<f32>,
334 ) {
335 assert_eq!(input.resolve_or_zero(context), expected);
336 }
337
338 #[rstest]
339 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), None, 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(-5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
342 #[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))]
343 fn resolve_or_zero_points(
344 #[case] input: Rect<Dimension>,
345 #[case] context: Option<f32>,
346 #[case] expected: Rect<f32>,
347 ) {
348 assert_eq!(input.resolve_or_zero(context), expected);
349 }
350
351 #[rstest]
352 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), None, Rect::zero())]
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(-5.0), Rect::new(-25.0, -25.0, -25.0, -25.0))]
355 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::zero())]
356 fn resolve_or_zero_percent(
357 #[case] input: Rect<Dimension>,
358 #[case] context: Option<f32>,
359 #[case] expected: Rect<f32>,
360 ) {
361 assert_eq!(input.resolve_or_zero(context), expected);
362 }
363 }
364}