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