1 //! Helper trait to calculate dimensions during layout resolution 2 3 use crate::geometry::{Rect, Size}; 4 use crate::style::{Dimension, LengthPercentage, LengthPercentageAuto}; 5 use 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. 12 pub trait MaybeResolve<In, Out> { 13 /// Resolve a dimension that might be dependent on a context, with `None` as fallback value maybe_resolve(self, context: In) -> Out14 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. 22 pub trait ResolveOrZero<TContext, TOutput: TaffyZero> { 23 /// Resolve a dimension that might be dependent on a context, with a default fallback value resolve_or_zero(self, context: TContext) -> TOutput24 fn resolve_or_zero(self, context: TContext) -> TOutput; 25 } 26 27 impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentage { 28 /// Converts the given [`LengthPercentage`] into an absolute length 29 /// Can return `None` maybe_resolve(self, context: Option<f32>) -> Option<f32>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 38 impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentageAuto { 39 /// Converts the given [`LengthPercentageAuto`] into an absolute length 40 /// Can return `None` maybe_resolve(self, context: Option<f32>) -> Option<f32>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 } 47 } 48 } 49 50 impl MaybeResolve<Option<f32>, Option<f32>> for Dimension { 51 /// Converts the given [`Dimension`] into an absolute length 52 /// 53 /// Can return `None` maybe_resolve(self, context: Option<f32>) -> Option<f32>54 fn maybe_resolve(self, context: Option<f32>) -> Option<f32> { 55 match self { 56 Dimension::Length(length) => Some(length), 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 65 impl<T: MaybeResolve<Option<f32>, Option<f32>>> MaybeResolve<f32, Option<f32>> for T { 66 /// Converts the given MaybeResolve value into an absolute length 67 /// Can return `None` maybe_resolve(self, context: f32) -> Option<f32>68 fn maybe_resolve(self, context: f32) -> Option<f32> { 69 self.maybe_resolve(Some(context)) 70 } 71 } 72 73 // Generic MaybeResolve for Size 74 impl<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 maybe_resolve(self, context: Size<In>) -> Size<Out>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 81 impl ResolveOrZero<Option<f32>, f32> for LengthPercentage { 82 /// Will return a default value of result is evaluated to `None` resolve_or_zero(self, context: Option<f32>) -> f3283 fn resolve_or_zero(self, context: Option<f32>) -> f32 { 84 self.maybe_resolve(context).unwrap_or(0.0) 85 } 86 } 87 88 impl ResolveOrZero<Option<f32>, f32> for LengthPercentageAuto { 89 /// Will return a default value of result is evaluated to `None` resolve_or_zero(self, context: Option<f32>) -> f3290 fn resolve_or_zero(self, context: Option<f32>) -> f32 { 91 self.maybe_resolve(context).unwrap_or(0.0) 92 } 93 } 94 95 impl ResolveOrZero<Option<f32>, f32> for Dimension { 96 /// Will return a default value of result is evaluated to `None` resolve_or_zero(self, context: Option<f32>) -> f3297 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 103 impl<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 resolve_or_zero(self, context: Size<In>) -> Size<Out>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 111 impl<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 resolve_or_zero(self, context: Size<In>) -> Rect<Out>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 124 impl<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 resolve_or_zero(self, context: Option<f32>) -> Rect<Out>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)] 137 mod tests { 138 use super::{MaybeResolve, ResolveOrZero}; 139 use crate::style_helpers::TaffyZero; 140 use core::fmt::Debug; 141 142 // MaybeResolve test runner mr_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out) where Lhs: MaybeResolve<Rhs, Out>, Out: PartialEq + Debug,143 fn mr_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out) 144 where 145 Lhs: MaybeResolve<Rhs, Out>, 146 Out: PartialEq + Debug, 147 { 148 assert_eq!(input.maybe_resolve(context), expected); 149 } 150 151 // ResolveOrZero test runner roz_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out) where Lhs: ResolveOrZero<Rhs, Out>, Out: PartialEq + Debug + TaffyZero,152 fn roz_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out) 153 where 154 Lhs: ResolveOrZero<Rhs, Out>, 155 Out: PartialEq + Debug + TaffyZero, 156 { 157 assert_eq!(input.resolve_or_zero(context), expected); 158 } 159 160 mod maybe_resolve_dimension { 161 use super::mr_case; 162 use crate::style::Dimension; 163 164 /// `Dimension::Auto` should always return `None` 165 /// 166 /// The parent / context should not affect the outcome. 167 #[test] resolve_auto()168 fn resolve_auto() { 169 mr_case(Dimension::Auto, None, None); 170 mr_case(Dimension::Auto, Some(5.0), None); 171 mr_case(Dimension::Auto, Some(-5.0), None); 172 mr_case(Dimension::Auto, Some(0.), None); 173 } 174 175 /// `Dimension::Length` should always return `Some(f32)` 176 /// where the f32 value is the inner absolute length. 177 /// 178 /// The parent / context should not affect the outcome. 179 #[test] resolve_length()180 fn resolve_length() { 181 mr_case(Dimension::Length(1.0), None, Some(1.0)); 182 mr_case(Dimension::Length(1.0), Some(5.0), Some(1.0)); 183 mr_case(Dimension::Length(1.0), Some(-5.0), Some(1.0)); 184 mr_case(Dimension::Length(1.0), Some(0.), Some(1.0)); 185 } 186 187 /// `Dimension::Percent` should return `None` if context is `None`. 188 /// Otherwise it should return `Some(f32)` 189 /// where the f32 value is the inner value of the percent * context value. 190 /// 191 /// The parent / context __should__ affect the outcome. 192 #[test] resolve_percent()193 fn resolve_percent() { 194 mr_case(Dimension::Percent(1.0), None, None); 195 mr_case(Dimension::Percent(1.0), Some(5.0), Some(5.0)); 196 mr_case(Dimension::Percent(1.0), Some(-5.0), Some(-5.0)); 197 mr_case(Dimension::Percent(1.0), Some(50.0), Some(50.0)); 198 } 199 } 200 201 mod maybe_resolve_size_dimension { 202 use super::mr_case; 203 use crate::geometry::Size; 204 use crate::style::Dimension; 205 206 /// Size<Dimension::Auto> should always return Size<None> 207 /// 208 /// The parent / context should not affect the outcome. 209 #[test] maybe_resolve_auto()210 fn maybe_resolve_auto() { 211 mr_case(Size::<Dimension>::auto(), Size::NONE, Size::NONE); 212 mr_case(Size::<Dimension>::auto(), Size::new(5.0, 5.0), Size::NONE); 213 mr_case(Size::<Dimension>::auto(), Size::new(-5.0, -5.0), Size::NONE); 214 mr_case(Size::<Dimension>::auto(), Size::new(0.0, 0.0), Size::NONE); 215 } 216 217 /// Size<Dimension::Length> should always return a Size<Some(f32)> 218 /// where the f32 values are the absolute length. 219 /// 220 /// The parent / context should not affect the outcome. 221 #[test] maybe_resolve_length()222 fn maybe_resolve_length() { 223 mr_case(Size::from_lengths(5.0, 5.0), Size::NONE, Size::new(5.0, 5.0)); 224 mr_case(Size::from_lengths(5.0, 5.0), Size::new(5.0, 5.0), Size::new(5.0, 5.0)); 225 mr_case(Size::from_lengths(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(5.0, 5.0)); 226 mr_case(Size::from_lengths(5.0, 5.0), Size::new(0.0, 0.0), Size::new(5.0, 5.0)); 227 } 228 229 /// `Size<Dimension::Percent>` should return `Size<None>` if context is `Size<None>`. 230 /// Otherwise it should return `Size<Some(f32)>` 231 /// where the f32 value is the inner value of the percent * context value. 232 /// 233 /// The context __should__ affect the outcome. 234 #[test] maybe_resolve_percent()235 fn maybe_resolve_percent() { 236 mr_case(Size::from_percent(5.0, 5.0), Size::NONE, Size::NONE); 237 mr_case(Size::from_percent(5.0, 5.0), Size::new(5.0, 5.0), Size::new(25.0, 25.0)); 238 mr_case(Size::from_percent(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(-25.0, -25.0)); 239 mr_case(Size::from_percent(5.0, 5.0), Size::new(0.0, 0.0), Size::new(0.0, 0.0)); 240 } 241 } 242 243 mod resolve_or_zero_dimension_to_option_f32 { 244 use super::roz_case; 245 use crate::style::Dimension; 246 247 #[test] resolve_or_zero_auto()248 fn resolve_or_zero_auto() { 249 roz_case(Dimension::Auto, None, 0.0); 250 roz_case(Dimension::Auto, Some(5.0), 0.0); 251 roz_case(Dimension::Auto, Some(-5.0), 0.0); 252 roz_case(Dimension::Auto, Some(0.0), 0.0); 253 } 254 #[test] resolve_or_zero_length()255 fn resolve_or_zero_length() { 256 roz_case(Dimension::Length(5.0), None, 5.0); 257 roz_case(Dimension::Length(5.0), Some(5.0), 5.0); 258 roz_case(Dimension::Length(5.0), Some(-5.0), 5.0); 259 roz_case(Dimension::Length(5.0), Some(0.0), 5.0); 260 } 261 #[test] resolve_or_zero_percent()262 fn resolve_or_zero_percent() { 263 roz_case(Dimension::Percent(5.0), None, 0.0); 264 roz_case(Dimension::Percent(5.0), Some(5.0), 25.0); 265 roz_case(Dimension::Percent(5.0), Some(-5.0), -25.0); 266 roz_case(Dimension::Percent(5.0), Some(0.0), 0.0); 267 } 268 } 269 270 mod resolve_or_zero_rect_dimension_to_rect { 271 use super::roz_case; 272 use crate::geometry::{Rect, Size}; 273 use crate::style::Dimension; 274 275 #[test] resolve_or_zero_auto()276 fn resolve_or_zero_auto() { 277 roz_case(Rect::<Dimension>::auto(), Size::NONE, Rect::zero()); 278 roz_case(Rect::<Dimension>::auto(), Size::new(5.0, 5.0), Rect::zero()); 279 roz_case(Rect::<Dimension>::auto(), Size::new(-5.0, -5.0), Rect::zero()); 280 roz_case(Rect::<Dimension>::auto(), Size::new(0.0, 0.0), Rect::zero()); 281 } 282 283 #[test] resolve_or_zero_length()284 fn resolve_or_zero_length() { 285 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)); 286 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)); 287 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)); 288 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)); 289 } 290 291 #[test] resolve_or_zero_percent()292 fn resolve_or_zero_percent() { 293 roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::zero()); 294 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)); 295 roz_case( 296 Rect::from_percent(5.0, 5.0, 5.0, 5.0), 297 Size::new(-5.0, -5.0), 298 Rect::new(-25.0, -25.0, -25.0, -25.0), 299 ); 300 roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::zero()); 301 } 302 } 303 304 mod resolve_or_zero_rect_dimension_to_rect_f32_via_option { 305 use super::roz_case; 306 use crate::geometry::Rect; 307 use crate::style::Dimension; 308 309 #[test] resolve_or_zero_auto()310 fn resolve_or_zero_auto() { 311 roz_case(Rect::<Dimension>::auto(), None, Rect::zero()); 312 roz_case(Rect::<Dimension>::auto(), Some(5.0), Rect::zero()); 313 roz_case(Rect::<Dimension>::auto(), Some(-5.0), Rect::zero()); 314 roz_case(Rect::<Dimension>::auto(), Some(0.0), Rect::zero()); 315 } 316 317 #[test] resolve_or_zero_length()318 fn resolve_or_zero_length() { 319 roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), None, Rect::new(5.0, 5.0, 5.0, 5.0)); 320 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)); 321 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)); 322 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)); 323 } 324 325 #[test] resolve_or_zero_percent()326 fn resolve_or_zero_percent() { 327 roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), None, Rect::zero()); 328 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)); 329 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)); 330 roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::zero()); 331 } 332 } 333 } 334