tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Definiciones

Grid: selectively rerun track sizing (#341)

* Add crosses_intrinsic_{row,column} properties to GridItem

* Only process items that cross an intrinsic track when resolving intrinsic track sizes

* Only re-run track sizing if min content contributions have changed

* Integrate rerunning track sizing optimisation with caching

* Fix known_dimensions caching

* Also re-run sizing in the case of an indefinitely sized container containing percentage sized tracks

* Add TODOs

* Fix clippy lints

08c16a7c36ce61a1f6b10a792710e39701a2dffc · por Nico Burns · (hace 3 años) · padre 6ce28e153 · ver el árbol en este commit

Delta semántico · 25

src/compute/grid/mod.rs

  • cambió por dentro fn compute pub

src/compute/grid/track_sizing.rs

src/compute/grid/types/grid_item.rs

src/compute/grid/types/grid_track.rs

src/style/grid.rs

5 archivos · +322 −149

diff --git a/src/compute/grid/mod.rs b/src/compute/grid/mod.rs
index 1fc504081..3b114e6cc 100644
--- a/src/compute/grid/mod.rs
+++ b/src/compute/grid/mod.rs
@@ -14,7 +14,9 @@ use alignment::{align_and_position_item, align_tracks};
 use explicit_grid::{compute_explicit_grid_size_in_axis, initialize_grid_tracks};
 use implicit_grid::compute_grid_size_estimate;
 use placement::place_grid_items;
-use track_sizing::{determine_if_item_crosses_flexible_tracks, resolve_item_track_indexes, track_sizing_algorithm};
+use track_sizing::{
+    determine_if_item_crosses_flexible_or_intrinsic_tracks, resolve_item_track_indexes, track_sizing_algorithm,
+};
 use types::{CellOccupancyMatrix, GridTrack};
 
 pub(crate) use types::{GridCoordinate, GridLine, OriginZeroLine};
@@ -175,7 +177,7 @@ pub fn compute(
 
     // For each item, and in each axis, determine whether the item crosses any flexible (fr) tracks
     // Record this as a boolean (per-axis) on each item for later use in the track-sizing algorithm
-    determine_if_item_crosses_flexible_tracks(&mut items, &columns, &rows);
+    determine_if_item_crosses_flexible_or_intrinsic_tracks(&mut items, &columns, &rows);
 
     // Run track sizing algorithm for Inline axis
     track_sizing_algorithm(
@@ -194,6 +196,8 @@ pub fn compute(
     let initial_column_sum = columns.iter().map(|track| track.base_size).sum::<f32>();
     inner_node_size.width = inner_node_size.width.or_else(|| initial_column_sum.into());
 
+    items.iter_mut().for_each(|item| item.known_dimensions_cache = None);
+
     // Run track sizing algorithm for Block axis
     track_sizing_algorithm(
         tree,
@@ -231,35 +235,113 @@ pub fn compute(
         return container_border_box.into();
     }
 
-    // Re-run track sizing algorithm for Inline axis
-    track_sizing_algorithm(
-        tree,
-        AbstractAxis::Inline,
-        min_size.get(AbstractAxis::Inline),
-        max_size.get(AbstractAxis::Inline),
-        style.grid_align_content(AbstractAxis::Block),
-        available_grid_space,
-        inner_node_size,
-        &mut columns,
-        &mut rows,
-        &mut items,
-        |track: &GridTrack, _| Some(track.base_size),
-    );
+    // Column sizing must be re-run (once) if:
+    //   - The grid container's width was initially indefinite and there are any columns with percentage track sizing functions
+    //   - Any grid item crossing an intrinsically sized track's min content contribution width has changed
+    // TODO: Only rerun sizing for tracks that actually require it rather than for all tracks if any need it.
+    let mut rerun_column_sizing;
+
+    let has_percentage_column = columns.iter().any(|track| track.uses_percentage());
+    let parent_width_indefinite = !available_space.width.is_definite();
+    rerun_column_sizing = parent_width_indefinite && has_percentage_column;
+
+    if !rerun_column_sizing {
+        let min_content_contribution_changed = items
+            .iter_mut()
+            .filter(|item| item.crosses_intrinsic_column)
+            .map(|item| {
+                let known_dimensions = item.known_dimensions(
+                    AbstractAxis::Inline,
+                    &rows,
+                    inner_node_size.height,
+                    |track: &GridTrack, _| Some(track.base_size),
+                );
+                let new_min_content_contribution =
+                    item.min_content_contribution(AbstractAxis::Inline, tree, known_dimensions, inner_node_size);
+
+                let has_changed = Some(new_min_content_contribution) != item.min_content_contribution_cache.width;
+
+                item.known_dimensions_cache = Some(known_dimensions);
+                item.min_content_contribution_cache.width = Some(new_min_content_contribution);
+                item.max_content_contribution_cache.width = None;
+                item.minimum_contribution_cache.width = None;
+
+                has_changed
+            })
+            .any(|has_changed| has_changed);
+        rerun_column_sizing = min_content_contribution_changed;
+    }
 
-    // Re-run track sizing algorithm for Block axis
-    track_sizing_algorithm(
-        tree,
-        AbstractAxis::Block,
-        min_size.get(AbstractAxis::Block),
-        max_size.get(AbstractAxis::Block),
-        style.grid_align_content(AbstractAxis::Inline),
-        available_grid_space,
-        inner_node_size,
-        &mut rows,
-        &mut columns,
-        &mut items,
-        |track: &GridTrack, _| Some(track.base_size),
-    );
+    if rerun_column_sizing {
+        // Re-run track sizing algorithm for Inline axis
+        track_sizing_algorithm(
+            tree,
+            AbstractAxis::Inline,
+            min_size.get(AbstractAxis::Inline),
+            max_size.get(AbstractAxis::Inline),
+            style.grid_align_content(AbstractAxis::Block),
+            available_grid_space,
+            inner_node_size,
+            &mut columns,
+            &mut rows,
+            &mut items,
+            |track: &GridTrack, _| Some(track.base_size),
+        );
+
+        // Row sizing must be re-run (once) if:
+        //   - The grid container's height was initially indefinite and there are any rows with percentage track sizing functions
+        //   - Any grid item crossing an intrinsically sized track's min content contribution height has changed
+        // TODO: Only rerun sizing for tracks that actually require it rather than for all tracks if any need it.
+        let mut rerun_row_sizing;
+
+        let has_percentage_row = rows.iter().any(|track| track.uses_percentage());
+        let parent_height_indefinite = !available_space.height.is_definite();
+        rerun_row_sizing = parent_height_indefinite && has_percentage_row;
+
+        if !rerun_row_sizing {
+            let min_content_contribution_changed = items
+                .iter_mut()
+                .filter(|item| item.crosses_intrinsic_column)
+                .map(|item| {
+                    let known_dimensions = item.known_dimensions(
+                        AbstractAxis::Block,
+                        &rows,
+                        inner_node_size.width,
+                        |track: &GridTrack, _| Some(track.base_size),
+                    );
+                    let new_min_content_contribution =
+                        item.min_content_contribution(AbstractAxis::Block, tree, known_dimensions, inner_node_size);
+
+                    let has_changed = Some(new_min_content_contribution) != item.min_content_contribution_cache.height;
+
+                    item.known_dimensions_cache = Some(known_dimensions);
+                    item.min_content_contribution_cache.height = Some(new_min_content_contribution);
+                    item.max_content_contribution_cache.height = None;
+                    item.minimum_contribution_cache.height = None;
+
+                    has_changed
+                })
+                .any(|has_changed| has_changed);
+            rerun_row_sizing = min_content_contribution_changed;
+        }
+
+        if rerun_row_sizing {
+            // Re-run track sizing algorithm for Block axis
+            track_sizing_algorithm(
+                tree,
+                AbstractAxis::Block,
+                min_size.get(AbstractAxis::Block),
+                max_size.get(AbstractAxis::Block),
+                style.grid_align_content(AbstractAxis::Inline),
+                available_grid_space,
+                inner_node_size,
+                &mut rows,
+                &mut columns,
+                &mut items,
+                |track: &GridTrack, _| Some(track.base_size),
+            );
+        }
+    }
 
     // 7. Resolve percentage track base sizes
     // In the case of an indefinitely sized container these resolve to zero during the "Initialise Tracks" step
diff --git a/src/compute/grid/track_sizing.rs b/src/compute/grid/track_sizing.rs
index fc6943ffc..134d50f0b 100644
--- a/src/compute/grid/track_sizing.rs
+++ b/src/compute/grid/track_sizing.rs
@@ -159,7 +159,7 @@ pub(super) fn resolve_item_track_indexes(items: &mut [GridItem], column_counts:
 
 /// Determine (in each axis) whether the item crosses any flexible tracks
 #[inline(always)]
-pub(super) fn determine_if_item_crosses_flexible_tracks(
+pub(super) fn determine_if_item_crosses_flexible_or_intrinsic_tracks(
     items: &mut Vec<GridItem>,
     columns: &[GridTrack],
     rows: &[GridTrack],
@@ -167,8 +167,12 @@ pub(super) fn determine_if_item_crosses_flexible_tracks(
     for item in items {
         item.crosses_flexible_column =
             item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].is_flexible());
+        item.crosses_intrinsic_column =
+            item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].has_intrinsic_sizing_function());
         item.crosses_flexible_row =
             item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].is_flexible());
+        item.crosses_intrinsic_row =
+            item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].has_intrinsic_sizing_function());
     }
 }
 
