Fix: available space in the presence of a min width (#407)
* Add `with_main` and `with_cross` methods to Size
* Add test for min_width > available_space
* Clamp available cross space by min/max height when computing flex basis
* Clamp available cross space by min/max size when computing hypothetical cross size
* Bump measure func call counts to 8
0b736d47e1e71f29aafdf940d0204d5fcdb61de4 · por Nico Burns · 2023-03-30T10:19:28Z (hace 3 años) · padre ac79a0044 · ver el árbol en este commit
Delta semántico · 9
tests/generated/measure_child_with_min_size_greater_than_available_space.rs
diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs
index 89069bc20..a5b3916a2 100644
--- a/src/compute/flexbox.rs
+++ b/src/compute/flexbox.rs
@@ -611,9 +611,20 @@ fn determine_flex_base_size(
available_space: Size<AvailableSpace>,
flex_items: &mut [FlexItem],
) {
+ let dir = constants.dir;
+
for child in flex_items.iter_mut() {
let child_style = tree.style(child.node);
+ // Parent size for child sizing
+ let cross_axis_parent_size = constants.node_inner_size.cross(dir);
+
+ // Available space for child sizing
+ let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
+ let child_min_cross = child.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
+ let child_max_cross = child.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
+ let cross_axis_available_space = cross_axis_parent_size.maybe_clamp(child_min_cross, child_max_cross).into();
+
child.flex_basis = 'flex_basis: {
// A. If the item has a definite used flex basis, that’s the flex base size.
@@ -625,8 +636,8 @@ fn determine_flex_base_size(
// Note: `child.size` has already been resolved against aspect_ratio in generate_anonymous_flex_items
// So B will just work here by using main_size without special handling for aspect_ratio
- let flex_basis = child_style.flex_basis.maybe_resolve(constants.node_inner_size.main(constants.dir));
- let main_size = child.size.main(constants.dir);
+ let flex_basis = child_style.flex_basis.maybe_resolve(constants.node_inner_size.main(dir));
+ let main_size = child.size.main(dir);
if let Some(flex_basis) = flex_basis.or(main_size) {
break 'flex_basis flex_basis;
};
@@ -655,15 +666,19 @@ fn determine_flex_base_size(
// is auto and not definite, in this calculation use fit-content as the
// flex item’s cross size. The flex base size is the item’s resulting main size.
+ let child_parent_size = Size::NONE.with_cross(dir, cross_axis_parent_size);
+ let child_available_space = available_space.with_cross(dir, cross_axis_available_space);
+
let child_known_dimensions = {
let mut ckd = child.size;
- if child.align_self == AlignSelf::Stretch && ckd.cross(constants.dir).is_none() {
+ if child.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
ckd.set_cross(
- constants.dir,
- available_space
- .cross(constants.dir)
+ dir,
+ child_available_space
+ .cross(dir)
.into_option()
- .maybe_sub(constants.margin.cross_axis_sum(constants.dir)),
+ .maybe_clamp(child_min_cross, child_max_cross)
+ .maybe_sub(constants.margin.cross_axis_sum(dir)),
);
}
ckd
@@ -673,11 +688,11 @@ fn determine_flex_base_size(
tree,
child.node,
child_known_dimensions,
- constants.node_inner_size,
- available_space,
+ child_parent_size,
+ child_available_space,
SizingMode::ContentSize,
)
- .main(constants.dir);
+ .main(dir);
};
// Floor flex-basis by the padding_border_sum (floors inner_flex_basis at zero)
@@ -713,23 +728,15 @@ fn determine_flex_base_size(
//
// See https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution
let min_content_size = {
- let cross_axis_parent_size = constants.node_inner_size.cross(constants.dir);
- let cross_axis_available_space = cross_axis_parent_size
- .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
- .into();
-
- let mut parent_size = Size::NONE;
- parent_size.set_cross(constants.dir, cross_axis_parent_size);
-
- let mut available_space: Size<AvailableSpace> = Size::MIN_CONTENT;
- available_space.set_cross(constants.dir, cross_axis_available_space);
+ let child_parent_size = Size::NONE.with_cross(dir, cross_axis_parent_size);
+ let child_available_space = Size::MIN_CONTENT.with_cross(dir, cross_axis_available_space);
GenericAlgorithm::measure_size(
tree,
child.node,
Size::NONE,
- parent_size,
- available_space,
+ child_parent_size,
+ child_available_space,
SizingMode::ContentSize,
)
};
@@ -1220,12 +1227,19 @@ fn determine_hypothetical_cross_size(
for child in line.items.iter_mut() {
let padding_border_sum = (child.padding + child.border).cross_axis_sum(constants.dir);
+ let child_known_main = constants.container_size.main(constants.dir).into();
+
let child_cross = child
.size
.cross(constants.dir)
.maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
.maybe_max(padding_border_sum);
+ let child_available_cross = available_space
+ .cross(constants.dir)
+ .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
+ .maybe_max(padding_border_sum);
+
let child_inner_cross = child_cross.unwrap_or_else(|| {
GenericAlgorithm::measure_size(
tree,
@@ -1236,16 +1250,8 @@ fn determine_hypothetical_cross_size(
},
constants.node_inner_size,
Size {
- width: if constants.is_row {
- constants.container_size.main(constants.dir).into()
- } else {
- available_space.width
- },
- height: if constants.is_row {
- available_space.height
- } else {
- constants.container_size.main(constants.dir).into()
- },
+ width: if constants.is_row { child_known_main } else { child_available_cross },
+ height: if constants.is_row { child_available_cross } else { child_known_main },
},
SizingMode::ContentSize,
)
diff --git a/src/geometry.rs b/src/geometry.rs
index 3db779ed1..3f96d3d85 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -320,6 +320,33 @@ impl<T> Size<T> {
}
}
+ /// Creates a new value of type Self with the main axis set to value provided
+ ///
+ /// Whether this is the width or height depends on the `direction` provided
+ #[allow(dead_code)]
+ pub(crate) fn with_main(self, direction: FlexDirection, value: T) -> Self {
+ let mut new = self;
+ if direction.is_row() {
+ new.width = value
+ } else {
+ new.height = value
+ }
+ new
+ }
+
+ /// Creates a new value of type Self with the cross axis set to value provided
+ ///
+ /// Whether this is the width or height depends on the `direction` provided
+ pub(crate) fn with_cross(self, direction: FlexDirection, value: T) -> Self {
+ let mut new = self;
+ if direction.is_row() {
+ new.height = value
+ } else {
+ new.width = value
+ }
+ new
+ }
+
/// Gets the extent of the main layout axis
///
/// Whether this is the width or height depends on the `direction` provided
diff --git a/test_fixtures/measure_child_with_min_size_greater_than_available_space.html b/test_fixtures/measure_child_with_min_size_greater_than_available_space.html
new file mode 100644
index 000000000..3e46330f0
--- /dev/null
+++ b/test_fixtures/measure_child_with_min_size_greater_than_available_space.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <script src="../scripts/gentest/test_helper.js"></script>
+ <link rel="stylesheet" type="text/css" href="../scripts/gentest/test_base_style.css">
+ <title>
+ Test description
+ </title>
+<head/>
+<body>
+
+<div id="test-root" style="display: flex; flex-direction: column; width: 100px;">
+ <div style="min-width: 200px;">HHHHHHHH​HHHHHHHH</div>
+</div>
diff --git a/tests/caching.rs b/tests/caching.rs
index 387d56002..581ce43e9 100644
--- a/tests/caching.rs
+++ b/tests/caching.rs
@@ -30,7 +30,7 @@ mod caching {
taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
- assert_eq!(NUM_MEASURES.load(Ordering::SeqCst), 7);
+ assert_eq!(NUM_MEASURES.load(Ordering::SeqCst), 8);
}
#[test]
@@ -62,6 +62,6 @@ mod caching {
}
taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
- assert_eq!(NUM_MEASURES.load(Ordering::SeqCst), 7);
+ assert_eq!(NUM_MEASURES.load(Ordering::SeqCst), 8);
}
}
diff --git a/tests/generated/measure_child_with_min_size_greater_than_available_space.rs b/tests/generated/measure_child_with_min_size_greater_than_available_space.rs
new file mode 100644
index 000000000..d42622268
--- /dev/null
+++ b/tests/generated/measure_child_with_min_size_greater_than_available_space.rs
@@ -0,0 +1,50 @@
+#[test]
+fn measure_child_with_min_size_greater_than_available_space() {
+ use slotmap::Key;
+ #[allow(unused_imports)]
+ use taffy::{layout::Layout, prelude::*};
+ let mut taffy = taffy::Taffy::new();
+ let node0 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style {
+ min_size: taffy::geometry::Size { width: taffy::style::Dimension::Points(200f32), height: auto() },
+ ..Default::default()
+ },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HHHHHHHH\u{200b}HHHHHHHH";
+ super::measure_standard_text(
+ known_dimensions,
+ available_space,
+ TEXT,
+ super::WritingMode::Horizontal,
+ None,
+ )
+ }),
+ )
+ .unwrap();
+ let node = taffy
+ .new_with_children(
+ taffy::style::Style {
+ display: taffy::style::Display::Flex,
+ flex_direction: taffy::style::FlexDirection::Column,
+ size: taffy::geometry::Size { width: taffy::style::Dimension::Points(100f32), height: auto() },
+ ..Default::default()
+ },
+ &[node0],
+ )
+ .unwrap();
+ taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+ println!("\nComputed tree:");
+ taffy::debug::print_tree(&taffy, node);
+ println!();
+ let Layout { size, location, .. } = taffy.layout(node).unwrap();
+ assert_eq!(size.width, 100f32, "width of node {:?}. Expected {}. Actual {}", node.data(), 100f32, size.width);
+ assert_eq!(size.height, 10f32, "height of node {:?}. Expected {}. Actual {}", node.data(), 10f32, size.height);
+ assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.x);
+ assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.y);
+ let Layout { size, location, .. } = taffy.layout(node0).unwrap();
+ assert_eq!(size.width, 200f32, "width of node {:?}. Expected {}. Actual {}", node0.data(), 200f32, size.width);
+ assert_eq!(size.height, 10f32, "height of node {:?}. Expected {}. Actual {}", node0.data(), 10f32, size.height);
+ assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.x);
+ assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.y);
+}
diff --git a/tests/generated/mod.rs b/tests/generated/mod.rs
index 482132f0b..4be363806 100644
--- a/tests/generated/mod.rs
+++ b/tests/generated/mod.rs
@@ -812,6 +812,7 @@ mod measure_child_constraint;
mod measure_child_constraint_padding_parent;
mod measure_child_with_flex_grow;
mod measure_child_with_flex_shrink;
+mod measure_child_with_min_size_greater_than_available_space;
mod measure_flex_basis_overrides_measure;
mod measure_height_overrides_measure;
mod measure_remeasure_child_after_growing;