//! Helper trait to calculate dimensions during layout resolution use crate::geometry::{Rect, Size}; use crate::style::{Dimension, LengthPercentage, LengthPercentageAuto}; use crate::style_helpers::TaffyZero; use crate::CompactLength; /// Trait to encapsulate behaviour where we need to resolve from a /// potentially context-dependent size or dimension into /// a context-independent size or dimension. /// /// Will return a `None` if it unable to resolve. pub trait MaybeResolve { /// Resolve a dimension that might be dependent on a context, with `None` as fallback value fn maybe_resolve(self, context: In, calc: impl Fn(*const (), f32) -> f32) -> Out; } /// Trait to encapsulate behaviour where we need to resolve from a /// potentially context-dependent size or dimension into /// a context-independent size or dimension. /// /// Will return a default value if it unable to resolve. pub trait ResolveOrZero { /// Resolve a dimension that might be dependent on a context, with a default fallback value fn resolve_or_zero(self, context: TContext, calc: impl Fn(*const (), f32) -> f32) -> TOutput; } impl MaybeResolve, Option> for LengthPercentage { /// Converts the given [`LengthPercentage`] into an absolute length /// Can return `None` fn maybe_resolve(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> Option { match self.0.tag() { CompactLength::LENGTH_TAG => Some(self.0.value()), CompactLength::PERCENT_TAG => context.map(|dim| dim * self.0.value()), #[cfg(feature = "calc")] _ if self.0.is_calc() => context.map(|dim| calc(self.0.calc_value(), dim)), _ => unreachable!(), } } } impl MaybeResolve, Option> for LengthPercentageAuto { /// Converts the given [`LengthPercentageAuto`] into an absolute length /// Can return `None` fn maybe_resolve(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> Option { match self.0.tag() { CompactLength::AUTO_TAG => None, CompactLength::LENGTH_TAG => Some(self.0.value()), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG => { context.map(|dim| dim * self.0.value()) } CompactLength::MIN_CONTENT_TAG | CompactLength::MAX_CONTENT_TAG | CompactLength::FIT_CONTENT_PX_TAG | CompactLength::FIT_CONTENT_KEYWORD_TAG => None, #[cfg(feature = "calc")] _ if self.0.is_calc() => context.map(|dim| calc(self.0.calc_value(), dim)), _ => None, } } } impl MaybeResolve, Option> for Dimension { /// Converts the given [`Dimension`] into an absolute length /// /// Can return `None` fn maybe_resolve(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> Option { match self.0.tag() { CompactLength::AUTO_TAG => None, CompactLength::LENGTH_TAG => Some(self.0.value()), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG => { context.map(|dim| dim * self.0.value()) } CompactLength::MIN_CONTENT_TAG | CompactLength::MAX_CONTENT_TAG | CompactLength::FIT_CONTENT_PX_TAG | CompactLength::FIT_CONTENT_KEYWORD_TAG => None, #[cfg(feature = "calc")] _ if self.0.is_calc() => context.map(|dim| calc(self.0.calc_value(), dim)), _ => None, } } } // Generic implementation of MaybeResolve for f32 context where MaybeResolve is implemented // for Option context impl, Option>> MaybeResolve> for T { /// Converts the given MaybeResolve value into an absolute length /// Can return `None` fn maybe_resolve(self, context: f32, calc: impl Fn(*const (), f32) -> f32) -> Option { self.maybe_resolve(Some(context), calc) } } // Generic MaybeResolve for Size impl> MaybeResolve, Size> for Size { /// Converts any `parent`-relative values for size into an absolute size fn maybe_resolve(self, context: Size, calc: impl Fn(*const (), f32) -> f32) -> Size { Size { width: self.width.maybe_resolve(context.width, &calc), height: self.height.maybe_resolve(context.height, &calc), } } } impl ResolveOrZero, f32> for LengthPercentage { /// Will return a default value of result is evaluated to `None` fn resolve_or_zero(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> f32 { self.maybe_resolve(context, calc).unwrap_or(0.0) } } impl ResolveOrZero, f32> for LengthPercentageAuto { /// Will return a default value of result is evaluated to `None` fn resolve_or_zero(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> f32 { self.maybe_resolve(context, calc).unwrap_or(0.0) } } impl ResolveOrZero, f32> for Dimension { /// Will return a default value of result is evaluated to `None` fn resolve_or_zero(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> f32 { self.maybe_resolve(context, calc).unwrap_or(0.0) } } // Generic ResolveOrZero for Size impl> ResolveOrZero, Size> for Size { /// Converts any `parent`-relative values for size into an absolute size fn resolve_or_zero(self, context: Size, calc: impl Fn(*const (), f32) -> f32) -> Size { Size { width: self.width.resolve_or_zero(context.width, &calc), height: self.height.resolve_or_zero(context.height, &calc), } } } // Generic ResolveOrZero for resolving Rect against Size impl> ResolveOrZero, Rect> for Rect { /// Converts any `parent`-relative values for Rect into an absolute Rect fn resolve_or_zero(self, context: Size, calc: impl Fn(*const (), f32) -> f32) -> Rect { Rect { left: self.left.resolve_or_zero(context.width, &calc), right: self.right.resolve_or_zero(context.width, &calc), top: self.top.resolve_or_zero(context.height, &calc), bottom: self.bottom.resolve_or_zero(context.height, &calc), } } } // Generic ResolveOrZero for resolving Rect against Option impl, Out>> ResolveOrZero, Rect> for Rect { /// Converts any `parent`-relative values for Rect into an absolute Rect fn resolve_or_zero(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> Rect { Rect { left: self.left.resolve_or_zero(context, &calc), right: self.right.resolve_or_zero(context, &calc), top: self.top.resolve_or_zero(context, &calc), bottom: self.bottom.resolve_or_zero(context, &calc), } } } #[cfg(test)] mod tests { use super::{MaybeResolve, ResolveOrZero}; use crate::style_helpers::TaffyZero; use core::fmt::Debug; // MaybeResolve test runner fn mr_case(input: Lhs, context: Rhs, expected: Out) where Lhs: MaybeResolve, Out: PartialEq + Debug, { assert_eq!(input.maybe_resolve(context, |_, _| 42.42), expected); } // ResolveOrZero test runner fn roz_case(input: Lhs, context: Rhs, expected: Out) where Lhs: ResolveOrZero, Out: PartialEq + Debug + TaffyZero, { assert_eq!(input.resolve_or_zero(context, |_, _| 42.42), expected); } mod maybe_resolve_dimension { use super::mr_case; use crate::style::Dimension; use crate::style_helpers::*; /// `Dimension::Auto` should always return `None` /// /// The parent / context should not affect the outcome. #[test] fn resolve_auto() { mr_case(Dimension::AUTO, None, None); mr_case(Dimension::AUTO, Some(5.0), None); mr_case(Dimension::AUTO, Some(-5.0), None); mr_case(Dimension::AUTO, Some(0.), None); } /// `Dimension::Length` should always return `Some(f32)` /// where the f32 value is the inner absolute length. /// /// The parent / context should not affect the outcome. #[test] fn resolve_length() { mr_case(Dimension::from_length(1.0), None, Some(1.0)); mr_case(Dimension::from_length(1.0), Some(5.0), Some(1.0)); mr_case(Dimension::from_length(1.0), Some(-5.0), Some(1.0)); mr_case(Dimension::from_length(1.0), Some(0.), Some(1.0)); } /// `Dimension::Percent` should return `None` if context is `None`. /// Otherwise it should return `Some(f32)` /// where the f32 value is the inner value of the percent * context value. /// /// The parent / context __should__ affect the outcome. #[test] fn resolve_percent() { mr_case(Dimension::from_percent(1.0), None, None); mr_case(Dimension::from_percent(1.0), Some(5.0), Some(5.0)); mr_case(Dimension::from_percent(1.0), Some(-5.0), Some(-5.0)); mr_case(Dimension::from_percent(1.0), Some(50.0), Some(50.0)); } /// Intrinsic size keywords/functions should remain unresolved in the generic numeric resolver. #[test] fn intrinsic_sizes_do_not_resolve_to_numeric_values() { mr_case(Dimension::MIN_CONTENT, None, None); mr_case(Dimension::MIN_CONTENT, Some(50.0), None); mr_case(Dimension::MAX_CONTENT, None, None); mr_case(Dimension::MAX_CONTENT, Some(50.0), None); mr_case(Dimension::fit_content_keyword(), None, None); mr_case(Dimension::fit_content_keyword(), Some(50.0), None); mr_case(fit_content::(length(12.0)), None, None); mr_case(fit_content::(length(12.0)), Some(50.0), None); } /// `fit-content(percentage)` carries the percentage limit until the algorithm consumes it. #[test] fn fit_content_percent_resolves_its_limit() { mr_case(fit_content::(percent(0.5)), None, None); mr_case(fit_content::(percent(0.5)), Some(80.0), Some(40.0)); } } mod maybe_resolve_size_dimension { use super::mr_case; use crate::geometry::Size; use crate::style::Dimension; /// Size should always return Size /// /// The parent / context should not affect the outcome. #[test] fn maybe_resolve_auto() { mr_case(Size::::auto(), Size::NONE, Size::NONE); mr_case(Size::::auto(), Size::new(5.0, 5.0), Size::NONE); mr_case(Size::::auto(), Size::new(-5.0, -5.0), Size::NONE); mr_case(Size::::auto(), Size::new(0.0, 0.0), Size::NONE); } /// Size should always return a Size /// where the f32 values are the absolute length. /// /// The parent / context should not affect the outcome. #[test] fn maybe_resolve_length() { mr_case(Size::from_lengths(5.0, 5.0), Size::NONE, Size::new(5.0, 5.0)); mr_case(Size::from_lengths(5.0, 5.0), Size::new(5.0, 5.0), Size::new(5.0, 5.0)); mr_case(Size::from_lengths(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(5.0, 5.0)); mr_case(Size::from_lengths(5.0, 5.0), Size::new(0.0, 0.0), Size::new(5.0, 5.0)); } /// `Size` should return `Size` if context is `Size`. /// Otherwise it should return `Size` /// where the f32 value is the inner value of the percent * context value. /// /// The context __should__ affect the outcome. #[test] fn maybe_resolve_percent() { mr_case(Size::from_percent(5.0, 5.0), Size::NONE, Size::NONE); mr_case(Size::from_percent(5.0, 5.0), Size::new(5.0, 5.0), Size::new(25.0, 25.0)); mr_case(Size::from_percent(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(-25.0, -25.0)); mr_case(Size::from_percent(5.0, 5.0), Size::new(0.0, 0.0), Size::new(0.0, 0.0)); } } mod resolve_or_zero_dimension_to_option_f32 { use super::roz_case; use crate::style::Dimension; use crate::style_helpers::*; #[test] fn resolve_or_zero_auto() { roz_case(Dimension::AUTO, None, 0.0); roz_case(Dimension::AUTO, Some(5.0), 0.0); roz_case(Dimension::AUTO, Some(-5.0), 0.0); roz_case(Dimension::AUTO, Some(0.0), 0.0); } #[test] fn resolve_or_zero_length() { roz_case(Dimension::from_length(5.0), None, 5.0); roz_case(Dimension::from_length(5.0), Some(5.0), 5.0); roz_case(Dimension::from_length(5.0), Some(-5.0), 5.0); roz_case(Dimension::from_length(5.0), Some(0.0), 5.0); } #[test] fn resolve_or_zero_percent() { roz_case(Dimension::from_percent(5.0), None, 0.0); roz_case(Dimension::from_percent(5.0), Some(5.0), 25.0); roz_case(Dimension::from_percent(5.0), Some(-5.0), -25.0); roz_case(Dimension::from_percent(5.0), Some(0.0), 0.0); } } mod resolve_or_zero_rect_dimension_to_rect { use super::roz_case; use crate::geometry::{Rect, Size}; use crate::style::Dimension; #[test] fn resolve_or_zero_auto() { roz_case(Rect::::auto(), Size::NONE, Rect::zero()); roz_case(Rect::::auto(), Size::new(5.0, 5.0), Rect::zero()); roz_case(Rect::::auto(), Size::new(-5.0, -5.0), Rect::zero()); roz_case(Rect::::auto(), Size::new(0.0, 0.0), Rect::zero()); } #[test] fn resolve_or_zero_length() { 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)); 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)); 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)); 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)); } #[test] fn resolve_or_zero_percent() { roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::zero()); 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)); 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), ); roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::zero()); } } mod resolve_or_zero_rect_dimension_to_rect_f32_via_option { use super::roz_case; use crate::geometry::Rect; use crate::style::Dimension; #[test] fn resolve_or_zero_auto() { roz_case(Rect::::auto(), None, Rect::zero()); roz_case(Rect::::auto(), Some(5.0), Rect::zero()); roz_case(Rect::::auto(), Some(-5.0), Rect::zero()); roz_case(Rect::::auto(), Some(0.0), Rect::zero()); } #[test] fn resolve_or_zero_length() { roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), None, Rect::new(5.0, 5.0, 5.0, 5.0)); 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)); 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)); 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)); } #[test] fn resolve_or_zero_percent() { roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), None, Rect::zero()); 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)); 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)); roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::zero()); } } }