@@ -199,10 +203,6 @@ pub(super) fn track_sizing_algorithm<Tree: LayoutTree>(
         return;
     }
 
-    // Clear caches
-    // These caches are only valid for a single run of the track sizing algorithm, so ensure that they are clear.
-    items.iter_mut().for_each(|item| item.clear_contribution_caches());
-
     // Pre-computations for 11.5 Resolve Intrinsic Track Sizes
 
     // The track sizing algorithm requires us to iterate through the items in ascendeding order of the number of
@@ -430,8 +430,9 @@ fn resolve_intrinsic_track_sizes(
         fn min_content_contribution(&mut self, item: &mut GridItem) -> f32 {
             let known_dimensions = self.known_dimensions(item);
             let margin_axis_sums = self.margins_axis_sums(item);
-            let contribution = item.min_content_contribution_cached(self.tree, known_dimensions, self.inner_node_size);
-            contribution.get(self.axis) + margin_axis_sums.get(self.axis)
+            let contribution =
+                item.min_content_contribution_cached(self.axis, self.tree, known_dimensions, self.inner_node_size);
+            contribution + margin_axis_sums.get(self.axis)
         }
 
         /// Retrieve the item's max content contribution from the cache or compute it using the provided parameters
@@ -439,8 +440,9 @@ fn resolve_intrinsic_track_sizes(
         fn max_content_contribution(&mut self, item: &mut GridItem) -> f32 {
             let known_dimensions = self.known_dimensions(item);
             let margin_axis_sums = self.margins_axis_sums(item);
-            let contribution = item.max_content_contribution_cached(self.tree, known_dimensions, self.inner_node_size);
-            contribution.get(self.axis) + margin_axis_sums.get(self.axis)
+            let contribution =
+                item.max_content_contribution_cached(self.axis, self.tree, known_dimensions, self.inner_node_size);
+            contribution + margin_axis_sums.get(self.axis)
         }
 
         /// The minimum contribution of an item is the smallest outer size it can have.
@@ -474,7 +476,7 @@ fn resolve_intrinsic_track_sizes(
         // First increase the base size of tracks with an intrinsic min track sizing function
         let has_intrinsic_min_track_sizing_function =
             move |track: &GridTrack| track.min_track_sizing_function.definite_value(axis_inner_node_size).is_none();
-        for item in batch.iter_mut() {
+        for item in batch.iter_mut().filter(|item| item.crosses_intrinsic_track(axis)) {
             // ...by distributing extra space as needed to accommodate these items’ minimum contributions.
             // If the grid container is being sized under a min- or max-content constraint, use the items’ limited min-content contributions
             // in place of their minimum contributions here.
@@ -911,8 +913,8 @@ fn expand_flexible_tracks(
                         let tracks = &axis_tracks[item.track_range_excluding_lines(axis)];
                         // TODO: plumb estimate of other axis size (known_dimensions) in here rather than just passing Size::NONE?
                         let max_content_contribution =
-                            item.max_content_contribution_cached(tree, Size::NONE, inner_node_size);
-                        find_size_of_fr(tracks, max_content_contribution.get(axis))
+                            item.max_content_contribution_cached(axis, tree, Size::NONE, inner_node_size);
+                        find_size_of_fr(tracks, max_content_contribution)
                     })
                     .max_by(|a, b| a.total_cmp(b))
                     .unwrap_or(0.0),
diff --git a/src/compute/grid/types/grid_item.rs b/src/compute/grid/types/grid_item.rs
index f42bb5ada..82d7a9ab9 100644
--- a/src/compute/grid/types/grid_item.rs
+++ b/src/compute/grid/types/grid_item.rs
@@ -45,16 +45,20 @@ pub(in super::super) struct GridItem {
     pub crosses_flexible_row: bool,
     /// Whether the item crosses a flexible column
     pub crosses_flexible_column: bool,
+    /// Whether the item crosses a intrinsic row
+    pub crosses_intrinsic_row: bool,
+    /// Whether the item crosses a intrinsic column
+    pub crosses_intrinsic_column: bool,
 
     // Caches for intrinsic size computation. These caches are only valid for a single run of the track-sizing algorithm.
     /// Cache for the known_dimensions input to intrinsic sizing computation
     pub known_dimensions_cache: Option<Size<Option<f32>>>,
     /// Cache for the min-content size
-    pub min_content_contribution_cache: Option<Size<f32>>,
+    pub min_content_contribution_cache: Size<Option<f32>>,
     /// Cache for the minimum contribution
-    pub minimum_contribution_cache: Option<f32>,
+    pub minimum_contribution_cache: Size<Option<f32>>,
     /// Cache for the max-content size
-    pub max_content_contribution_cache: Option<Size<f32>>,
+    pub max_content_contribution_cache: Size<Option<f32>>,
 }
 
 impl GridItem {
@@ -76,10 +80,12 @@ impl GridItem {
             column_indexes: Line { start: 0, end: 0 }, // Properly initialised later
             crosses_flexible_row: false,            // Properly initialised later
             crosses_flexible_column: false,         // Properly initialised later
+            crosses_intrinsic_row: false,           // Properly initialised later
+            crosses_intrinsic_column: false,        // Properly initialised later
             known_dimensions_cache: None,
-            min_content_contribution_cache: None,
-            max_content_contribution_cache: None,
-            minimum_contribution_cache: None,
+            min_content_contribution_cache: Size::NONE,
+            max_content_contribution_cache: Size::NONE,
+            minimum_contribution_cache: Size::NONE,
         }
     }
 
@@ -124,37 +130,13 @@ impl GridItem {
         }
     }
 
-    /// Compute the known_dimensions to be passed to the child sizing functions
-    /// These are estimates based on either the max track sizing function or the provisional base size in the opposite
-    /// axis to the one currently being sized.
-    /// https://www.w3.org/TR/css-grid-1/#algo-overview
-    pub fn known_dimensions_cached(
-        &mut self,
-        axis: AbstractAxis,
-        other_axis_tracks: &[GridTrack],
-        other_axis_available_space: Option<f32>,
-        get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
-    ) -> Size<Option<f32>> {
-        self.known_dimensions_cache.unwrap_or_else(|| {
-            let item_other_axis_size: Option<f32> = {
-                other_axis_tracks[self.track_range_excluding_lines(axis.other())]
-                    .iter()
-                    .map(|track| {
-                        get_track_size_estimate(track, other_axis_available_space)
-                            .map(|size| size + track.content_alignment_adjustment)
-                    })
-                    .sum::<Option<f32>>()
-            };
-            let known_dimensions = {
-                let mut size = Size::NONE;
-                size.set(axis.other(), item_other_axis_size);
-                size
-            };
-
-            self.known_dimensions_cache = Some(known_dimensions);
-
-            known_dimensions
-        })
+    /// Returns the pre-computed value indicating whether the grid item crosses an intrinsic track in
+    /// the specified axis
+    pub fn crosses_intrinsic_track(&self, axis: AbstractAxis) -> bool {
+        match axis {
+            AbstractAxis::Inline => self.crosses_intrinsic_column,
+            AbstractAxis::Block => self.crosses_intrinsic_row,
+        }
     }
 
     /// For an item spanning multiple tracks, the upper limit used to calculate its limited min-/max-content contribution is the
@@ -180,44 +162,114 @@ impl GridItem {
         }
     }
 
+    /// Compute the known_dimensions to be passed to the child sizing functions
+    /// These are estimates based on either the max track sizing function or the provisional base size in the opposite
+    /// axis to the one currently being sized.
+    /// https://www.w3.org/TR/css-grid-1/#algo-overview
+    pub fn known_dimensions(
+        &self,
+        axis: AbstractAxis,
+        other_axis_tracks: &[GridTrack],
+        other_axis_available_space: Option<f32>,
+        get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
+    ) -> Size<Option<f32>> {
+        let item_other_axis_size: Option<f32> = {
+            other_axis_tracks[self.track_range_excluding_lines(axis.other())]
+                .iter()
+                .map(|track| {
+                    get_track_size_estimate(track, other_axis_available_space)
+                        .map(|size| size + track.content_alignment_adjustment)
+                })
+                .sum::<Option<f32>>()
+        };
+
+        let mut size = Size::NONE;
+        size.set(axis.other(), item_other_axis_size);
+        size
+    }
+
+    /// Retrieve the known_dimensions from the cache or compute them using the passed parameters
+    pub fn known_dimensions_cached(
+        &mut self,
+        axis: AbstractAxis,
+        other_axis_tracks: &[GridTrack],
+        other_axis_available_space: Option<f32>,
+        get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
+    ) -> Size<Option<f32>> {
+        self.known_dimensions_cache.unwrap_or_else(|| {
+            let known_dimensions =
+                self.known_dimensions(axis, other_axis_tracks, other_axis_available_space, get_track_size_estimate);
+            self.known_dimensions_cache = Some(known_dimensions);
+            known_dimensions
+        })
+    }
+
+    /// Compute the item's min content contribution from the provided parameters
+    pub fn min_content_contribution(
+        &self,
+        axis: AbstractAxis,
+        tree: &mut impl LayoutTree,
+        known_dimensions: Size<Option<f32>>,
+        inner_node_size: Size<Option<f32>>,
+    ) -> f32 {
+        GenericAlgorithm::measure_size(
+            tree,
+            self.node,
+            known_dimensions,
+            inner_node_size,
+            Size::MIN_CONTENT,
+            SizingMode::InherentSize,
+        )
+        .get(axis)
+    }
+
     /// Retrieve the item's min content contribution from the cache or compute it using the provided parameters
+    #[inline(always)]
     pub fn min_content_contribution_cached(
         &mut self,
+        axis: AbstractAxis,
         tree: &mut impl LayoutTree,
         known_dimensions: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
-    ) -> Size<f32> {
-        self.min_content_contribution_cache.unwrap_or_else(|| {
-            let size = GenericAlgorithm::measure_size(
-                tree,
-                self.node,
-                known_dimensions,
-                inner_node_size,
-                Size::MIN_CONTENT,
-                SizingMode::InherentSize,
-            );
-            self.min_content_contribution_cache = Some(size);
+    ) -> f32 {
+        self.min_content_contribution_cache.get(axis).unwrap_or_else(|| {
+            let size = self.min_content_contribution(axis, tree, known_dimensions, inner_node_size);
+            self.min_content_contribution_cache.set(axis, Some(size));
             size
         })
     }
 
+    /// Compute the item's max content contribution from the provided parameters
+    pub fn max_content_contribution(
+        &self,
+        axis: AbstractAxis,
+        tree: &mut impl LayoutTree,
+        known_dimensions: Size<Option<f32>>,
+        inner_node_size: Size<Option<f32>>,
+    ) -> f32 {
+        GenericAlgorithm::measure_size(
+            tree,
+            self.node,
+            known_dimensions,
+            inner_node_size,
+            Size::MAX_CONTENT,
+            SizingMode::InherentSize,
+        )
+        .get(axis)
+    }
+
     /// Retrieve the item's max content contribution from the cache or compute it using the provided parameters
+    #[inline(always)]
     pub fn max_content_contribution_cached(
         &mut self,
+        axis: AbstractAxis,
         tree: &mut impl LayoutTree,
         known_dimensions: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
-    ) -> Size<f32> {
-        self.max_content_contribution_cache.unwrap_or_else(|| {
-            let size = GenericAlgorithm::measure_size(
-                tree,
-                self.node,
-                known_dimensions,
-                inner_node_size,
-                Size::MAX_CONTENT,
-                SizingMode::InherentSize,
-            );
-            self.max_content_contribution_cache = Some(size);
+    ) -> f32 {
+        self.max_content_contribution_cache.get(axis).unwrap_or_else(|| {
+            let size = self.max_content_contribution(axis, tree, known_dimensions, inner_node_size);
+            self.max_content_contribution_cache.set(axis, Some(size));
             size
         })
     }
@@ -228,7 +280,7 @@ impl GridItem {
     ///     Its minimum contribution is the outer size that would result from assuming the item’s used minimum size as its preferred size;
     ///   - Else the item’s minimum contribution is its min-content contribution.
     /// Because the minimum contribution often depends on the size of the item’s content, it is considered a type of intrinsic size contribution.
-    pub fn minimum_contribution_cached(
+    pub fn minimum_contribution(
         &mut self,
         tree: &mut impl LayoutTree,
         axis: AbstractAxis,
@@ -236,58 +288,63 @@ impl GridItem {
         known_dimensions: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
     ) -> f32 {
-        self.minimum_contribution_cache.unwrap_or_else(|| {
-            let style = tree.style(self.node);
-            let contribution = style
-                .size
-                .maybe_resolve(inner_node_size)
-                .maybe_apply_aspect_ratio(style.aspect_ratio)
-                .get(axis)
-                .or_else(|| {
-                    style.min_size.maybe_resolve(inner_node_size).maybe_apply_aspect_ratio(style.aspect_ratio).get(axis)
-                })
-                .unwrap_or_else(|| {
-                    // Automatic minimum size. See https://www.w3.org/TR/css-grid-1/#min-size-auto
+        let style = tree.style(self.node);
+        style
+            .size
+            .maybe_resolve(inner_node_size)
+            .maybe_apply_aspect_ratio(style.aspect_ratio)
+            .get(axis)
+            .or_else(|| {
+                style.min_size.maybe_resolve(inner_node_size).maybe_apply_aspect_ratio(style.aspect_ratio).get(axis)
+            })
+            .unwrap_or_else(|| {
+                // Automatic minimum size. See https://www.w3.org/TR/css-grid-1/#min-size-auto
 
-                    // To provide a more reasonable default minimum size for grid items, the used value of its automatic minimum size
-                    // in a given axis is the content-based minimum size if all of the following are true:
-                    let item_axis_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
+                // To provide a more reasonable default minimum size for grid items, the used value of its automatic minimum size
+                // in a given axis is the content-based minimum size if all of the following are true:
+                let item_axis_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
 
-                    // it is not a scroll container
-                    // TODO: support overflow propety
+                // it is not a scroll container
+                // TODO: support overflow propety
 
-                    // it spans at least one track in that axis whose min track sizing function is auto
-                    let spans_auto_min_track = axis_tracks
-                        .iter()
-                        // TODO: should this be 'behaves as auto' rather than just literal auto?
-                        .any(|track| track.min_track_sizing_function == MinTrackSizingFunction::Auto);
+                // it spans at least one track in that axis whose min track sizing function is auto
+                let spans_auto_min_track = axis_tracks
+                    .iter()
+                    // TODO: should this be 'behaves as auto' rather than just literal auto?
+                    .any(|track| track.min_track_sizing_function == MinTrackSizingFunction::Auto);
 
-                    // if it spans more than one track in that axis, none of those tracks are flexible
-                    let only_span_one_track = item_axis_tracks.len() == 1;
-                    let spans_a_flexible_track = axis_tracks
-                        .iter()
-                        .any(|track| matches!(track.max_track_sizing_function, MaxTrackSizingFunction::Flex(_)));
+                // if it spans more than one track in that axis, none of those tracks are flexible
+                let only_span_one_track = item_axis_tracks.len() == 1;
+                let spans_a_flexible_track = axis_tracks
+                    .iter()
+                    .any(|track| matches!(track.max_track_sizing_function, MaxTrackSizingFunction::Flex(_)));
 
-                    let use_content_based_minimum =
-                        spans_auto_min_track && (only_span_one_track || !spans_a_flexible_track);
+                let use_content_based_minimum =
+                    spans_auto_min_track && (only_span_one_track || !spans_a_flexible_track);
 
-                    // Otherwise, the automatic minimum size is zero, as usual.
-                    if use_content_based_minimum {
-                        self.min_content_contribution_cached(tree, known_dimensions, inner_node_size).get(axis)
-                    } else {
-                        0.0
-                    }
-                });
-            self.minimum_contribution_cache = Some(contribution);
-            contribution
-        })
+                // Otherwise, the automatic minimum size is zero, as usual.
+                if use_content_based_minimum {
+                    self.min_content_contribution_cached(axis, tree, known_dimensions, inner_node_size)
+                } else {
+                    0.0
+                }
+            })
     }
 
-    /// Clears the per-track-sizing-alogrithm-run caches
-    pub fn clear_contribution_caches(&mut self) {
-        self.known_dimensions_cache = None;
-        self.min_content_contribution_cache = None;
-        self.max_content_contribution_cache = None;
-        self.minimum_contribution_cache = None;
+    /// Retrieve the item's minimum contribution from the cache or compute it using the provided parameters
+    #[inline(always)]
+    pub fn minimum_contribution_cached(
+        &mut self,
+        tree: &mut impl LayoutTree,
+        axis: AbstractAxis,
+        axis_tracks: &[GridTrack],
+        known_dimensions: Size<Option<f32>>,
+        inner_node_size: Size<Option<f32>>,
+    ) -> f32 {
+        self.minimum_contribution_cache.get(axis).unwrap_or_else(|| {
+            let size = self.minimum_contribution(tree, axis, axis_tracks, known_dimensions, inner_node_size);
+            self.minimum_contribution_cache.set(axis, Some(size));
+            size
+        })
     }
 }
diff --git a/src/compute/grid/types/grid_track.rs b/src/compute/grid/types/grid_track.rs
index 2c57b8e65..99e4e4da5 100644
--- a/src/compute/grid/types/grid_track.rs
+++ b/src/compute/grid/types/grid_track.rs
@@ -105,12 +105,24 @@ impl GridTrack {
         self.max_track_sizing_function = MaxTrackSizingFunction::Fixed(LengthPercentage::Points(0.0));
     }
 
-    #[inline]
+    #[inline(always)]
     /// Returns true if the track is flexible (has a Flex MaxTrackSizingFunction), else false.
     pub fn is_flexible(&self) -> bool {
         matches!(self.max_track_sizing_function, MaxTrackSizingFunction::Flex(_))
     }
 
+    #[inline(always)]
+    /// Returns true if the track is flexible (has a Flex MaxTrackSizingFunction), else false.
+    pub fn uses_percentage(&self) -> bool {
+        self.min_track_sizing_function.uses_percentage() || self.max_track_sizing_function.uses_percentage()
+    }
+
+    #[inline(always)]
+    /// Returns true if the track has an intrinsic min and or max sizing function
+    pub fn has_intrinsic_sizing_function(&self) -> bool {
+        self.min_track_sizing_function.is_intrinsic() || self.max_track_sizing_function.is_intrinsic()
+    }
+
     #[inline]
     /// Returns true if the track is flexible (has a Flex MaxTrackSizingFunction), else false.
     pub fn fit_content_limit(&self, axis_available_grid_space: Option<f32>) -> f32 {
diff --git a/src/style/grid.rs b/src/style/grid.rs
index fa6364698..388a9290d 100644
--- a/src/style/grid.rs
+++ b/src/style/grid.rs
@@ -287,7 +287,7 @@ impl FromFlex for MaxTrackSizingFunction {
 }
 
 impl MaxTrackSizingFunction {
-    /// Returns true if the max track sizing function is `MinContent`, `MaxContent` or `Auto`, else false.
+    /// Returns true if the max track sizing function is `MinContent`, `MaxContent`, `FitContent` or `Auto`, else false.
     #[inline(always)]
     pub fn is_intrinsic(&self) -> bool {
         matches!(self, Self::MinContent | Self::MaxContent | Self::FitContent(_) | Self::Auto)
@@ -346,6 +346,13 @@ impl MaxTrackSizingFunction {
             Fixed(LengthPercentage::Points(_)) | MinContent | MaxContent | FitContent(_) | Auto | Flex(_) => None,
         }
     }
+
+    /// Whether the track sizing functions depends on the size of the parent node
+    #[inline(always)]
+    pub fn uses_percentage(self) -> bool {
+        use MaxTrackSizingFunction::*;
+        matches!(self, Fixed(LengthPercentage::Percent(_)) | FitContent(LengthPercentage::Percent(_)))
+    }
 }
 
 /// Minimum track sizing function
@@ -388,6 +395,12 @@ impl FromPercent for MinTrackSizingFunction {
 }
 
 impl MinTrackSizingFunction {
+    /// Returns true if the min track sizing function is `MinContent`, `MaxContent` or `Auto`, else false.
+    #[inline(always)]
+    pub fn is_intrinsic(&self) -> bool {
+        matches!(self, Self::MinContent | Self::MaxContent | Self::Auto)
+    }
+
     /// Returns fixed point values directly. Attempts to resolve percentage values against
     /// the passed available_space and returns if this results in a concrete value (which it
     /// will if the available_space is `Some`). Otherwise returns `None`.
@@ -411,6 +424,13 @@ impl MinTrackSizingFunction {
             Fixed(LengthPercentage::Points(_)) | MinContent | MaxContent | Auto => None,
         }
     }
+
+    /// Whether the track sizing functions depends on the size of the parent node
+    #[inline(always)]
+    pub fn uses_percentage(self) -> bool {
+        use MinTrackSizingFunction::*;
+        matches!(self, Fixed(LengthPercentage::Percent(_)))
+    }
 }
 
 /// The sizing function for a grid track (row/column) (either auto-track or template track)