Unit test `compute_constants()` (#184)
* Begin testing algo-constants
* Separating out resolve into own trait + module
* Fix tests
* Add convenience methods to Size
* Move tests to resolve
* Add tests for maybe_resolve Size<Dimension>
* Move resolve tests to resolve.rs
* Rewording parent -> context
* Add last test
* Add tests for resolve_or_default for Dimension
* Adding rect constructors
* Using consts instead of constructors
* from_some() -> new()
* Add note to releases
* Turning constructors to consts
* Rect convenience constructors
* Add test-suite for rect resolve_or_default
* Apply suggestions from code review
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
* Fix clippy lints & add docs
* Add tests for Rect -> Option -> Rect
* Finish testing algo-consts
* Apply suggestions from alice
* Tweak RELEASES.md wording
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
7762c2490a21a7f3f4e911efd097bdee7b0ef108 · por Andreas Weibye · 2022-06-23T18:41:36Z (hace 4 años) · padre 4b89afd5e · ver el árbol en este commit
Delta semántico · 71
src/layout.rs
cambió por dentro def new
diff --git a/RELEASES.md b/RELEASES.md
index 106462d35..4f9560ed8 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -9,6 +9,8 @@
### 0.2.0 Changed
+- `Size<f32>.zero()` is now `Size::<f32>::ZERO`
+- `Point<f32>.zero()` is now `Point::<f32>::ZERO`
- renamed `taffy::node::Taffy.new_leaf()` -> `taffy::node::Taffy.new_leaf_with_measure()`
- removed the public `Number` type; a more idiomatic `Option<f32>` is used instead
- the associated public `MinMax` and `OrElse` traits have also been removed; these should never have been public
diff --git a/src/flexbox.rs b/src/flexbox.rs
index 072eeb4c7..bfb0d9ff2 100644
--- a/src/flexbox.rs
+++ b/src/flexbox.rs
@@ -8,6 +8,7 @@ use crate::geometry::{Point, Rect, Size};
use crate::layout::{Cache, Layout};
use crate::math::MaybeMath;
use crate::node::{MeasureFunc, NodeId};
+use crate::resolve::{MaybeResolve, ResolveOrDefault};
use crate::style::{AlignContent, AlignSelf, Dimension, Display, FlexWrap, JustifyContent, PositionType};
use crate::style::{FlexDirection, FlexboxLayout};
use crate::sys::{abs, round, ChildrenVec, Vec};
@@ -112,20 +113,20 @@ impl Forest {
|| style.max_size.height.is_defined();
let preliminary_size = if has_root_min_max {
- let first_pass = self.compute_preliminary(root, style.size.resolve(size), size, false, true);
+ let first_pass = self.compute_preliminary(root, style.size.maybe_resolve(size), size, false, true);
self.compute_preliminary(
root,
Size {
width: first_pass
.width
- .maybe_max(style.min_size.width.resolve(size.width))
- .maybe_min(style.max_size.width.resolve(size.width))
+ .maybe_max(style.min_size.width.maybe_resolve(size.width))
+ .maybe_min(style.max_size.width.maybe_resolve(size.width))
.into(),
height: first_pass
.height
- .maybe_max(style.min_size.height.resolve(size.height))
- .maybe_min(style.max_size.height.resolve(size.height))
+ .maybe_max(style.min_size.height.maybe_resolve(size.height))
+ .maybe_min(style.max_size.height.maybe_resolve(size.height))
.into(),
},
size,
@@ -133,10 +134,10 @@ impl Forest {
true,
)
} else {
- self.compute_preliminary(root, style.size.resolve(size), size, true, true)
+ self.compute_preliminary(root, style.size.maybe_resolve(size), size, true, true)
};
- self.nodes[root].layout = Layout { order: 0, size: preliminary_size, location: Point::zero() };
+ self.nodes[root].layout = Layout { order: 0, size: preliminary_size, location: Point::ZERO };
Self::round_layout(&mut self.nodes, &self.children, root, 0.0, 0.0);
}
@@ -207,19 +208,18 @@ impl Forest {
/// Compute constants that can be reused during the flexbox algorithm.
#[inline]
fn compute_constants(
- &self,
- node: NodeId,
+ node: &NodeData,
node_size: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
) -> AlgoConstants {
- let dir = self.nodes[node].style.flex_direction;
+ let dir = node.style.flex_direction;
let is_row = dir.is_row();
let is_column = dir.is_column();
- let is_wrap_reverse = self.nodes[node].style.flex_wrap == FlexWrap::WrapReverse;
+ let is_wrap_reverse = node.style.flex_wrap == FlexWrap::WrapReverse;
- let margin = self.nodes[node].style.margin.map(|n| n.resolve(parent_size.width).unwrap_or(0.0));
- let padding = self.nodes[node].style.padding.map(|n| n.resolve(parent_size.width).unwrap_or(0.0));
- let border = self.nodes[node].style.border.map(|n| n.resolve(parent_size.width).unwrap_or(0.0));
+ let margin = node.style.margin.resolve_or_default(parent_size.width);
+ let padding = node.style.padding.resolve_or_default(parent_size.width);
+ let border = node.style.border.resolve_or_default(parent_size.width);
let padding_border = Rect {
start: padding.start + border.start,
@@ -233,8 +233,8 @@ impl Forest {
height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
};
- let container_size = Size::zero();
- let inner_container_size = Size::zero();
+ let container_size = Size::ZERO;
+ let inner_container_size = Size::ZERO;
AlgoConstants {
dir,
@@ -264,24 +264,23 @@ impl Forest {
.filter(|(_, style)| style.display != Display::None)
.map(|(child, child_style)| FlexItem {
node: *child,
- size: child_style.size.resolve(constants.node_inner_size),
- min_size: child_style.min_size.resolve(constants.node_inner_size),
- max_size: child_style.max_size.resolve(constants.node_inner_size),
-
- position: child_style.position.zip_size(constants.node_inner_size, |p, s| p.resolve(s)),
- margin: child_style.margin.map(|m| m.resolve(constants.node_inner_size.width).unwrap_or(0.0)),
- padding: child_style.padding.map(|p| p.resolve(constants.node_inner_size.width).unwrap_or(0.0)),
- border: child_style.border.map(|b| b.resolve(constants.node_inner_size.width).unwrap_or(0.0)),
-
+ size: child_style.size.maybe_resolve(constants.node_inner_size),
+ min_size: child_style.min_size.maybe_resolve(constants.node_inner_size),
+ max_size: child_style.max_size.maybe_resolve(constants.node_inner_size),
+
+ position: child_style.position.zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
+ margin: child_style.margin.resolve_or_default(constants.node_inner_size.width),
+ padding: child_style.padding.resolve_or_default(constants.node_inner_size.width),
+ border: child_style.border.resolve_or_default(constants.node_inner_size.width),
flex_basis: 0.0,
inner_flex_basis: 0.0,
violation: 0.0,
frozen: false,
- hypothetical_inner_size: Size::zero(),
- hypothetical_outer_size: Size::zero(),
- target_size: Size::zero(),
- outer_target_size: Size::zero(),
+ hypothetical_inner_size: Size::ZERO,
+ hypothetical_outer_size: Size::ZERO,
+ target_size: Size::ZERO,
+ outer_target_size: Size::ZERO,
baseline: 0.0,
@@ -368,7 +367,7 @@ impl Forest {
// A. If the item has a definite used flex basis, that’s the flex base size.
- let flex_basis = child_style.flex_basis.resolve(constants.node_inner_size.main(constants.dir));
+ let flex_basis = child_style.flex_basis.maybe_resolve(constants.node_inner_size.main(constants.dir));
if flex_basis.is_some() {
child.flex_basis = flex_basis.unwrap_or(0.0);
continue;
@@ -866,7 +865,7 @@ impl Forest {
&Layout {
order: self.children[node].iter().position(|n| *n == child.node).unwrap() as u32,
size: preliminary_size,
- location: Point::zero(),
+ location: Point::ZERO,
},
);
}
@@ -1416,21 +1415,21 @@ impl Forest {
let child_style = self.nodes[child].style;
// X-axis
- let child_position_start = child_style.position.start.resolve(container_width);
- let child_margin_start = child_style.margin.start.resolve(container_width);
+ let child_position_start = child_style.position.start.maybe_resolve(container_width);
+ let child_margin_start = child_style.margin.start.maybe_resolve(container_width);
let start = child_position_start.maybe_add(child_margin_start);
- let child_position_end = child_style.position.end.resolve(container_width);
- let child_margin_end = child_style.margin.end.resolve(container_width);
+ let child_position_end = child_style.position.end.maybe_resolve(container_width);
+ let child_margin_end = child_style.margin.end.maybe_resolve(container_width);
let end = child_position_end.maybe_add(child_margin_end);
// Y-axis
- let child_position_top = child_style.position.top.resolve(container_height);
- let child_margin_top = child_style.margin.top.resolve(container_height);
+ let child_position_top = child_style.position.top.maybe_resolve(container_height);
+ let child_margin_top = child_style.margin.top.maybe_resolve(container_height);
let top = child_position_top.maybe_add(child_margin_top);
- let child_position_bottom = child_style.position.bottom.resolve(container_height);
- let child_margin_bottom = child_style.margin.bottom.resolve(container_height);
+ let child_position_bottom = child_style.position.bottom.maybe_resolve(container_height);
+ let child_margin_bottom = child_style.margin.bottom.maybe_resolve(container_height);
let bottom = child_position_bottom.maybe_add(child_margin_bottom);
let (start_main, end_main) = if constants.is_row { (start, end) } else { (top, bottom) };
@@ -1439,9 +1438,9 @@ impl Forest {
let mut width = child_style
.size
.width
- .resolve(container_width)
- .maybe_max(child_style.min_size.width.resolve(container_width))
- .maybe_min(child_style.max_size.width.resolve(container_width));
+ .maybe_resolve(container_width)
+ .maybe_max(child_style.min_size.width.maybe_resolve(container_width))
+ .maybe_min(child_style.max_size.width.maybe_resolve(container_width));
if width.is_none() && start.is_some() && end.is_some() {
width = container_width.maybe_sub(start).maybe_sub(end);
@@ -1450,9 +1449,9 @@ impl Forest {
let mut height: Option<f32> = child_style
.size
.height
- .resolve(container_height)
- .maybe_max(child_style.min_size.height.resolve(container_height))
- .maybe_min(child_style.max_size.height.resolve(container_height));
+ .maybe_resolve(container_height)
+ .maybe_max(child_style.min_size.height.maybe_resolve(container_height))
+ .maybe_min(child_style.max_size.height.maybe_resolve(container_height));
if height.is_none() && top.is_some() && bottom.is_some() {
height = container_height.maybe_sub(top).maybe_sub(bottom);
@@ -1470,10 +1469,14 @@ impl Forest {
- preliminary_size
.main(constants.dir)
.maybe_max(
- child_style.min_main_size(constants.dir).resolve(constants.node_inner_size.main(constants.dir)),
+ child_style
+ .min_main_size(constants.dir)
+ .maybe_resolve(constants.node_inner_size.main(constants.dir)),
)
.maybe_min(
- child_style.max_main_size(constants.dir).resolve(constants.node_inner_size.main(constants.dir)),
+ child_style
+ .max_main_size(constants.dir)
+ .maybe_resolve(constants.node_inner_size.main(constants.dir)),
);
let free_cross_space = constants.container_size.cross(constants.dir)
@@ -1482,12 +1485,12 @@ impl Forest {
.maybe_max(
child_style
.min_cross_size(constants.dir)
- .resolve(constants.node_inner_size.cross(constants.dir)),
+ .maybe_resolve(constants.node_inner_size.cross(constants.dir)),
)
.maybe_min(
child_style
.max_cross_size(constants.dir)
- .resolve(constants.node_inner_size.cross(constants.dir)),
+ .maybe_resolve(constants.node_inner_size.cross(constants.dir)),
);
let offset_main = if start_main.is_some() {
@@ -1567,7 +1570,7 @@ impl Forest {
}
// Define some general constants we will need for the remainder of the algorithm.
- let mut constants = self.compute_constants(node, node_size, parent_size);
+ let mut constants = Forest::compute_constants(&self.nodes[node], node_size, parent_size);
// If this is a leaf node we can skip a lot of this function in some cases
if self.children[node].is_empty() {
@@ -1716,7 +1719,7 @@ impl Forest {
///
/// Each hidden node has zero size and is placed at the origin
fn hidden_layout(nodes: &mut [NodeData], children: &[ChildrenVec<NodeId>], node: NodeId, order: u32) {
- nodes[node].layout = Layout { order, size: Size::zero(), location: Point::zero() };
+ nodes[node].layout = Layout { order, size: Size::ZERO, location: Point::ZERO };
for (order, child) in children[node].iter().enumerate() {
hidden_layout(nodes, children, *child, order as _);
@@ -1735,3 +1738,61 @@ impl Forest {
container_size
}
}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ forest::Forest,
+ math::MaybeMath,
+ prelude::{Rect, Size},
+ resolve::ResolveOrDefault,
+ style::{FlexWrap, FlexboxLayout},
+ };
+
+ // Make sure we get correct constants
+ #[test]
+ fn correct_constants() {
+ let mut forest = Forest::with_capacity(16);
+
+ let style = FlexboxLayout::default();
+ let node_id = forest.new_leaf(style);
+
+ let node_size = Size::undefined();
+ let parent_size = Size::undefined();
+
+ let constants = Forest::compute_constants(&forest.nodes[node_id], node_size, parent_size);
+
+ assert!(constants.dir == style.flex_direction);
+ assert!(constants.is_row == style.flex_direction.is_row());
+ assert!(constants.is_column == style.flex_direction.is_column());
+ assert!(constants.is_wrap_reverse == (style.flex_wrap == FlexWrap::WrapReverse));
+
+ let margin = style.margin.resolve_or_default(parent_size);
+ assert_eq!(constants.margin, margin);
+
+ let border = style.border.resolve_or_default(parent_size);
+ assert_eq!(constants.border, border);
+
+ let padding = style.padding.resolve_or_default(parent_size);
+
+ // TODO: Replace with something less hardcoded?
+ let padding_border = Rect {
+ start: padding.start + border.start,
+ end: padding.end + border.end,
+ top: padding.top + border.top,
+ bottom: padding.bottom + border.bottom,
+ };
+
+ assert_eq!(constants.padding_border, padding_border);
+
+ // TODO: Replace with something less hardcoded?
+ let inner_size = Size {
+ width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
+ height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
+ };
+ assert_eq!(constants.node_inner_size, inner_size);
+
+ assert_eq!(constants.container_size, Size::ZERO);
+ assert_eq!(constants.inner_container_size, Size::ZERO);
+ }
+}
diff --git a/src/geometry.rs b/src/geometry.rs
index 110f20179..c976d667a 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -29,16 +29,6 @@ pub struct Rect<T> {
}
impl<T> Rect<T> {
- /// Applies the function `f` to all four sides of the [`Rect`]
- ///
- /// This is used to transform a `Rect<T>` into a `Rect<R>`.
- pub(crate) fn map<R, F>(self, f: F) -> Rect<R>
- where
- F: Fn(T) -> R,
- {
- Rect { start: f(self.start), end: f(self.end), top: f(self.top), bottom: f(self.bottom) }
- }
-
/// Applies the function `f` to all four sides of the rect
///
/// When applied to the left and right sides, the width is used
@@ -148,6 +138,17 @@ where
}
}
+impl Rect<f32> {
+ /// Creates a new Rect with `0.0` as all parameters
+ pub const ZERO: Rect<f32> = Self { start: 0.0, end: 0.0, top: 0.0, bottom: 0.0 };
+
+ /// Creates a new Rect
+ #[must_use]
+ pub fn new(start: f32, end: f32, top: f32, bottom: f32) -> Self {
+ Self { start, end, top, bottom }
+ }
+}
+
/// The width and height of a [`Rect`]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -225,27 +226,38 @@ impl<T> Size<T> {
impl Size<f32> {
/// A [`Size`] with zero width and height
+ pub const ZERO: Size<f32> = Self { width: 0.0, height: 0.0 };
+}
+
+impl Size<Option<f32>> {
+ /// A [`Size`] with `None` width and height
+ pub const NONE: Size<Option<f32>> = Self { width: None, height: None };
+
+ /// A [`Size<Option<f32>>`] with `Some(width)` and `Some(height)` as parameters
#[must_use]
- pub fn zero() -> Self {
- Self { width: 0.0, height: 0.0 }
+ pub fn new(width: f32, height: f32) -> Self {
+ Size { width: Some(width), height: Some(height) }
}
}
impl Size<Dimension> {
/// Generates a [`Size<Dimension>`] using [`Dimension::Points`] values
+ #[must_use]
pub fn from_points(width: f32, height: f32) -> Self {
Size { width: Dimension::Points(width), height: Dimension::Points(height) }
}
/// Generates a [`Size<Dimension>`] using [`Dimension::Percent`] values
+ #[must_use]
pub fn from_percent(width: f32, height: f32) -> Self {
Size { width: Dimension::Percent(width), height: Dimension::Percent(height) }
}
- /// Converts any `parent`-relative values for size into an absolute size
- pub(crate) fn resolve(&self, parent: Size<Option<f32>>) -> Size<Option<f32>> {
- Size { width: self.width.resolve(parent.width), height: self.height.resolve(parent.height) }
- }
+ /// Generates a [`Size<Dimension>`] using [`Dimension::Auto`] in both width and height
+ pub const AUTO: Size<Dimension> = Self { width: Dimension::Auto, height: Dimension::Auto };
+
+ /// Generates a [`Size<Dimension>`] using [`Dimension::Undefined`] in both width and height
+ pub const UNDEFINED: Size<Dimension> = Self { width: Dimension::Undefined, height: Dimension::Undefined };
}
/// A 2-dimensional coordinate.
@@ -261,42 +273,5 @@ pub struct Point<T> {
impl Point<f32> {
/// A [`Point`] with values (0,0), representing the origin
- #[must_use]
- pub fn zero() -> Self {
- Self { x: 0.0, y: 0.0 }
- }
-}
-
-#[cfg(test)]
-mod tests {
- mod test_resolve_size {
- use crate::geometry::Size;
- use crate::style::Dimension;
- use rstest::rstest;
-
- #[rstest]
- #[case(Size { width: Dimension::Undefined, height: Dimension::Undefined }, Size { width: None, height: None }, Size { width: None, height: None })]
- #[case(Size { width: Dimension::Undefined, height: Dimension::Undefined }, Size { width: Some(0.0), height: Some(0.0) }, Size { width: None, height: None })]
- #[case(Size { width: Dimension::Undefined, height: Dimension::Undefined }, Size { width: Some(1.0), height: Some(1.0) }, Size { width: None, height: None })]
- #[case(Size { width: Dimension::Undefined, height: Dimension::Undefined }, Size { width: Some(-1.0), height: Some(-1.0) }, Size { width: None, height: None })]
- #[case(Size { width: Dimension::Auto, height: Dimension::Auto }, Size { width: None, height: None }, Size { width: None, height: None })]
- #[case(Size { width: Dimension::Auto, height: Dimension::Auto }, Size { width: Some(0.0), height: Some(0.0) }, Size { width: None, height: None })]
- #[case(Size { width: Dimension::Auto, height: Dimension::Auto }, Size { width: Some(1.0), height: Some(1.0) }, Size { width: None, height: None })]
- #[case(Size { width: Dimension::Auto, height: Dimension::Auto }, Size { width: Some(-1.0), height: Some(-1.0) }, Size { width: None, height: None })]
- #[case(Size { width: Dimension::Points(5.0), height: Dimension::Points(5.0) }, Size { width: None, height: None }, Size { width: Some(5.0), height: Some(5.0) })]
- #[case(Size { width: Dimension::Points(5.0), height: Dimension::Points(5.0) }, Size { width: Some(0.0), height: Some(0.0) }, Size { width: Some(5.0), height: Some(5.0) })]
- #[case(Size { width: Dimension::Points(5.0), height: Dimension::Points(5.0) }, Size { width: Some(1.0), height: Some(1.0) }, Size { width: Some(5.0), height: Some(5.0) })]
- #[case(Size { width: Dimension::Points(5.0), height: Dimension::Points(5.0) }, Size { width: Some(-1.0), height: Some(-1.0) }, Size { width: Some(5.0), height: Some(5.0) })]
- #[case(Size { width: Dimension::Percent(50.0), height: Dimension::Percent(50.0) }, Size { width: None, height: None }, Size { width: None, height: None })]
- #[case(Size { width: Dimension::Percent(50.0), height: Dimension::Percent(50.0) }, Size { width: Some(0.0), height: Some(0.0) }, Size { width: Some(0.0), height: Some(0.0) })]
- #[case(Size { width: Dimension::Percent(50.0), height: Dimension::Percent(50.0) }, Size { width: Some(1.0), height: Some(1.0) }, Size { width: Some(50.0), height: Some(50.0) })]
- #[case(Size { width: Dimension::Percent(50.0), height: Dimension::Percent(50.0) }, Size { width: Some(-1.0), height: Some(-1.0) }, Size { width: Some(-50.0), height: Some(-50.0) })]
- fn resolve_size(
- #[case] input: Size<Dimension>,
- #[case] parent: Size<Option<f32>>,
- #[case] expected: Size<Option<f32>>,
- ) {
- assert_eq!(input.resolve(parent), expected);
- }
- }
+ pub const ZERO: Point<f32> = Self { x: 0.0, y: 0.0 };
}
diff --git a/src/layout.rs b/src/layout.rs
index cbf323a90..397a7d434 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -20,7 +20,7 @@ impl Layout {
/// Creates a new [`Layout`] struct with zero size positioned at the origin
#[must_use]
pub(crate) fn new() -> Self {
- Self { order: 0, size: Size::zero(), location: Point::zero() }
+ Self { order: 0, size: Size::ZERO, location: Point::ZERO }
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 46bc39bdc..3ccf788bd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,6 +29,7 @@ mod flexbox;
mod forest;
#[cfg(all(not(feature = "alloc"), not(feature = "std")))]
mod indexmap;
+mod resolve;
mod sys;
pub use crate::node::Taffy;
diff --git a/src/node.rs b/src/node.rs
index b86d3fd5a..1df0178b2 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -429,7 +429,7 @@ mod tests {
fn new_leaf_with_measure() {
let mut taffy = Taffy::new();
- let res = taffy.new_leaf_with_measure(FlexboxLayout::default(), MeasureFunc::Raw(|_| Size::zero()));
+ let res = taffy.new_leaf_with_measure(FlexboxLayout::default(), MeasureFunc::Raw(|_| Size::ZERO));
assert!(res.is_ok());
let node = res.unwrap();
diff --git a/src/resolve.rs b/src/resolve.rs
new file mode 100644
index 000000000..bfe62d682
--- /dev/null
+++ b/src/resolve.rs
@@ -0,0 +1,363 @@
+//! Helper trait to calculate dimensions during layout resolution
+
+use crate::prelude::{Dimension, Rect, Size};
+
+/// 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(crate) trait ResolveOrDefault<TContext, TOutput> {
+ /// Resolve a dimension that might be dependent on a context, with a default fallback value
+ fn resolve_or_default(self, context: TContext) -> TOutput;
+}
+
+/// 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(crate) trait MaybeResolve<T> {
+ /// Resolve a dimension that might be dependent on a context, with `None` as fallback value
+ fn maybe_resolve(self, context: T) -> T;
+}
+
+impl MaybeResolve<Option<f32>> for Dimension {
+ /// Converts the given [`Dimension`] into a concrete value of points
+ ///
+ /// Can return `None`
+ fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
+ match self {
+ Dimension::Points(points) => Some(points),
+ // parent_dim * percent
+ Dimension::Percent(percent) => context.map(|dim| dim * percent),
+ _ => None,
+ }
+ }
+}
+
+impl MaybeResolve<Size<Option<f32>>> for Size<Dimension> {
+ /// Converts any `parent`-relative values for size into an absolute size
+ fn maybe_resolve(self, context: Size<Option<f32>>) -> Size<Option<f32>> {
+ Size { width: self.width.maybe_resolve(context.width), height: self.height.maybe_resolve(context.height) }
+ }
+}
+
+impl ResolveOrDefault<Option<f32>, f32> for Dimension {
+ /// Will return a default value of result is evaluated to `None`
+ fn resolve_or_default(self, context: Option<f32>) -> f32 {
+ self.maybe_resolve(context).unwrap_or(0.0)
+ }
+}
+
+impl ResolveOrDefault<Size<Option<f32>>, Rect<f32>> for Rect<Dimension> {
+ fn resolve_or_default(self, context: Size<Option<f32>>) -> Rect<f32> {
+ Rect {
+ start: self.start.resolve_or_default(context.width),
+ end: self.end.resolve_or_default(context.width),
+ top: self.top.resolve_or_default(context.height),
+ bottom: self.bottom.resolve_or_default(context.height),
+ }
+ }
+}
+
+impl ResolveOrDefault<Option<f32>, Rect<f32>> for Rect<Dimension> {
+ fn resolve_or_default(self, context: Option<f32>) -> Rect<f32> {
+ Rect {
+ start: self.start.resolve_or_default(context),
+ end: self.end.resolve_or_default(context),
+ top: self.top.resolve_or_default(context),
+ bottom: self.bottom.resolve_or_default(context),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ mod maybe_resolve_dimension {
+
+ use crate::resolve::MaybeResolve;
+ use crate::style::Dimension;
+ use rstest::rstest;
+
+ /// `Dimension::Undefined` should always return `None`
+ ///
+ /// The parent / context should not affect the outcome.
+ #[rstest]
+ #[case(Dimension::Undefined, None, None)]
+ #[case(Dimension::Undefined, Some(5.0), None)]
+ #[case(Dimension::Undefined, Some(-5.0), None)]
+ #[case(Dimension::Undefined, Some(0.), None)]
+ fn resolve_undefined(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
+ assert_eq!(input.maybe_resolve(context), expected);
+ }
+
+ /// `Dimension::Auto` should always return `None`
+ ///
+ /// The parent / context should not affect the outcome.
+ #[rstest]
+ #[case(Dimension::Auto, None, None)]
+ #[case(Dimension::Auto, Some(5.0), None)]
+ #[case(Dimension::Auto, Some(-5.0), None)]
+ #[case(Dimension::Auto, Some(0.), None)]
+ fn resolve_auto(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
+ assert_eq!(input.maybe_resolve(context), expected);
+ }
+
+ /// `Dimension::Points` should always return `Some(f32)`
+ /// where the f32 value is the inner value of the points.
+ ///
+ /// The parent / context should not affect the outcome.
+ #[rstest]
+ #[case(Dimension::Points(1.0), None, Some(1.0))]
+ #[case(Dimension::Points(1.0), Some(5.0), Some(1.0))]
+ #[case(Dimension::Points(1.0), Some(-5.0), Some(1.0))]
+ #[case(Dimension::Points(1.0), Some(0.), Some(1.0))]
+ fn resolve_points(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
+ assert_eq!(input.maybe_resolve(context), expected);
+ }
+
+ /// `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.
+ #[rstest]
+ #[case(Dimension::Percent(1.0), None, None)]
+ #[case(Dimension::Percent(1.0), Some(5.0), Some(5.0))]
+ #[case(Dimension::Percent(1.0), Some(-5.0), Some(-5.0))]
+ #[case(Dimension::Percent(1.0), Some(50.0), Some(50.0))]
+ fn resolve_percent(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
+ assert_eq!(input.maybe_resolve(context), expected);
+ }
+ }
+
+ mod maybe_resolve_size_dimension {
+ use crate::{prelude::Size, resolve::MaybeResolve, style::Dimension};
+ use rstest::rstest;
+
+ /// Size<Dimension::Undefined> should always return Size<None>
+ ///
+ /// The parent / context should not affect the outcome.
+ #[rstest]
+ #[case(Size::UNDEFINED, Size::NONE, Size::NONE)]
+ #[case(Size::UNDEFINED, Size::new(5.0, 5.0), Size::NONE)]
+ #[case(Size::UNDEFINED, Size::new(-5.0, -5.0), Size::NONE)]
+ #[case(Size::UNDEFINED, Size::new(0.0, 0.0), Size::NONE)]
+ fn maybe_resolve_undefined(
+ #[case] input: Size<Dimension>,
+ #[case] context: Size<Option<f32>>,
+ #[case] expected: Size<Option<f32>>,
+ ) {
+ assert_eq!(input.maybe_resolve(context), expected);
+ }
+
+ /// Size<Dimension::Auto> should always return Size<None>
+ ///
+ /// The parent / context should not affect the outcome.
+ #[rstest]
+ #[case(Size::AUTO, Size::NONE, Size::NONE)]
+ #[case(Size::AUTO, Size::new(5.0, 5.0), Size::NONE)]
+ #[case(Size::AUTO, Size::new(-5.0, -5.0), Size::NONE)]
+ #[case(Size::AUTO, Size::new(0.0, 0.0), Size::NONE)]
+ fn maybe_resolve_auto(
+ #[case] input: Size<Dimension>,
+ #[case] context: Size<Option<f32>>,
+ #[case] expected: Size<Option<f32>>,
+ ) {
+ assert_eq!(input.maybe_resolve(context), expected);
+ }
+
+ /// Size<Dimension::Points> should always return a Size<Some(f32)>
+ /// where the f32 values are the inner value of the points.
+ ///
+ /// The parent / context should not affect the outcome.
+ #[rstest]
+ #[case(Size::from_points(5.0, 5.0), Size::NONE, Size::new(5.0, 5.0))]
+ #[case(Size::from_points(5.0, 5.0), Size::new(5.0, 5.0), Size::new(5.0, 5.0))]
+ #[case(Size::from_points(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(5.0, 5.0))]
+ #[case(Size::from_points(5.0, 5.0), Size::new(0.0, 0.0), Size::new(5.0, 5.0))]
+ fn maybe_resolve_points(
+ #[case] input: Size<Dimension>,
+ #[case] context: Size<Option<f32>>,
+ #[case] expected: Size<Option<f32>>,
+ ) {
+ assert_eq!(input.maybe_resolve(context), expected);
+ }
+
+ /// `Size<Dimension::Percent>` should return `Size<None>` if context is `Size<None>`.
+ /// Otherwise it should return `Size<Some(f32)>`
+ /// where the f32 value is the inner value of the percent * context value.
+ ///
+ /// The context __should__ affect the outcome.
+ #[rstest]
+ #[case(Size::from_percent(5.0, 5.0), Size::NONE, Size::NONE)]
+ #[case(Size::from_percent(5.0, 5.0), Size::new(5.0, 5.0), Size::new(25.0, 25.0))]
+ #[case(Size::from_percent(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(-25.0, -25.0))]
+ #[case(Size::from_percent(5.0, 5.0), Size::new(0.0, 0.0), Size::new(0.0, 0.0))]
+ fn maybe_resolve_percent(
+ #[case] input: Size<Dimension>,
+ #[case] context: Size<Option<f32>>,
+ #[case] expected: Size<Option<f32>>,
+ ) {
+ assert_eq!(input.maybe_resolve(context), expected);
+ }
+ }
+
+ mod resolve_or_default_dimension_to_option_f32 {
+ use crate::resolve::ResolveOrDefault;
+ use crate::style::Dimension;
+ use rstest::rstest;
+
+ #[rstest]
+ #[case(Dimension::Undefined, None, 0.0)]
+ #[case(Dimension::Undefined, Some(5.0), 0.0)]
+ #[case(Dimension::Undefined, Some(-5.0), 0.0)]
+ #[case(Dimension::Undefined, Some(0.0), 0.0)]
+ fn resolve_or_default_undefined(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+ #[rstest]
+ #[case(Dimension::Auto, None, 0.0)]
+ #[case(Dimension::Auto, Some(5.0), 0.0)]
+ #[case(Dimension::Auto, Some(-5.0), 0.0)]
+ #[case(Dimension::Auto, Some(0.0), 0.0)]
+ fn resolve_or_default_auto(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+ #[rstest]
+ #[case(Dimension::Points(5.0), None, 5.0)]
+ #[case(Dimension::Points(5.0), Some(5.0), 5.0)]
+ #[case(Dimension::Points(5.0), Some(-5.0), 5.0)]
+ #[case(Dimension::Points(5.0), Some(0.0), 5.0)]
+ fn resolve_or_default_points(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+ #[rstest]
+ #[case(Dimension::Percent(5.0), None, 0.0)]
+ #[case(Dimension::Percent(5.0), Some(5.0), 25.0)]
+ #[case(Dimension::Percent(5.0), Some(-5.0), -25.0)]
+ #[case(Dimension::Percent(5.0), Some(0.0), 0.0)]
+ fn resolve_or_default_percent(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+ }
+
+ mod resolve_or_default_rect_dimension_to_rect {
+ use crate::geometry::{Rect, Size};
+ use crate::resolve::ResolveOrDefault;
+ use crate::style::Dimension;
+ use rstest::rstest;
+
+ #[rstest]
+ #[case(Rect::UNDEFINED, Size::NONE, Rect::ZERO)]
+ #[case(Rect::UNDEFINED, Size::new(5.0, 5.0), Rect::ZERO)]
+ #[case(Rect::UNDEFINED, Size::new(-5.0, -5.0), Rect::ZERO)]
+ #[case(Rect::UNDEFINED, Size::new(0.0, 0.0), Rect::ZERO)]
+ fn resolve_or_default_undefined(
+ #[case] input: Rect<Dimension>,
+ #[case] context: Size<Option<f32>>,
+ #[case] expected: Rect<f32>,
+ ) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+
+ #[rstest]
+ #[case(Rect::AUTO, Size::NONE, Rect::ZERO)]
+ #[case(Rect::AUTO, Size::new(5.0, 5.0), Rect::ZERO)]
+ #[case(Rect::AUTO, Size::new(-5.0, -5.0), Rect::ZERO)]
+ #[case(Rect::AUTO, Size::new(0.0, 0.0), Rect::ZERO)]
+ fn resolve_or_default_auto(
+ #[case] input: Rect<Dimension>,
+ #[case] context: Size<Option<f32>>,
+ #[case] expected: Rect<f32>,
+ ) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+
+ #[rstest]
+ #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::new(5.0, 5.0, 5.0, 5.0))]
+ #[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))]
+ #[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))]
+ #[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))]
+ fn resolve_or_default_points(
+ #[case] input: Rect<Dimension>,
+ #[case] context: Size<Option<f32>>,
+ #[case] expected: Rect<f32>,
+ ) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+
+ #[rstest]
+ #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::ZERO)]
+ #[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))]
+ #[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))]
+ #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::ZERO)]
+ fn resolve_or_default_percent(
+ #[case] input: Rect<Dimension>,
+ #[case] context: Size<Option<f32>>,
+ #[case] expected: Rect<f32>,
+ ) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+ }
+
+ mod resolve_or_default_rect_dimension_to_rect_f32_via_option {
+ use crate::geometry::Rect;
+ use crate::resolve::ResolveOrDefault;
+ use crate::style::Dimension;
+ use rstest::rstest;
+
+ #[rstest]
+ #[case(Rect::UNDEFINED, None, Rect::ZERO)]
+ #[case(Rect::UNDEFINED, Some(5.0), Rect::ZERO)]
+ #[case(Rect::UNDEFINED, Some(-5.0), Rect::ZERO)]
+ #[case(Rect::UNDEFINED, Some(0.0), Rect::ZERO)]
+ fn resolve_or_default_undefined(
+ #[case] input: Rect<Dimension>,
+ #[case] context: Option<f32>,
+ #[case] expected: Rect<f32>,
+ ) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+
+ #[rstest]
+ #[case(Rect::AUTO, None, Rect::ZERO)]
+ #[case(Rect::AUTO, Some(5.0), Rect::ZERO)]
+ #[case(Rect::AUTO, Some(-5.0), Rect::ZERO)]
+ #[case(Rect::AUTO, Some(0.0), Rect::ZERO)]
+ fn resolve_or_default_auto(
+ #[case] input: Rect<Dimension>,
+ #[case] context: Option<f32>,
+ #[case] expected: Rect<f32>,
+ ) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+
+ #[rstest]
+ #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), None, Rect::new(5.0, 5.0, 5.0, 5.0))]
+ #[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))]
+ #[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))]
+ #[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))]
+ fn resolve_or_default_points(
+ #[case] input: Rect<Dimension>,
+ #[case] context: Option<f32>,
+ #[case] expected: Rect<f32>,
+ ) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+
+ #[rstest]
+ #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), None, Rect::ZERO)]
+ #[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))]
+ #[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))]
+ #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::ZERO)]
+ fn resolve_or_default_percent(
+ #[case] input: Rect<Dimension>,
+ #[case] context: Option<f32>,
+ #[case] expected: Rect<f32>,
+ ) {
+ assert_eq!(input.resolve_or_default(context), expected);
+ }
+ }
+}
diff --git a/src/style.rs b/src/style.rs
index 8403093ed..d57e38358 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -270,16 +270,6 @@ impl Default for Dimension {
}
impl Dimension {
- /// Converts the given [`Dimension`] into a concrete value of points
- pub(crate) fn resolve(self, parent_dim: Option<f32>) -> Option<f32> {
- match self {
- Dimension::Points(points) => Some(points),
- // parent_dim * percent
- Dimension::Percent(percent) => parent_dim.map(|dim| dim * percent),
- _ => None,
- }
- }
-
/// Is this value defined?
pub(crate) fn is_defined(self) -> bool {
matches!(self, Dimension::Points(_) | Dimension::Percent(_))
@@ -294,24 +284,62 @@ impl Default for Rect<Dimension> {
impl Rect<Dimension> {
/// Generates a [`Rect<Dimension>`] using [`Dimension::Points`] values for `start` and `top`
+ #[must_use]
pub fn top_from_points(start: f32, top: f32) -> Rect<Dimension> {
Rect { start: Dimension::Points(start), top: Dimension::Points(top), ..Default::default() }
}
/// Generates a [`Rect<Dimension>`] using [`Dimension::Points`] values for `end` and `bottom`
+ #[must_use]
pub fn bot_from_points(end: f32, bottom: f32) -> Rect<Dimension> {
Rect { end: Dimension::Points(end), bottom: Dimension::Points(bottom), ..Default::default() }
}
/// Generates a [`Rect<Dimension>`] using [`Dimension::Percent`] values for `start` and `top`
+ #[must_use]
pub fn top_from_percent(start: f32, top: f32) -> Rect<Dimension> {
Rect { start: Dimension::Percent(start), top: Dimension::Percent(top), ..Default::default() }
}
/// Generates a [`Rect<Dimension>`] using [`Dimension::Percent`] values for `end` and `bottom`
+ #[must_use]
pub fn bot_from_percent(end: f32, bottom: f32) -> Rect<Dimension> {
Rect { end: Dimension::Percent(end), bottom: Dimension::Percent(bottom), ..Default::default() }
}
+
+ /// Generates a [`Rect<Dimension>`] using [`Dimension::Undefined`] for all values
+ pub const UNDEFINED: Rect<Dimension> = Self {
+ start: Dimension::Undefined,
+ end: Dimension::Undefined,
+ top: Dimension::Undefined,
+ bottom: Dimension::Undefined,
+ };
+
+ /// Generates a [`Rect<Dimension>`] using [`Dimension::Auto`] for all values
+ pub const AUTO: Rect<Dimension> =
+ Self { start: Dimension::Auto, end: Dimension::Auto, top: Dimension::Auto, bottom: Dimension::Auto };
+
+ /// Create a new Rect with [`Dimension::Points`]
+ #[must_use]
+ pub fn from_points(start: f32, end: f32, top: f32, bottom: f32) -> Self {
+ Rect {
+ start: Dimension::Points(start),
+ end: Dimension::Points(end),
+ top: Dimension::Points(top),
+ bottom: Dimension::Points(bottom),
+ }
+ }
+
+ /// Create a new Rect with [`Dimension::Percent`]
+ #[must_use]
+ pub fn from_percent(start: f32, end: f32, top: f32, bottom: f32) -> Self {
+ Rect {
+ start: Dimension::Percent(start),
+ end: Dimension::Percent(end),
+ top: Dimension::Percent(top),
+ bottom: Dimension::Percent(bottom),
+ }
+ }
}
impl Default for Size<Dimension> {
@@ -514,47 +542,6 @@ impl FlexboxLayout {
#[cfg(test)]
mod tests {
- mod test_resolve_dimensions {
- use crate::style::Dimension;
- use rstest::rstest;
-
- #[rstest]
- #[case(Dimension::Undefined, None, None)]
- #[case(Dimension::Undefined, Some(5.0), None)]
- #[case(Dimension::Undefined, Some(-5.0), None)]
- #[case(Dimension::Undefined, Some(0.), None)]
- fn resolve_undefined(#[case] input: Dimension, #[case] parent: Option<f32>, #[case] expected: Option<f32>) {
- assert_eq!(input.resolve(parent), expected);
- }
-
- #[rstest]
- #[case(Dimension::Auto, None, None)]
- #[case(Dimension::Auto, Some(5.0), None)]
- #[case(Dimension::Auto, Some(-5.0), None)]
- #[case(Dimension::Auto, Some(0.), None)]
- fn resolve_auto(#[case] input: Dimension, #[case] parent: Option<f32>, #[case] expected: Option<f32>) {
- assert_eq!(input.resolve(parent), expected);
- }
-
- #[rstest]
- #[case(Dimension::Points(0.), None, Some(0.))]
- #[case(Dimension::Points(1.), Some(5.0), Some(1.))]
- #[case(Dimension::Points(-1.), Some(-5.0), Some(-1.))]
- #[case(Dimension::Points(1.0), Some(0.), Some(1.0))]
- fn resolve_points(#[case] input: Dimension, #[case] parent: Option<f32>, #[case] expected: Option<f32>) {
- assert_eq!(input.resolve(parent), expected);
- }
-
- #[rstest]
- #[case(Dimension::Percent(1.0), None, None)]
- #[case(Dimension::Percent(1.0), Some(5.0), Some(5.0))]
- #[case(Dimension::Percent(1.0), Some(-5.0), Some(-5.0))]
- #[case(Dimension::Percent(10.0), Some(5.0), Some(50.0))]
- fn resolve_percent(#[case] input: Dimension, #[case] parent: Option<f32>, #[case] expected: Option<f32>) {
- assert_eq!(input.resolve(parent), expected);
- }
- }
-
mod test_flex_direction {
use crate::style::*;