Skip to main content

taffy/compute/grid/types/
grid_item.rs

1//! Contains GridItem used to represent a single grid item during layout
2use super::GridTrack;
3use crate::compute::grid::OriginZeroLine;
4use crate::geometry::AbstractAxis;
5use crate::geometry::{Line, Point, Rect, Size};
6use crate::style::{AlignItems, AlignSelf, AvailableSpace, Dimension, LengthPercentageAuto, Overflow};
7use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId, SizingMode};
8use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
9use crate::{BoxSizing, GridItemStyle, LengthPercentage};
10use core::ops::Range;
11
12/// Represents a single grid item
13#[derive(Debug)]
14pub(in super::super) struct GridItem {
15    /// The id of the node that this item represents
16    pub node: NodeId,
17
18    /// The order of the item in the children array
19    ///
20    /// We sort the list of grid items during track sizing. This field allows us to sort back the original order
21    /// for final positioning
22    pub source_order: u16,
23
24    /// The item's definite row-start and row-end, as resolved by the placement algorithm
25    /// (in origin-zero coordinates)
26    pub row: Line<OriginZeroLine>,
27    /// The items definite column-start and column-end, as resolved by the placement algorithm
28    /// (in origin-zero coordinates)
29    pub column: Line<OriginZeroLine>,
30
31    /// Is it a compressible replaced element?
32    /// https://drafts.csswg.org/css-sizing-3/#min-content-zero
33    pub is_compressible_replaced: bool,
34    /// The item's overflow style
35    pub overflow: Point<Overflow>,
36    /// The item's box_sizing style
37    pub box_sizing: BoxSizing,
38    /// The item's size style
39    pub size: Size<Dimension>,
40    /// The item's min_size style
41    pub min_size: Size<Dimension>,
42    /// The item's max_size style
43    pub max_size: Size<Dimension>,
44    /// The item's aspect_ratio style
45    pub aspect_ratio: Option<f32>,
46    /// The item's padding style
47    pub padding: Rect<LengthPercentage>,
48    /// The item's border style
49    pub border: Rect<LengthPercentage>,
50    /// The item's margin style
51    pub margin: Rect<LengthPercentageAuto>,
52    /// The item's align_self property, or the parent's align_items property is not set
53    pub align_self: AlignSelf,
54    /// The item's justify_self property, or the parent's justify_items property is not set
55    pub justify_self: AlignSelf,
56    /// The items first baseline (horizontal)
57    pub baseline: Option<f32>,
58    /// Shim for baseline alignment that acts like an extra top margin
59    /// TODO: Support last baseline and vertical text baselines
60    pub baseline_shim: f32,
61
62    /// The item's definite row-start and row-end (same as `row` field, except in a different coordinate system)
63    /// (as indexes into the Vec<GridTrack> stored in a grid's AbstractAxisTracks)
64    pub row_indexes: Line<u16>,
65    /// The items definite column-start and column-end (same as `column` field, except in a different coordinate system)
66    /// (as indexes into the Vec<GridTrack> stored in a grid's AbstractAxisTracks)
67    pub column_indexes: Line<u16>,
68
69    /// Whether the item crosses a flexible row
70    pub crosses_flexible_row: bool,
71    /// Whether the item crosses a flexible column
72    pub crosses_flexible_column: bool,
73    /// Whether the item crosses a intrinsic row
74    pub crosses_intrinsic_row: bool,
75    /// Whether the item crosses a intrinsic column
76    pub crosses_intrinsic_column: bool,
77
78    // Caches for intrinsic size computation. These caches are only valid for a single run of the track-sizing algorithm.
79    /// Cache for the known_dimensions input to intrinsic sizing computation
80    pub grid_area_size_cache: Option<Size<Option<f32>>>,
81    /// Cache for the min-content size
82    pub min_content_contribution_cache: Size<Option<f32>>,
83    /// Cache for the minimum contribution
84    pub minimum_contribution_cache: Size<Option<f32>>,
85    /// Cache for the max-content size
86    pub max_content_contribution_cache: Size<Option<f32>>,
87
88    /// Final y position. Used to compute baseline alignment for the container.
89    pub y_position: f32,
90    /// Final height. Used to compute baseline alignment for the container.
91    pub height: f32,
92}
93
94impl GridItem {
95    /// Create a new item given a concrete placement in both axes
96    pub fn new_with_placement_style_and_order<S: GridItemStyle>(
97        node: NodeId,
98        col_span: Line<OriginZeroLine>,
99        row_span: Line<OriginZeroLine>,
100        style: S,
101        parent_align_items: AlignItems,
102        parent_justify_items: AlignItems,
103        source_order: u16,
104    ) -> Self {
105        GridItem {
106            node,
107            source_order,
108            row: row_span,
109            column: col_span,
110            is_compressible_replaced: style.is_compressible_replaced(),
111            overflow: style.overflow(),
112            box_sizing: style.box_sizing(),
113            size: style.size(),
114            min_size: style.min_size(),
115            max_size: style.max_size(),
116            aspect_ratio: style.aspect_ratio(),
117            padding: style.padding(),
118            border: style.border(),
119            margin: style.margin(),
120            align_self: style.align_self().unwrap_or(parent_align_items),
121            justify_self: style.justify_self().unwrap_or(parent_justify_items),
122            baseline: None,
123            baseline_shim: 0.0,
124            row_indexes: Line { start: 0, end: 0 }, // Properly initialised later
125            column_indexes: Line { start: 0, end: 0 }, // Properly initialised later
126            crosses_flexible_row: false,            // Properly initialised later
127            crosses_flexible_column: false,         // Properly initialised later
128            crosses_intrinsic_row: false,           // Properly initialised later
129            crosses_intrinsic_column: false,        // Properly initialised later
130            grid_area_size_cache: None,
131            min_content_contribution_cache: Size::NONE,
132            max_content_contribution_cache: Size::NONE,
133            minimum_contribution_cache: Size::NONE,
134            y_position: 0.0,
135            height: 0.0,
136        }
137    }
138
139    /// This item's placement in the specified axis in OriginZero coordinates
140    pub fn placement(&self, axis: AbstractAxis) -> Line<OriginZeroLine> {
141        match axis {
142            AbstractAxis::Block => self.row,
143            AbstractAxis::Inline => self.column,
144        }
145    }
146
147    /// This item's placement in the specified axis as GridTrackVec indices
148    pub fn placement_indexes(&self, axis: AbstractAxis) -> Line<u16> {
149        match axis {
150            AbstractAxis::Block => self.row_indexes,
151            AbstractAxis::Inline => self.column_indexes,
152        }
153    }
154
155    /// Returns a range which can be used as an index into the GridTrackVec in the specified axis
156    /// which will produce a sub-slice of covering all the tracks and lines that this item spans
157    /// excluding the lines that bound it.
158    pub fn track_range_excluding_lines(&self, axis: AbstractAxis) -> Range<usize> {
159        let indexes = self.placement_indexes(axis);
160        (indexes.start as usize + 1)..(indexes.end as usize)
161    }
162
163    /// Returns the number of tracks that this item spans in the specified axis
164    pub fn span(&self, axis: AbstractAxis) -> u16 {
165        match axis {
166            AbstractAxis::Block => self.row.span(),
167            AbstractAxis::Inline => self.column.span(),
168        }
169    }
170
171    /// Returns the pre-computed value indicating whether the grid item crosses a flexible track in
172    /// the specified axis
173    pub fn crosses_flexible_track(&self, axis: AbstractAxis) -> bool {
174        match axis {
175            AbstractAxis::Inline => self.crosses_flexible_column,
176            AbstractAxis::Block => self.crosses_flexible_row,
177        }
178    }
179
180    /// Returns the pre-computed value indicating whether the grid item crosses an intrinsic track in
181    /// the specified axis
182    pub fn crosses_intrinsic_track(&self, axis: AbstractAxis) -> bool {
183        match axis {
184            AbstractAxis::Inline => self.crosses_intrinsic_column,
185            AbstractAxis::Block => self.crosses_intrinsic_row,
186        }
187    }
188
189    /// For an item spanning multiple tracks, the upper limit used to calculate its limited min-/max-content contribution is the
190    /// sum of the fixed max track sizing functions of any tracks it spans, and is applied if it only spans such tracks.
191    pub fn spanned_track_limit(
192        &mut self,
193        axis: AbstractAxis,
194        axis_tracks: &[GridTrack],
195        axis_parent_size: Option<f32>,
196        resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
197    ) -> Option<f32> {
198        let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
199        let tracks_all_fixed = spanned_tracks.iter().all(|track| {
200            track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).is_some()
201        });
202        if tracks_all_fixed {
203            let limit: f32 = spanned_tracks
204                .iter()
205                .map(|track| {
206                    track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).unwrap()
207                })
208                .sum();
209            Some(limit)
210        } else {
211            None
212        }
213    }
214
215    /// Similar to the spanned_track_limit, but excludes FitContent arguments from the limit.
216    /// Used to clamp the automatic minimum contributions of an item
217    pub fn spanned_fixed_track_limit(
218        &mut self,
219        axis: AbstractAxis,
220        axis_tracks: &[GridTrack],
221        axis_parent_size: Option<f32>,
222        resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
223    ) -> Option<f32> {
224        let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
225        let tracks_all_fixed = spanned_tracks.iter().all(|track| {
226            track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).is_some()
227        });
228        if tracks_all_fixed {
229            let limit: f32 = spanned_tracks
230                .iter()
231                .map(|track| {
232                    track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).unwrap()
233                })
234                .sum();
235            Some(limit)
236        } else {
237            None
238        }
239    }
240
241    /// Compute the known_dimensions to be passed to the child sizing functions
242    /// The key thing that is being done here is applying stretch alignment, which is necessary to
243    /// allow percentage sizes further down the tree to resolve properly in some cases
244    fn known_dimensions(
245        &self,
246        tree: &mut impl LayoutPartialTree,
247        grid_area_size: Size<Option<f32>>,
248    ) -> Size<Option<f32>> {
249        let margins = self.margins_axis_sums_with_baseline_shims(grid_area_size.width, tree);
250
251        let aspect_ratio = self.aspect_ratio;
252        // CSS resolves percentage padding and border against the inline size of the containing
253        // block. For a grid item under intrinsic measurement, that inline-size basis is the grid
254        // area's width when it is definite.
255        // Spec:
256        // https://www.w3.org/TR/css-grid-1/#item-margins
257        // https://www.w3.org/TR/CSS22/box.html#padding-properties
258        let padding = self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
259        let border = self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
260        let padding_border_size = (padding + border).sum_axes();
261        let box_sizing_adjustment =
262            if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
263        let inherent_size = self
264            .size
265            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
266            .maybe_apply_aspect_ratio(aspect_ratio)
267            .maybe_add(box_sizing_adjustment);
268        let min_size = self
269            .min_size
270            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
271            .maybe_apply_aspect_ratio(aspect_ratio)
272            .maybe_add(box_sizing_adjustment);
273        let max_size = self
274            .max_size
275            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
276            .maybe_apply_aspect_ratio(aspect_ratio)
277            .maybe_add(box_sizing_adjustment);
278
279        let grid_area_minus_item_margins_size = grid_area_size.maybe_sub(margins);
280
281        // If node is absolutely positioned and width is not set explicitly, then deduce it
282        // from left, right and container_content_box if both are set.
283        let width = inherent_size.width.or_else(|| {
284            // Apply width based on stretch alignment if:
285            //  - Alignment style is "stretch"
286            //  - The node is not absolutely positioned
287            //  - The node does not have auto margins in this axis.
288            if !self.margin.left.is_auto() && !self.margin.right.is_auto() && self.justify_self == AlignSelf::STRETCH {
289                return grid_area_minus_item_margins_size.width;
290            }
291
292            None
293        });
294        // Reapply aspect ratio after stretch and absolute position width adjustments
295        let Size { width, height } =
296            Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
297
298        let height = height.or_else(|| {
299            // Apply height based on stretch alignment if:
300            //  - Alignment style is "stretch"
301            //  - The node is not absolutely positioned
302            //  - The node does not have auto margins in this axis.
303            if !self.margin.top.is_auto() && !self.margin.bottom.is_auto() && self.align_self == AlignSelf::STRETCH {
304                return grid_area_minus_item_margins_size.height;
305            }
306
307            None
308        });
309        // Reapply aspect ratio after stretch and absolute position height adjustments
310        let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
311
312        // Clamp size by min and max width/height
313        let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
314
315        Size { width, height }
316    }
317
318    /// Returns the grid area's size in the specified axis when every spanned track has a definite fixed size.
319    ///
320    /// During intrinsic sizing, percentages on grid items resolve against the size of the grid area,
321    /// not the grid container. If the spanned tracks in an axis are not all definite yet, the grid
322    /// area is still indefinite in that axis and percentage-dependent values must stay unresolved here.
323    ///
324    /// Spec:
325    /// https://www.w3.org/TR/css-grid-1/#grid-item-sizing
326    /// https://www.w3.org/TR/css-grid-1/#algo-overview
327    ///
328    /// Compute the available_space to be passed to the child sizing functions
329    /// These are estimates based on either the max track sizing function or the provisional base size in the opposite
330    /// axis to the one currently being sized.
331    /// https://www.w3.org/TR/css-grid-1/#algo-overview
332    pub fn grid_area_size(
333        &self,
334        axis: AbstractAxis,
335        axis_tracks: &[GridTrack],
336        other_axis_tracks: &[GridTrack],
337        available_space: Size<Option<f32>>,
338        get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
339        resolve_calc_value: &impl Fn(*const (), f32) -> f32,
340    ) -> Size<Option<f32>> {
341        let mut size = Size::NONE;
342        size.set(
343            axis,
344            axis_tracks[self.track_range_excluding_lines(axis)]
345                .iter()
346                .map(|track| {
347                    let min_size = track
348                        .min_track_sizing_function
349                        .definite_value(available_space.get(axis), resolve_calc_value)?;
350                    let max_size = track
351                        .max_track_sizing_function
352                        .definite_value(available_space.get(axis), resolve_calc_value)?;
353
354                    if min_size == max_size {
355                        Some(track.base_size)
356                    } else {
357                        None
358                    }
359                })
360                .sum::<Option<f32>>(),
361        );
362
363        size.set(
364            axis.other(),
365            other_axis_tracks[self.track_range_excluding_lines(axis.other())]
366                .iter()
367                .map(|track| {
368                    get_track_size_estimate(track, available_space.get(axis.other()))
369                        .map(|size| size + track.content_alignment_adjustment)
370                })
371                .sum::<Option<f32>>(),
372        );
373
374        size
375    }
376
377    /// Retrieve the available_space from the cache or compute them using the passed parameters
378    pub fn grid_area_size_cached(
379        &mut self,
380        axis: AbstractAxis,
381        axis_tracks: &[GridTrack],
382        other_axis_tracks: &[GridTrack],
383        available_space: Size<Option<f32>>,
384        get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
385        resolve_calc_value: &impl Fn(*const (), f32) -> f32,
386    ) -> Size<Option<f32>> {
387        self.grid_area_size_cache.unwrap_or_else(|| {
388            let grid_area_size = self.grid_area_size(
389                axis,
390                axis_tracks,
391                other_axis_tracks,
392                available_space,
393                get_track_size_estimate,
394                resolve_calc_value,
395            );
396            self.grid_area_size_cache = Some(grid_area_size);
397            grid_area_size
398        })
399    }
400
401    /// Compute the item's resolved margins for size contributions. Horizontal percentage margins always resolve
402    /// to zero if the container size is indefinite as otherwise this would introduce a cyclic dependency.
403    #[inline(always)]
404    pub fn margins_axis_sums_with_baseline_shims(
405        &self,
406        inner_node_width: Option<f32>,
407        tree: &impl LayoutPartialTree,
408    ) -> Size<f32> {
409        Rect {
410            left: self.margin.left.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
411            right: self.margin.right.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
412            top: self.margin.top.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis))
413                + self.baseline_shim,
414            bottom: self.margin.bottom.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis)),
415        }
416        .sum_axes()
417    }
418
419    /// Compute the item's min content contribution from the provided parameters
420    pub fn min_content_contribution(
421        &self,
422        axis: AbstractAxis,
423        tree: &mut impl LayoutPartialTree,
424        grid_area_size: Size<Option<f32>>,
425        available_space: Size<Option<f32>>,
426    ) -> f32 {
427        let known_dimensions = self.known_dimensions(tree, grid_area_size);
428        // The child sees the grid area as its containing block during intrinsic measurement, so
429        // percentage box properties resolve against the grid area when that size is definite.
430        // Spec:
431        // https://www.w3.org/TR/css-grid-1/#grid-item-sizing
432        // https://www.w3.org/TR/css-grid-1/#algo-overview
433        tree.measure_child_size(
434            self.node,
435            known_dimensions,
436            grid_area_size,
437            available_space.map(|opt| match opt {
438                Some(size) => AvailableSpace::Definite(size),
439                None => AvailableSpace::MinContent,
440            }),
441            SizingMode::InherentSize,
442            axis.as_abs_naive(),
443            Line::FALSE,
444        )
445    }
446
447    /// Retrieve the item's min content contribution from the cache or compute it using the provided parameters
448    #[inline(always)]
449    pub fn min_content_contribution_cached(
450        &mut self,
451        axis: AbstractAxis,
452        tree: &mut impl LayoutPartialTree,
453        grid_area_size: Size<Option<f32>>,
454        available_space: Size<Option<f32>>,
455    ) -> f32 {
456        self.min_content_contribution_cache.get(axis).unwrap_or_else(|| {
457            let size = self.min_content_contribution(axis, tree, grid_area_size, available_space);
458            self.min_content_contribution_cache.set(axis, Some(size));
459            size
460        })
461    }
462
463    /// Compute the item's max content contribution from the provided parameters
464    pub fn max_content_contribution(
465        &self,
466        axis: AbstractAxis,
467        tree: &mut impl LayoutPartialTree,
468        grid_area_size: Size<Option<f32>>,
469        available_space: Size<Option<f32>>,
470    ) -> f32 {
471        let known_dimensions = self.known_dimensions(tree, grid_area_size);
472        // See the min-content path above. Max-content measurement uses the same containing-block
473        // basis so percentage-dependent item geometry is measured from the grid area rather than
474        // from the container.
475        tree.measure_child_size(
476            self.node,
477            known_dimensions,
478            grid_area_size,
479            available_space.map(|opt| match opt {
480                Some(size) => AvailableSpace::Definite(size),
481                None => AvailableSpace::MaxContent,
482            }),
483            SizingMode::InherentSize,
484            axis.as_abs_naive(),
485            Line::FALSE,
486        )
487    }
488
489    /// Retrieve the item's max content contribution from the cache or compute it using the provided parameters
490    #[inline(always)]
491    pub fn max_content_contribution_cached(
492        &mut self,
493        axis: AbstractAxis,
494        tree: &mut impl LayoutPartialTree,
495        grid_area_size: Size<Option<f32>>,
496        available_space: Size<Option<f32>>,
497    ) -> f32 {
498        self.max_content_contribution_cache.get(axis).unwrap_or_else(|| {
499            let size = self.max_content_contribution(axis, tree, grid_area_size, available_space);
500            self.max_content_contribution_cache.set(axis, Some(size));
501            size
502        })
503    }
504
505    /// The minimum contribution of an item is the smallest outer size it can have.
506    /// Specifically:
507    ///   - If the item’s computed preferred size behaves as auto or depends on the size of its containing block in the relevant axis:
508    ///     Its minimum contribution is the outer size that would result from assuming the item’s used minimum size as its preferred size;
509    ///   - Else the item’s minimum contribution is its min-content contribution.
510    ///
511    /// Because the minimum contribution often depends on the size of the item’s content, it is considered a type of intrinsic size contribution.
512    /// See: https://www.w3.org/TR/css-grid-1/#min-size-auto
513    pub fn minimum_contribution(
514        &mut self,
515        tree: &mut impl LayoutPartialTree,
516        axis: AbstractAxis,
517        axis_tracks: &[GridTrack],
518        grid_area_size: Size<Option<f32>>,
519        inner_node_size: Size<Option<f32>>,
520    ) -> f32 {
521        let padding = self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
522        let border = self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
523        let padding_border_size = (padding + border).sum_axes();
524        let box_sizing_adjustment =
525            if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
526        let size = self
527            .size
528            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
529            .maybe_apply_aspect_ratio(self.aspect_ratio)
530            .maybe_add(box_sizing_adjustment)
531            .get(axis)
532            .or_else(|| {
533                self.min_size
534                    .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
535                    .maybe_apply_aspect_ratio(self.aspect_ratio)
536                    .maybe_add(box_sizing_adjustment)
537                    .get(axis)
538            })
539            .or_else(|| self.overflow.get(axis).maybe_into_automatic_min_size())
540            .unwrap_or_else(|| {
541                // Automatic minimum size. See https://www.w3.org/TR/css-grid-1/#min-size-auto
542
543                // To provide a more reasonable default minimum size for grid items, the used value of its automatic minimum size
544                // in a given axis is the content-based minimum size if all of the following are true:
545                let item_axis_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
546
547                // it is not a scroll container
548                // TODO: support overflow property
549
550                // it spans at least one track in that axis whose min track sizing function is auto
551                let spans_auto_min_track = axis_tracks
552                    .iter()
553                    // TODO: should this be 'behaves as auto' rather than just literal auto?
554                    .any(|track| track.min_track_sizing_function.is_auto());
555
556                // if it spans more than one track in that axis, none of those tracks are flexible
557                let only_span_one_track = item_axis_tracks.len() == 1;
558                let spans_a_flexible_track = axis_tracks.iter().any(|track| track.max_track_sizing_function.is_fr());
559
560                let use_content_based_minimum =
561                    spans_auto_min_track && (only_span_one_track || !spans_a_flexible_track);
562
563                // Otherwise, the automatic minimum size is zero, as usual.
564                if use_content_based_minimum {
565                    let mut minimum_contribution =
566                        self.min_content_contribution_cached(axis, tree, grid_area_size, grid_area_size);
567
568                    // If the item is a compressible replaced element, and has a definite preferred size or maximum size in the
569                    // relevant axis, the size suggestion is capped by those sizes; for this purpose, any indefinite percentages
570                    // in these sizes are resolved against zero (and considered definite).
571                    if self.is_compressible_replaced {
572                        let size = self.size.get(axis).maybe_resolve(Some(0.0), |val, basis| tree.calc(val, basis));
573                        let max_size =
574                            self.max_size.get(axis).maybe_resolve(Some(0.0), |val, basis| tree.calc(val, basis));
575                        minimum_contribution = minimum_contribution.maybe_min(size).maybe_min(max_size);
576                    }
577
578                    minimum_contribution
579                } else {
580                    0.0
581                }
582            });
583
584        // In all cases, the size suggestion is additionally clamped by the maximum size in the affected axis, if it’s definite.
585        // Note: The argument to fit-content() does not clamp the content-based minimum size in the same way as a fixed max track
586        // sizing function.
587        let limit = self.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis), &|val, basis| {
588            tree.resolve_calc_value(val, basis)
589        });
590        size.maybe_min(limit)
591    }
592
593    /// Retrieve the item's minimum contribution from the cache or compute it using the provided parameters
594    #[inline(always)]
595    pub fn minimum_contribution_cached(
596        &mut self,
597        tree: &mut impl LayoutPartialTree,
598        axis: AbstractAxis,
599        axis_tracks: &[GridTrack],
600        grid_area_size: Size<Option<f32>>,
601        inner_node_size: Size<Option<f32>>,
602    ) -> f32 {
603        self.minimum_contribution_cache.get(axis).unwrap_or_else(|| {
604            let size = self.minimum_contribution(tree, axis, axis_tracks, grid_area_size, inner_node_size);
605            self.minimum_contribution_cache.set(axis, Some(size));
606            size
607        })
608    }
609}