Fix sizing of flexbox nodes under a min-content constraint (#291)
* Fix flexbox nodes sized under a min_content constraint
* Improve caching in light on min_content changes (bump cache size to 5)
* Update measure.rs tests: these were plain wrong (don't match chrome)
* Fix clippy lints
* Add min-content fix to release notes
* Document caching strategy
* Remove debug logs from only_measure_once test
* Remove commented out code
* Cargo fmt
163284a65cd48cdf6c6b96b9216284cb1fefacd3 · por Nico Burns · (hace 3 años) · padre 2b7d63a03 · ver el árbol en este commit
Delta semántico · 39
benches/generated/grid_min_content_flex_column.rs
benches/generated/grid_min_content_flex_row.rs
tests/generated/grid_min_content_flex_column.rs
tests/generated/grid_min_content_flex_row.rs
diff --git a/RELEASES.md b/RELEASES.md
index 1559ce214..203e632fe 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,5 +1,11 @@
# Release Notes
+## 0.3.0-alpha2 (unreleased)
+
+### Fixes
+
+- Flexbox nodes sized under a min-content constraint now size correctly (#291)
+
## 0.3.0-alpha1
This is the first in a series of planned alpha releases to allow users of Taffy to try out the new CSS Grid layout mode in advance of a stable release. We hope that by marking this is alpha release we are clearly communicating that this a pre-release and that the implementation is not yet of production quality. But we never-the-less encourage you to try it out. Feedback is welcome, and bug reports for the Grid implementation are being accepted as of this release.
diff --git a/benches/generated/grid_min_content_flex_column.rs b/benches/generated/grid_min_content_flex_column.rs
new file mode 100644
index 000000000..1714e6234
--- /dev/null
+++ b/benches/generated/grid_min_content_flex_column.rs
@@ -0,0 +1,54 @@
+pub fn compute() {
+ #[allow(unused_imports)]
+ use taffy::prelude::*;
+ let mut taffy = taffy::Taffy::new();
+ let node00 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node01 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node02 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node0 = taffy
+ .new_with_children(
+ taffy::style::Style {
+ display: taffy::style::Display::Flex,
+ flex_direction: taffy::style::FlexDirection::Column,
+ ..Default::default()
+ },
+ &[node00, node01, node02],
+ )
+ .unwrap();
+ let node = taffy
+ .new_with_children(
+ taffy::style::Style {
+ display: taffy::style::Display::Grid,
+ grid_template_rows: vec![points(40f32)],
+ grid_template_columns: vec![min_content()],
+ ..Default::default()
+ },
+ &[node0],
+ )
+ .unwrap();
+ taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+}
diff --git a/benches/generated/grid_min_content_flex_row.rs b/benches/generated/grid_min_content_flex_row.rs
new file mode 100644
index 000000000..0d715bda0
--- /dev/null
+++ b/benches/generated/grid_min_content_flex_row.rs
@@ -0,0 +1,50 @@
+pub fn compute() {
+ #[allow(unused_imports)]
+ use taffy::prelude::*;
+ let mut taffy = taffy::Taffy::new();
+ let node00 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node01 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node02 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node0 = taffy
+ .new_with_children(
+ taffy::style::Style { display: taffy::style::Display::Flex, ..Default::default() },
+ &[node00, node01, node02],
+ )
+ .unwrap();
+ let node = taffy
+ .new_with_children(
+ taffy::style::Style {
+ display: taffy::style::Display::Grid,
+ grid_template_rows: vec![points(40f32)],
+ grid_template_columns: vec![min_content()],
+ ..Default::default()
+ },
+ &[node0],
+ )
+ .unwrap();
+ taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+}
diff --git a/benches/generated/mod.rs b/benches/generated/mod.rs
index f887159a4..b5e58dc88 100644
--- a/benches/generated/mod.rs
+++ b/benches/generated/mod.rs
@@ -359,6 +359,10 @@ mod grid_max_content_single_item_margin_fixed;
#[cfg(feature = "experimental_grid")]
mod grid_max_content_single_item_margin_percent;
#[cfg(feature = "experimental_grid")]
+mod grid_min_content_flex_column;
+#[cfg(feature = "experimental_grid")]
+mod grid_min_content_flex_row;
+#[cfg(feature = "experimental_grid")]
mod grid_min_content_flex_single_item;
#[cfg(feature = "experimental_grid")]
mod grid_min_content_flex_single_item_margin_auto;
@@ -839,6 +843,10 @@ fn benchmark(c: &mut Criterion) {
#[cfg(feature = "experimental_grid")]
grid_max_content_single_item_margin_percent::compute();
#[cfg(feature = "experimental_grid")]
+ grid_min_content_flex_column::compute();
+ #[cfg(feature = "experimental_grid")]
+ grid_min_content_flex_row::compute();
+ #[cfg(feature = "experimental_grid")]
grid_min_content_flex_single_item::compute();
#[cfg(feature = "experimental_grid")]
grid_min_content_flex_single_item_margin_auto::compute();
diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs
index 6eb546859..52b04ee3c 100644
--- a/src/compute/flexbox.rs
+++ b/src/compute/flexbox.rs
@@ -9,7 +9,7 @@ use crate::geometry::{Point, Rect, Size};
use crate::layout::{Layout, RunMode, SizingMode};
use crate::math::MaybeMath;
use crate::node::Node;
-use crate::prelude::TaffyMaxContent;
+use crate::prelude::{TaffyMaxContent, TaffyMinContent};
use crate::resolve::{MaybeResolve, ResolveOrZero};
use crate::style::{
AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, Display, FlexWrap, JustifyContent,
@@ -36,6 +36,10 @@ struct FlexItem {
/// The cross-alignment of this item
align_self: AlignSelf,
+ /// The minimum size of the item. This differs from min_size above because it also
+ /// takes into account content based automatic minimum sizes
+ resolved_minimum_size: Size<f32>,
+
/// The final offset of this item
position: Rect<Option<f32>>,
/// The margin of this item
@@ -202,6 +206,15 @@ fn compute_preliminary(
NODE_LOGGER.log("determine_flex_base_size");
determine_flex_base_size(tree, known_dimensions, &constants, available_space, &mut flex_items);
+ #[cfg(feature = "debug")]
+ for item in flex_items.iter() {
+ NODE_LOGGER.labelled_log("item.flex_basis", item.flex_basis);
+ NODE_LOGGER.labelled_log("item.inner_flex_basis", item.inner_flex_basis);
+ NODE_LOGGER.labelled_debug_log("item.hypothetical_outer_size", item.hypothetical_outer_size);
+ NODE_LOGGER.labelled_debug_log("item.hypothetical_inner_size", item.hypothetical_inner_size);
+ NODE_LOGGER.labelled_debug_log("item.resolved_minimum_size", item.resolved_minimum_size);
+ }
+
// TODO: Add step 4 according to spec: https://www.w3.org/TR/css-flexbox-1/#algo-main-container
// 9.3. Main Size Determination
@@ -431,6 +444,7 @@ fn generate_anonymous_flex_items(tree: &impl LayoutTree, node: Node, constants:
violation: 0.0,
frozen: false,
+ resolved_minimum_size: Size::zero(),
hypothetical_inner_size: Size::zero(),
hypothetical_outer_size: Size::zero(),
target_size: Size::zero(),
@@ -565,13 +579,8 @@ fn determine_flex_base_size(
let child_known_dimensions = {
let mut ckd = child.size;
- if child.align_self == AlignSelf::Stretch {
- if constants.is_column && ckd.width.is_none() {
- ckd.width = available_space.width.into_option();
- }
- if constants.is_row && ckd.height.is_none() {
- ckd.height = available_space.height.into_option();
- }
+ if child.align_self == AlignSelf::Stretch && ckd.cross(constants.dir).is_none() {
+ ckd.set_cross(constants.dir, available_space.cross(constants.dir).into_option());
}
ckd
};
@@ -579,13 +588,12 @@ fn determine_flex_base_size(
child.flex_basis = compute_node_layout(
tree,
child.node,
- child_known_dimensions.maybe_min(child.max_size),
+ child_known_dimensions,
available_space,
RunMode::ComputeSize,
SizingMode::ContentSize,
)
- .main(constants.dir)
- .maybe_min(child.max_size.main(constants.dir));
+ .main(constants.dir);
}
// The hypothetical main size is the item’s flex base size clamped according to its
@@ -595,26 +603,36 @@ fn determine_flex_base_size(
child.inner_flex_basis =
child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
- // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
- // The following logic was developed not from the spec but by trail and error looking into how
- // webkit handled various scenarios. Can probably be solved better by passing in
- // min-content max-content constraints from the top
- let min_main = compute_node_layout(
+ let child_known_dimensions = {
+ let mut ckd = Size::NONE;
+ if child.align_self == AlignSelf::Stretch && ckd.cross(constants.dir).is_none() {
+ ckd.set_cross(constants.dir, available_space.cross(constants.dir).into_option());
+ }
+ ckd
+ };
+
+ let min_content_size = compute_node_layout(
tree,
child.node,
- Size::NONE,
- available_space,
+ child_known_dimensions, // Should possibly also be Size::NONE
+ Size::MIN_CONTENT,
RunMode::ComputeSize,
SizingMode::ContentSize,
- )
- .main(constants.dir)
- .maybe_clamp(child.min_size.main(constants.dir), child.size.main(constants.dir))
- .into();
+ );
- child
- .hypothetical_inner_size
- .set_main(constants.dir, child.flex_basis.maybe_clamp(min_main, child.max_size.main(constants.dir)));
+ // 4.5. Automatic Minimum Size of Flex Items
+ // https://www.w3.org/TR/css-flexbox-1/#min-size-auto
+ let specified = child.size.maybe_min(child.max_size);
+ child.resolved_minimum_size = child.min_size.unwrap_or(min_content_size.maybe_min(specified));
+ let hypothetical_inner_min_main = min_content_size
+ .main(constants.dir)
+ .maybe_clamp(child.resolved_minimum_size.main(constants.dir).into(), child.size.main(constants.dir))
+ .into();
+ child.hypothetical_inner_size.set_main(
+ constants.dir,
+ child.flex_basis.maybe_clamp(hypothetical_inner_min_main, child.max_size.main(constants.dir)),
+ );
child.hypothetical_outer_size.set_main(
constants.dir,
child.hypothetical_inner_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
@@ -770,7 +788,7 @@ fn resolve_flexible_lengths(
.iter()
.map(|child| {
child.margin.main_axis_sum(constants.dir)
- + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
+ + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
})
.sum::<f32>();
@@ -798,7 +816,7 @@ fn resolve_flexible_lengths(
.iter()
.map(|child| {
child.margin.main_axis_sum(constants.dir)
- + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
+ + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
})
.sum::<f32>();
@@ -870,29 +888,9 @@ fn resolve_flexible_lengths(
// If the item’s target main size was made larger by this, it’s a min violation.
let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
- // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
- // The following logic was developed not from the spec but by trial and error looking into how
- // webkit handled various scenarios. Can probably be solved better by passing in
- // min-content max-content constraints from the top. Need to figure out correct thing to do here as
- // just piling on more conditionals.
- let min_main = if constants.is_row && !tree.needs_measure(child.node) {
- compute_node_layout(
- tree,
- child.node,
- Size::NONE,
- available_space,
- RunMode::ComputeSize,
- SizingMode::ContentSize,
- )
- .width
- .maybe_clamp(child.min_size.width, child.size.width)
- .into()
- } else {
- child.min_size.main(constants.dir)
- };
-
+ let resolved_min_main: Option<f32> = child.resolved_minimum_size.main(constants.dir).into();
let max_main = child.max_size.main(constants.dir);
- let clamped = child.target_size.main(constants.dir).maybe_clamp(min_main, max_main).max(0.0);
+ let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
child.violation = clamped - child.target_size.main(constants.dir);
child.target_size.set_main(constants.dir, clamped);
child.outer_target_size.set_main(
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
index 8758f4927..157615ea4 100644
--- a/src/compute/mod.rs
+++ b/src/compute/mod.rs
@@ -7,6 +7,7 @@ pub(crate) mod leaf;
#[cfg(feature = "experimental_grid")]
pub(crate) mod grid;
+use crate::data::CACHE_SIZE;
use crate::error::TaffyError;
use crate::geometry::{Point, Size};
use crate::layout::{Cache, Layout, RunMode, SizingMode};
@@ -130,7 +131,7 @@ fn compute_node_layout(
};
// Cache result
- let cache_slot = (known_dimensions.width.is_some() as usize) + (known_dimensions.height.is_some() as usize * 2);
+ let cache_slot = compute_cache_slot(known_dimensions, available_space);
*tree.cache_mut(node, cache_slot) =
Some(Cache { known_dimensions, available_space, run_mode: cache_run_mode, cached_size: computed_size });
@@ -142,6 +143,50 @@ fn compute_node_layout(
computed_size
}
+/// Return the cache slot to cache the current computed result in
+///
+/// ## Caching Strategy
+///
+/// We need multiple cache slots, because a node's size is often queried by it's parent multiple times in the course of the layout
+/// process, and we don't want later results to clobber earlier ones.
+///
+/// The two variables that we care about when determining cache slot are:
+///
+/// - How many "known_dimensions" are set. In the worst case, a node may be called first with neither dimensions known known_dimensions,
+/// then with one dimension known (either width of height - which doesn't matter for our purposes here), and then with both dimensions
+/// known.
+/// - Whether unknown dimensions are being sized under a min-content or a max-content available space constraint (definite available space
+/// shares a cache slot with max-content because a node will generally be sized under one or the other but not both).
+///
+/// ## Cache slots:
+///
+/// - Slot 0: Both known_dimensions were set
+/// - Slot 1: 1 of 2 known_dimensions were set and the other dimension was either a MaxContent or Definite available space constraint
+/// - Slot 2: 1 of 2 known_dimensions were set and the other dimension was a MinContent constraint
+/// - Slot 3: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
+/// - Slot 4: Neither known_dimensions were set and we are sizing under a MinContent constraint
+#[inline]
+fn compute_cache_slot(known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>) -> usize {
+ let has_known_width = known_dimensions.width.is_some();
+ let has_known_height = known_dimensions.height.is_some();
+
+ // Slot 0: Both known_dimensions were set
+ if has_known_width && has_known_height {
+ return 0;
+ }
+
+ // Slot 1: 1 of 2 known_dimensions were set and the other dimension was either a MaxContent or Definite available space constraint
+ // Slot 2: 1 of 2 known_dimensions were set and the other dimension was a MinContent constraint
+ if has_known_width || has_known_height {
+ let other_dim_available_space = if has_known_width { available_space.height } else { available_space.width };
+ return 1 + (other_dim_available_space == AvailableSpace::MinContent) as usize;
+ }
+
+ // Slot 3: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
+ // Slot 4: Neither known_dimensions were set and we are sizing under a MinContent constraint
+ 3 + (available_space.width == AvailableSpace::MinContent) as usize
+}
+
/// Try to get the computation result from the cache.
#[inline]
fn compute_from_cache(
@@ -152,7 +197,7 @@ fn compute_from_cache(
run_mode: RunMode,
sizing_mode: SizingMode,
) -> Option<Size<f32>> {
- for idx in 0..4 {
+ for idx in 0..CACHE_SIZE {
let entry = tree.cache_mut(node, idx);
#[cfg(feature = "debug")]
NODE_LOGGER.labelled_debug_log("cache_entry", &entry);
@@ -162,24 +207,20 @@ fn compute_from_cache(
return None;
}
- // if known_dimensions.width == entry.known_dimensions.width
- // && known_dimensions.height == entry.known_dimensions.height
- if (known_dimensions.width == entry.known_dimensions.width || known_dimensions.width == Some(entry.cached_size.width))
- && (known_dimensions.height == entry.known_dimensions.height || known_dimensions.height == Some(entry.cached_size.height))
- // && entry.available_space.width.is_roughly_equal(available_space.width)
- // && entry.available_space.height.is_roughly_equal(available_space.height)
- && (
- known_dimensions.width.is_some()
- || entry.available_space.width.is_roughly_equal(available_space.width)
- || (sizing_mode == SizingMode::ContentSize && available_space.width.is_definite() && available_space.width.unwrap() >= entry.cached_size.width)
- )
- && (
- known_dimensions.height.is_some()
- || entry.available_space.height.is_roughly_equal(available_space.height)
- || (sizing_mode == SizingMode::ContentSize && available_space.height.is_definite() && available_space.height.unwrap() >= entry.cached_size.height)
- )
- // && (entry.available_space.width.is_roughly_equal(available_space.width) || (available_space.width.is_definite() && available_space.width.unwrap() >= entry.cached_size.width))
- // && (entry.available_space.height.is_roughly_equal(available_space.height) || (available_space.height.is_definite() && available_space.height.unwrap() >= entry.cached_size.height))
+ if (known_dimensions.width == entry.known_dimensions.width
+ || known_dimensions.width == Some(entry.cached_size.width))
+ && (known_dimensions.height == entry.known_dimensions.height
+ || known_dimensions.height == Some(entry.cached_size.height))
+ && (known_dimensions.width.is_some()
+ || entry.available_space.width.is_roughly_equal(available_space.width)
+ || (sizing_mode == SizingMode::ContentSize
+ && available_space.width.is_definite()
+ && available_space.width.unwrap() >= entry.cached_size.width))
+ && (known_dimensions.height.is_some()
+ || entry.available_space.height.is_roughly_equal(available_space.height)
+ || (sizing_mode == SizingMode::ContentSize
+ && available_space.height.is_definite()
+ && available_space.height.unwrap() >= entry.cached_size.height))
{
return Some(entry.cached_size);
}
diff --git a/src/data.rs b/src/data.rs
index 871bb5e73..f09d3cb1b 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -5,6 +5,9 @@
use crate::layout::{Cache, Layout};
use crate::style::Style;
+/// The number of cache entries for each node in the tree
+pub(crate) const CACHE_SIZE: usize = 5;
+
/// Layout information for a given [`Node`](crate::node::Node)
///
/// Stored in a [`Taffy`].
@@ -18,14 +21,14 @@ pub(crate) struct NodeData {
pub(crate) needs_measure: bool,
/// The primary cached results of the layout computation
- pub(crate) size_cache: [Option<Cache>; 4],
+ pub(crate) size_cache: [Option<Cache>; CACHE_SIZE],
}
impl NodeData {
/// Create the data for a new node
#[must_use]
pub const fn new(style: Style) -> Self {
- Self { style, size_cache: [None; 4], layout: Layout::new(), needs_measure: false }
+ Self { style, size_cache: [None; CACHE_SIZE], layout: Layout::new(), needs_measure: false }
}
/// Marks a node and all of its parents (recursively) as dirty
@@ -33,6 +36,6 @@ impl NodeData {
/// This clears any cached data and signals that the data must be recomputed.
#[inline]
pub fn mark_dirty(&mut self) {
- self.size_cache = [None; 4];
+ self.size_cache = [None; CACHE_SIZE];
}
}
diff --git a/test_fixtures/grid_min_content_flex_column.html b/test_fixtures/grid_min_content_flex_column.html
new file mode 100644
index 000000000..975977e19
--- /dev/null
+++ b/test_fixtures/grid_min_content_flex_column.html
@@ -0,0 +1,21 @@
+<!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: grid; grid-template-columns: min-content;grid-template-rows: 40px">
+ <div style="display: flex;flex-direction: column">
+ <div>HH​HH</div>
+ <div>HH​HH</div>
+ <div>HH​HH</div>
+ </div>
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/test_fixtures/grid_min_content_flex_row.html b/test_fixtures/grid_min_content_flex_row.html
new file mode 100644
index 000000000..c4b8249f1
--- /dev/null
+++ b/test_fixtures/grid_min_content_flex_row.html
@@ -0,0 +1,21 @@
+<!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: grid; grid-template-columns: min-content;grid-template-rows: 40px">
+ <div style="display: flex;flex-direction: row">
+ <div>HH​HH</div>
+ <div>HH​HH</div>
+ <div>HH​HH</div>
+ </div>
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/generated/grid_min_content_flex_column.rs b/tests/generated/grid_min_content_flex_column.rs
new file mode 100644
index 000000000..412744369
--- /dev/null
+++ b/tests/generated/grid_min_content_flex_column.rs
@@ -0,0 +1,84 @@
+#[test]
+fn grid_min_content_flex_column() {
+ use slotmap::Key;
+ #[allow(unused_imports)]
+ use taffy::{layout::Layout, prelude::*};
+ let mut taffy = taffy::Taffy::new();
+ let node00 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node01 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node02 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node0 = taffy
+ .new_with_children(
+ taffy::style::Style {
+ display: taffy::style::Display::Flex,
+ flex_direction: taffy::style::FlexDirection::Column,
+ ..Default::default()
+ },
+ &[node00, node01, node02],
+ )
+ .unwrap();
+ let node = taffy
+ .new_with_children(
+ taffy::style::Style {
+ display: taffy::style::Display::Grid,
+ grid_template_rows: vec![points(40f32)],
+ grid_template_columns: vec![min_content()],
+ ..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, 20f32, "width of node {:?}. Expected {}. Actual {}", node.data(), 20f32, size.width);
+ assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node.data(), 40f32, 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, 20f32, "width of node {:?}. Expected {}. Actual {}", node0.data(), 20f32, size.width);
+ assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node0.data(), 40f32, 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);
+ let Layout { size, location, .. } = taffy.layout(node00).unwrap();
+ assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node00.data(), 20f32, size.width);
+ assert_eq!(size.height, 20f32, "height of node {:?}. Expected {}. Actual {}", node00.data(), 20f32, size.height);
+ assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node00.data(), 0f32, location.x);
+ assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node00.data(), 0f32, location.y);
+ let Layout { size, location, .. } = taffy.layout(node01).unwrap();
+ assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, size.width);
+ assert_eq!(size.height, 20f32, "height of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, size.height);
+ assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node01.data(), 0f32, location.x);
+ assert_eq!(location.y, 20f32, "y of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, location.y);
+ let Layout { size, location, .. } = taffy.layout(node02).unwrap();
+ assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node02.data(), 20f32, size.width);
+ assert_eq!(size.height, 20f32, "height of node {:?}. Expected {}. Actual {}", node02.data(), 20f32, size.height);
+ assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node02.data(), 0f32, location.x);
+ assert_eq!(location.y, 40f32, "y of node {:?}. Expected {}. Actual {}", node02.data(), 40f32, location.y);
+}
diff --git a/tests/generated/grid_min_content_flex_row.rs b/tests/generated/grid_min_content_flex_row.rs
new file mode 100644
index 000000000..084d485a7
--- /dev/null
+++ b/tests/generated/grid_min_content_flex_row.rs
@@ -0,0 +1,80 @@
+#[test]
+fn grid_min_content_flex_row() {
+ use slotmap::Key;
+ #[allow(unused_imports)]
+ use taffy::{layout::Layout, prelude::*};
+ let mut taffy = taffy::Taffy::new();
+ let node00 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node01 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node02 = taffy
+ .new_leaf_with_measure(
+ taffy::style::Style { ..Default::default() },
+ taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
+ const TEXT: &str = "HH\u{200b}HH";
+ super::measure_standard_text(known_dimensions, available_space, TEXT)
+ }),
+ )
+ .unwrap();
+ let node0 = taffy
+ .new_with_children(
+ taffy::style::Style { display: taffy::style::Display::Flex, ..Default::default() },
+ &[node00, node01, node02],
+ )
+ .unwrap();
+ let node = taffy
+ .new_with_children(
+ taffy::style::Style {
+ display: taffy::style::Display::Grid,
+ grid_template_rows: vec![points(40f32)],
+ grid_template_columns: vec![min_content()],
+ ..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, 60f32, "width of node {:?}. Expected {}. Actual {}", node.data(), 60f32, size.width);
+ assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node.data(), 40f32, 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, 60f32, "width of node {:?}. Expected {}. Actual {}", node0.data(), 60f32, size.width);
+ assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node0.data(), 40f32, 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);
+ let Layout { size, location, .. } = taffy.layout(node00).unwrap();
+ assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node00.data(), 20f32, size.width);
+ assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node00.data(), 40f32, size.height);
+ assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node00.data(), 0f32, location.x);
+ assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node00.data(), 0f32, location.y);
+ let Layout { size, location, .. } = taffy.layout(node01).unwrap();
+ assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, size.width);
+ assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node01.data(), 40f32, size.height);
+ assert_eq!(location.x, 20f32, "x of node {:?}. Expected {}. Actual {}", node01.data(), 20f32, location.x);
+ assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node01.data(), 0f32, location.y);
+ let Layout { size, location, .. } = taffy.layout(node02).unwrap();
+ assert_eq!(size.width, 20f32, "width of node {:?}. Expected {}. Actual {}", node02.data(), 20f32, size.width);
+ assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node02.data(), 40f32, size.height);
+ assert_eq!(location.x, 40f32, "x of node {:?}. Expected {}. Actual {}", node02.data(), 40f32, location.x);
+ assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node02.data(), 0f32, location.y);
+}
diff --git a/tests/generated/mod.rs b/tests/generated/mod.rs
index 1e21b06bb..93f2a398d 100644
--- a/tests/generated/mod.rs
+++ b/tests/generated/mod.rs
@@ -358,6 +358,10 @@ mod grid_max_content_single_item_margin_fixed;
#[cfg(feature = "experimental_grid")]
mod grid_max_content_single_item_margin_percent;
#[cfg(feature = "experimental_grid")]
+mod grid_min_content_flex_column;
+#[cfg(feature = "experimental_grid")]
+mod grid_min_content_flex_row;
+#[cfg(feature = "experimental_grid")]
mod grid_min_content_flex_single_item;
#[cfg(feature = "experimental_grid")]
mod grid_min_content_flex_single_item_margin_auto;
diff --git a/tests/measure.rs b/tests/measure.rs
index 99fc7b6cb..9b65ae7a3 100644
--- a/tests/measure.rs
+++ b/tests/measure.rs
@@ -68,10 +68,11 @@ mod measure {
taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
+ // Parent
assert_eq!(taffy.layout(node).unwrap().size.width, 50.0);
assert_eq!(taffy.layout(node).unwrap().size.height, 100.0);
-
- assert_eq!(taffy.layout(child).unwrap().size.width, 50.0);
+ // Child
+ assert_eq!(taffy.layout(child).unwrap().size.width, 100.0);
assert_eq!(taffy.layout(child).unwrap().size.height, 100.0);
}
@@ -105,10 +106,14 @@ mod measure {
.unwrap();
taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
+ assert_eq!(taffy.layout(node).unwrap().location.x, 0.0);
+ assert_eq!(taffy.layout(node).unwrap().location.y, 0.0);
assert_eq!(taffy.layout(node).unwrap().size.width, 50.0);
assert_eq!(taffy.layout(node).unwrap().size.height, 120.0);
- assert_eq!(taffy.layout(child).unwrap().size.width, 30.0);
+ assert_eq!(taffy.layout(child).unwrap().location.x, 10.0);
+ assert_eq!(taffy.layout(child).unwrap().location.y, 10.0);
+ assert_eq!(taffy.layout(child).unwrap().size.width, 100.0);
assert_eq!(taffy.layout(child).unwrap().size.height, 100.0);
}
@@ -175,7 +180,7 @@ mod measure {
taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
- assert_eq!(taffy.layout(child1).unwrap().size.width, 50.0);
+ assert_eq!(taffy.layout(child1).unwrap().size.width, 100.0);
assert_eq!(taffy.layout(child1).unwrap().size.height, 50.0);
}
@@ -253,8 +258,8 @@ mod measure {
taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
- assert_eq!(taffy.layout(child1).unwrap().size.width, 50.0);
- assert_eq!(taffy.layout(child1).unwrap().size.height, 100.0);
+ assert_eq!(taffy.layout(child1).unwrap().size.width, 100.0);
+ assert_eq!(taffy.layout(child1).unwrap().size.height, 200.0);
}
#[test]
@@ -444,16 +449,16 @@ mod measure {
#[test]
fn only_measure_once() {
- use std::sync::atomic;
+ use std::sync::atomic::{AtomicU32, Ordering};
let mut taffy = Taffy::new();
- static NUM_MEASURES: atomic::AtomicU32 = atomic::AtomicU32::new(0);
+ static NUM_MEASURES: AtomicU32 = AtomicU32::new(0);
let grandchild = taffy
.new_leaf_with_measure(
Style { ..Default::default() },
MeasureFunc::Raw(|known_dimensions, _available_space| {
- NUM_MEASURES.fetch_add(1, atomic::Ordering::SeqCst);
+ NUM_MEASURES.fetch_add(1, Ordering::SeqCst);
Size {
width: known_dimensions.width.unwrap_or(50.0),
height: known_dimensions.height.unwrap_or(50.0),
@@ -467,6 +472,6 @@ mod measure {
let node = taffy.new_with_children(Style { ..Default::default() }, &[child]).unwrap();
taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
- assert_eq!(NUM_MEASURES.load(atomic::Ordering::SeqCst), 1);
+ assert_eq!(NUM_MEASURES.load(Ordering::SeqCst), 2);
}
}