• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Contains GridItem used to represent a single grid item during layout
2 use super::GridTrack;
3 use crate::compute::grid::OriginZeroLine;
4 use crate::geometry::AbstractAxis;
5 use crate::geometry::{Line, Point, Rect, Size};
6 use crate::style::{
7     AlignItems, AlignSelf, AvailableSpace, Dimension, LengthPercentageAuto, MaxTrackSizingFunction,
8     MinTrackSizingFunction, Overflow,
9 };
10 use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId, SizingMode};
11 use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
12 use crate::{BoxSizing, GridItemStyle, LengthPercentage};
13 use core::ops::Range;
14 
15 /// Represents a single grid item
16 #[derive(Debug)]
17 pub(in super::super) struct GridItem {
18     /// The id of the node that this item represents
19     pub node: NodeId,
20 
21     /// The order of the item in the children array
22     ///
23     /// We sort the list of grid items during track sizing. This field allows us to sort back the original order
24     /// for final positioning
25     pub source_order: u16,
26 
27     /// The item's definite row-start and row-end, as resolved by the placement algorithm
28     /// (in origin-zero coordinates)
29     pub row: Line<OriginZeroLine>,
30     /// The items definite column-start and column-end, as resolved by the placement algorithm
31     /// (in origin-zero coordinates)
32     pub column: Line<OriginZeroLine>,
33 
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 available_space_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 
94 impl GridItem {
95     /// Create a new item given a concrete placement in both axes
new_with_placement_style_and_order<S: GridItemStyle>( node: NodeId, col_span: Line<OriginZeroLine>, row_span: Line<OriginZeroLine>, style: S, parent_align_items: AlignItems, parent_justify_items: AlignItems, source_order: u16, ) -> Self96     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             overflow: style.overflow(),
111             box_sizing: style.box_sizing(),
112             size: style.size(),
113             min_size: style.min_size(),
114             max_size: style.max_size(),
115             aspect_ratio: style.aspect_ratio(),
116             padding: style.padding(),
117             border: style.border(),
118             margin: style.margin(),
119             align_self: style.align_self().unwrap_or(parent_align_items),
120             justify_self: style.justify_self().unwrap_or(parent_justify_items),
121             baseline: None,
122             baseline_shim: 0.0,
123             row_indexes: Line { start: 0, end: 0 }, // Properly initialised later
124             column_indexes: Line { start: 0, end: 0 }, // Properly initialised later
125             crosses_flexible_row: false,            // Properly initialised later
126             crosses_flexible_column: false,         // Properly initialised later
127             crosses_intrinsic_row: false,           // Properly initialised later
128             crosses_intrinsic_column: false,        // Properly initialised later
129             available_space_cache: None,
130             min_content_contribution_cache: Size::NONE,
131             max_content_contribution_cache: Size::NONE,
132             minimum_contribution_cache: Size::NONE,
133             y_position: 0.0,
134             height: 0.0,
135         }
136     }
137 
138     /// This item's placement in the specified axis in OriginZero coordinates
placement(&self, axis: AbstractAxis) -> Line<OriginZeroLine>139     pub fn placement(&self, axis: AbstractAxis) -> Line<OriginZeroLine> {
140         match axis {
141             AbstractAxis::Block => self.row,
142             AbstractAxis::Inline => self.column,
143         }
144     }
145 
146     /// This item's placement in the specified axis as GridTrackVec indices
placement_indexes(&self, axis: AbstractAxis) -> Line<u16>147     pub fn placement_indexes(&self, axis: AbstractAxis) -> Line<u16> {
148         match axis {
149             AbstractAxis::Block => self.row_indexes,
150             AbstractAxis::Inline => self.column_indexes,
151         }
152     }
153 
154     /// Returns a range which can be used as an index into the GridTrackVec in the specified axis
155     /// which will produce a sub-slice of covering all the tracks and lines that this item spans
156     /// excluding the lines that bound it.
track_range_excluding_lines(&self, axis: AbstractAxis) -> Range<usize>157     pub fn track_range_excluding_lines(&self, axis: AbstractAxis) -> Range<usize> {
158         let indexes = self.placement_indexes(axis);
159         (indexes.start as usize + 1)..(indexes.end as usize)
160     }
161 
162     /// Returns the number of tracks that this item spans in the specified axis
span(&self, axis: AbstractAxis) -> u16163     pub fn span(&self, axis: AbstractAxis) -> u16 {
164         match axis {
165             AbstractAxis::Block => self.row.span(),
166             AbstractAxis::Inline => self.column.span(),
167         }
168     }
169 
170     /// Returns the pre-computed value indicating whether the grid item crosses a flexible track in
171     /// the specified axis
crosses_flexible_track(&self, axis: AbstractAxis) -> bool172     pub fn crosses_flexible_track(&self, axis: AbstractAxis) -> bool {
173         match axis {
174             AbstractAxis::Inline => self.crosses_flexible_column,
175             AbstractAxis::Block => self.crosses_flexible_row,
176         }
177     }
178 
179     /// Returns the pre-computed value indicating whether the grid item crosses an intrinsic track in
180     /// the specified axis
crosses_intrinsic_track(&self, axis: AbstractAxis) -> bool181     pub fn crosses_intrinsic_track(&self, axis: AbstractAxis) -> bool {
182         match axis {
183             AbstractAxis::Inline => self.crosses_intrinsic_column,
184             AbstractAxis::Block => self.crosses_intrinsic_row,
185         }
186     }
187 
188     /// For an item spanning multiple tracks, the upper limit used to calculate its limited min-/max-content contribution is the
189     /// sum of the fixed max track sizing functions of any tracks it spans, and is applied if it only spans such tracks.
spanned_track_limit( &mut self, axis: AbstractAxis, axis_tracks: &[GridTrack], axis_parent_size: Option<f32>, ) -> Option<f32>190     pub fn spanned_track_limit(
191         &mut self,
192         axis: AbstractAxis,
193         axis_tracks: &[GridTrack],
194         axis_parent_size: Option<f32>,
195     ) -> Option<f32> {
196         let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
197         let tracks_all_fixed = spanned_tracks
198             .iter()
199             .all(|track| track.max_track_sizing_function.definite_limit(axis_parent_size).is_some());
200         if tracks_all_fixed {
201             let limit: f32 = spanned_tracks
202                 .iter()
203                 .map(|track| track.max_track_sizing_function.definite_limit(axis_parent_size).unwrap())
204                 .sum();
205             Some(limit)
206         } else {
207             None
208         }
209     }
210 
211     /// Similar to the spanned_track_limit, but excludes FitContent arguments from the limit.
212     /// Used to clamp the automatic minimum contributions of an item
spanned_fixed_track_limit( &mut self, axis: AbstractAxis, axis_tracks: &[GridTrack], axis_parent_size: Option<f32>, ) -> Option<f32>213     pub fn spanned_fixed_track_limit(
214         &mut self,
215         axis: AbstractAxis,
216         axis_tracks: &[GridTrack],
217         axis_parent_size: Option<f32>,
218     ) -> Option<f32> {
219         let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
220         let tracks_all_fixed = spanned_tracks
221             .iter()
222             .all(|track| track.max_track_sizing_function.definite_value(axis_parent_size).is_some());
223         if tracks_all_fixed {
224             let limit: f32 = spanned_tracks
225                 .iter()
226                 .map(|track| track.max_track_sizing_function.definite_value(axis_parent_size).unwrap())
227                 .sum();
228             Some(limit)
229         } else {
230             None
231         }
232     }
233 
234     /// Compute the known_dimensions to be passed to the child sizing functions
235     /// The key thing that is being done here is applying stretch alignment, which is necessary to
236     /// allow percentage sizes further down the tree to resolve properly in some cases
known_dimensions( &self, inner_node_size: Size<Option<f32>>, grid_area_size: Size<Option<f32>>, ) -> Size<Option<f32>>237     fn known_dimensions(
238         &self,
239         inner_node_size: Size<Option<f32>>,
240         grid_area_size: Size<Option<f32>>,
241     ) -> Size<Option<f32>> {
242         let margins = self.margins_axis_sums_with_baseline_shims(inner_node_size.width);
243 
244         let aspect_ratio = self.aspect_ratio;
245         let padding = self.padding.resolve_or_zero(grid_area_size);
246         let border = self.border.resolve_or_zero(grid_area_size);
247         let padding_border_size = (padding + border).sum_axes();
248         let box_sizing_adjustment =
249             if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
250         let inherent_size = self
251             .size
252             .maybe_resolve(grid_area_size)
253             .maybe_apply_aspect_ratio(aspect_ratio)
254             .maybe_add(box_sizing_adjustment);
255         let min_size = self
256             .min_size
257             .maybe_resolve(grid_area_size)
258             .maybe_apply_aspect_ratio(aspect_ratio)
259             .maybe_add(box_sizing_adjustment);
260         let max_size = self
261             .max_size
262             .maybe_resolve(grid_area_size)
263             .maybe_apply_aspect_ratio(aspect_ratio)
264             .maybe_add(box_sizing_adjustment);
265 
266         let grid_area_minus_item_margins_size = grid_area_size.maybe_sub(margins);
267 
268         // If node is absolutely positioned and width is not set explicitly, then deduce it
269         // from left, right and container_content_box if both are set.
270         let width = inherent_size.width.or_else(|| {
271             // Apply width based on stretch alignment if:
272             //  - Alignment style is "stretch"
273             //  - The node is not absolutely positioned
274             //  - The node does not have auto margins in this axis.
275             if self.margin.left != LengthPercentageAuto::Auto
276                 && self.margin.right != LengthPercentageAuto::Auto
277                 && self.justify_self == AlignSelf::Stretch
278             {
279                 return grid_area_minus_item_margins_size.width;
280             }
281 
282             None
283         });
284         // Reapply aspect ratio after stretch and absolute position width adjustments
285         let Size { width, height } =
286             Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
287 
288         let height = height.or_else(|| {
289             // Apply height based on stretch alignment if:
290             //  - Alignment style is "stretch"
291             //  - The node is not absolutely positioned
292             //  - The node does not have auto margins in this axis.
293             if self.margin.top != LengthPercentageAuto::Auto
294                 && self.margin.bottom != LengthPercentageAuto::Auto
295                 && self.align_self == AlignSelf::Stretch
296             {
297                 return grid_area_minus_item_margins_size.height;
298             }
299 
300             None
301         });
302         // Reapply aspect ratio after stretch and absolute position height adjustments
303         let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
304 
305         // Clamp size by min and max width/height
306         let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
307 
308         Size { width, height }
309     }
310 
311     /// Compute the available_space to be passed to the child sizing functions
312     /// These are estimates based on either the max track sizing function or the provisional base size in the opposite
313     /// axis to the one currently being sized.
314     /// https://www.w3.org/TR/css-grid-1/#algo-overview
available_space( &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>>315     pub fn available_space(
316         &self,
317         axis: AbstractAxis,
318         other_axis_tracks: &[GridTrack],
319         other_axis_available_space: Option<f32>,
320         get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
321     ) -> Size<Option<f32>> {
322         let item_other_axis_size: Option<f32> = {
323             other_axis_tracks[self.track_range_excluding_lines(axis.other())]
324                 .iter()
325                 .map(|track| {
326                     get_track_size_estimate(track, other_axis_available_space)
327                         .map(|size| size + track.content_alignment_adjustment)
328                 })
329                 .sum::<Option<f32>>()
330         };
331 
332         let mut size = Size::NONE;
333         size.set(axis.other(), item_other_axis_size);
334         size
335     }
336 
337     /// Retrieve the available_space from the cache or compute them using the passed parameters
available_space_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>>338     pub fn available_space_cached(
339         &mut self,
340         axis: AbstractAxis,
341         other_axis_tracks: &[GridTrack],
342         other_axis_available_space: Option<f32>,
343         get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
344     ) -> Size<Option<f32>> {
345         self.available_space_cache.unwrap_or_else(|| {
346             let available_spaces =
347                 self.available_space(axis, other_axis_tracks, other_axis_available_space, get_track_size_estimate);
348             self.available_space_cache = Some(available_spaces);
349             available_spaces
350         })
351     }
352 
353     /// Compute the item's resolved margins for size contributions. Horizontal percentage margins always resolve
354     /// to zero if the container size is indefinite as otherwise this would introduce a cyclic dependency.
355     #[inline(always)]
margins_axis_sums_with_baseline_shims(&self, inner_node_width: Option<f32>) -> Size<f32>356     pub fn margins_axis_sums_with_baseline_shims(&self, inner_node_width: Option<f32>) -> Size<f32> {
357         Rect {
358             left: self.margin.left.resolve_or_zero(Some(0.0)),
359             right: self.margin.right.resolve_or_zero(Some(0.0)),
360             top: self.margin.top.resolve_or_zero(inner_node_width) + self.baseline_shim,
361             bottom: self.margin.bottom.resolve_or_zero(inner_node_width),
362         }
363         .sum_axes()
364     }
365 
366     /// Compute the item's min content contribution from the provided parameters
min_content_contribution( &self, axis: AbstractAxis, tree: &mut impl LayoutPartialTree, available_space: Size<Option<f32>>, inner_node_size: Size<Option<f32>>, ) -> f32367     pub fn min_content_contribution(
368         &self,
369         axis: AbstractAxis,
370         tree: &mut impl LayoutPartialTree,
371         available_space: Size<Option<f32>>,
372         inner_node_size: Size<Option<f32>>,
373     ) -> f32 {
374         let known_dimensions = self.known_dimensions(inner_node_size, available_space);
375         tree.measure_child_size(
376             self.node,
377             known_dimensions,
378             inner_node_size,
379             available_space.map(|opt| match opt {
380                 Some(size) => AvailableSpace::Definite(size),
381                 None => AvailableSpace::MinContent,
382             }),
383             SizingMode::InherentSize,
384             axis.as_abs_naive(),
385             Line::FALSE,
386         )
387     }
388 
389     /// Retrieve the item's min content contribution from the cache or compute it using the provided parameters
390     #[inline(always)]
min_content_contribution_cached( &mut self, axis: AbstractAxis, tree: &mut impl LayoutPartialTree, available_space: Size<Option<f32>>, inner_node_size: Size<Option<f32>>, ) -> f32391     pub fn min_content_contribution_cached(
392         &mut self,
393         axis: AbstractAxis,
394         tree: &mut impl LayoutPartialTree,
395         available_space: Size<Option<f32>>,
396         inner_node_size: Size<Option<f32>>,
397     ) -> f32 {
398         self.min_content_contribution_cache.get(axis).unwrap_or_else(|| {
399             let size = self.min_content_contribution(axis, tree, available_space, inner_node_size);
400             self.min_content_contribution_cache.set(axis, Some(size));
401             size
402         })
403     }
404 
405     /// Compute the item's max content contribution from the provided parameters
max_content_contribution( &self, axis: AbstractAxis, tree: &mut impl LayoutPartialTree, available_space: Size<Option<f32>>, inner_node_size: Size<Option<f32>>, ) -> f32406     pub fn max_content_contribution(
407         &self,
408         axis: AbstractAxis,
409         tree: &mut impl LayoutPartialTree,
410         available_space: Size<Option<f32>>,
411         inner_node_size: Size<Option<f32>>,
412     ) -> f32 {
413         let known_dimensions = self.known_dimensions(inner_node_size, available_space);
414         tree.measure_child_size(
415             self.node,
416             known_dimensions,
417             inner_node_size,
418             available_space.map(|opt| match opt {
419                 Some(size) => AvailableSpace::Definite(size),
420                 None => AvailableSpace::MaxContent,
421             }),
422             SizingMode::InherentSize,
423             axis.as_abs_naive(),
424             Line::FALSE,
425         )
426     }
427 
428     /// Retrieve the item's max content contribution from the cache or compute it using the provided parameters
429     #[inline(always)]
max_content_contribution_cached( &mut self, axis: AbstractAxis, tree: &mut impl LayoutPartialTree, available_space: Size<Option<f32>>, inner_node_size: Size<Option<f32>>, ) -> f32430     pub fn max_content_contribution_cached(
431         &mut self,
432         axis: AbstractAxis,
433         tree: &mut impl LayoutPartialTree,
434         available_space: Size<Option<f32>>,
435         inner_node_size: Size<Option<f32>>,
436     ) -> f32 {
437         self.max_content_contribution_cache.get(axis).unwrap_or_else(|| {
438             let size = self.max_content_contribution(axis, tree, available_space, inner_node_size);
439             self.max_content_contribution_cache.set(axis, Some(size));
440             size
441         })
442     }
443 
444     /// The minimum contribution of an item is the smallest outer size it can have.
445     /// Specifically:
446     ///   - If the item’s computed preferred size behaves as auto or depends on the size of its containing block in the relevant axis:
447     ///     Its minimum contribution is the outer size that would result from assuming the item’s used minimum size as its preferred size;
448     ///   - Else the item’s minimum contribution is its min-content contribution.
449     ///
450     /// Because the minimum contribution often depends on the size of the item’s content, it is considered a type of intrinsic size contribution.
451     /// See: https://www.w3.org/TR/css-grid-1/#min-size-auto
minimum_contribution( &mut self, tree: &mut impl LayoutPartialTree, axis: AbstractAxis, axis_tracks: &[GridTrack], known_dimensions: Size<Option<f32>>, inner_node_size: Size<Option<f32>>, ) -> f32452     pub fn minimum_contribution(
453         &mut self,
454         tree: &mut impl LayoutPartialTree,
455         axis: AbstractAxis,
456         axis_tracks: &[GridTrack],
457         known_dimensions: Size<Option<f32>>,
458         inner_node_size: Size<Option<f32>>,
459     ) -> f32 {
460         let padding = self.padding.resolve_or_zero(inner_node_size);
461         let border = self.border.resolve_or_zero(inner_node_size);
462         let padding_border_size = (padding + border).sum_axes();
463         let box_sizing_adjustment =
464             if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
465         let size = self
466             .size
467             .maybe_resolve(inner_node_size)
468             .maybe_apply_aspect_ratio(self.aspect_ratio)
469             .maybe_add(box_sizing_adjustment)
470             .get(axis)
471             .or_else(|| {
472                 self.min_size
473                     .maybe_resolve(inner_node_size)
474                     .maybe_apply_aspect_ratio(self.aspect_ratio)
475                     .maybe_add(box_sizing_adjustment)
476                     .get(axis)
477             })
478             .or_else(|| self.overflow.get(axis).maybe_into_automatic_min_size())
479             .unwrap_or_else(|| {
480                 // Automatic minimum size. See https://www.w3.org/TR/css-grid-1/#min-size-auto
481 
482                 // To provide a more reasonable default minimum size for grid items, the used value of its automatic minimum size
483                 // in a given axis is the content-based minimum size if all of the following are true:
484                 let item_axis_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
485 
486                 // it is not a scroll container
487                 // TODO: support overflow property
488 
489                 // it spans at least one track in that axis whose min track sizing function is auto
490                 let spans_auto_min_track = axis_tracks
491                     .iter()
492                     // TODO: should this be 'behaves as auto' rather than just literal auto?
493                     .any(|track| track.min_track_sizing_function == MinTrackSizingFunction::Auto);
494 
495                 // if it spans more than one track in that axis, none of those tracks are flexible
496                 let only_span_one_track = item_axis_tracks.len() == 1;
497                 let spans_a_flexible_track = axis_tracks
498                     .iter()
499                     .any(|track| matches!(track.max_track_sizing_function, MaxTrackSizingFunction::Fraction(_)));
500 
501                 let use_content_based_minimum =
502                     spans_auto_min_track && (only_span_one_track || !spans_a_flexible_track);
503 
504                 // Otherwise, the automatic minimum size is zero, as usual.
505                 if use_content_based_minimum {
506                     self.min_content_contribution_cached(axis, tree, known_dimensions, inner_node_size)
507                 } else {
508                     0.0
509                 }
510             });
511 
512         // In all cases, the size suggestion is additionally clamped by the maximum size in the affected axis, if it’s definite.
513         // Note: The argument to fit-content() does not clamp the content-based minimum size in the same way as a fixed max track
514         // sizing function.
515         let limit = self.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis));
516         size.maybe_min(limit)
517     }
518 
519     /// Retrieve the item's minimum contribution from the cache or compute it using the provided parameters
520     #[inline(always)]
minimum_contribution_cached( &mut self, tree: &mut impl LayoutPartialTree, axis: AbstractAxis, axis_tracks: &[GridTrack], known_dimensions: Size<Option<f32>>, inner_node_size: Size<Option<f32>>, ) -> f32521     pub fn minimum_contribution_cached(
522         &mut self,
523         tree: &mut impl LayoutPartialTree,
524         axis: AbstractAxis,
525         axis_tracks: &[GridTrack],
526         known_dimensions: Size<Option<f32>>,
527         inner_node_size: Size<Option<f32>>,
528     ) -> f32 {
529         self.minimum_contribution_cache.get(axis).unwrap_or_else(|| {
530             let size = self.minimum_contribution(tree, axis, axis_tracks, known_dimensions, inner_node_size);
531             self.minimum_contribution_cache.set(axis, Some(size));
532             size
533         })
534     }
535 }
536