1use 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, OofCandidates, SizingMode};
9use crate::util::sys::f32_max;
10use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
11use crate::{AlignItemsKeyword, BoxSizing, GridItemStyle, LengthPercentage};
12use core::ops::Range;
13
14#[derive(Debug, Clone, Copy)]
16pub(in super::super) struct KnownDimensionsCacheEntry {
17 grid_area_size: Size<Option<f32>>,
19 known_dimensions: Size<Option<f32>>,
21}
22
23#[derive(Debug)]
25pub(in super::super) struct GridItem {
26 pub node: NodeId,
28
29 pub source_order: u16,
31
32 pub row: Line<OriginZeroLine>,
35 pub column: Line<OriginZeroLine>,
38
39 pub is_compressible_replaced: bool,
42 pub is_replaced: bool,
44 pub overflow: Point<Overflow>,
46 pub box_sizing: BoxSizing,
48 pub size: Size<Dimension>,
50 pub min_size: Size<LengthPercentageAuto>,
52 pub max_size: Size<LengthPercentageAuto>,
54 pub aspect_ratio: Option<f32>,
56 pub padding: Rect<LengthPercentage>,
58 pub border: Rect<LengthPercentage>,
60 pub margin: Rect<LengthPercentageAuto>,
62 pub align_self: AlignSelf,
64 pub justify_self: AlignSelf,
66 pub baseline: Option<f32>,
69 pub baseline_shim: f32,
72
73 pub row_indexes: Line<u16>,
76 pub column_indexes: Line<u16>,
79
80 pub crosses_flexible_row: bool,
82 pub crosses_flexible_column: bool,
84 pub crosses_intrinsic_row: bool,
86 pub crosses_intrinsic_column: bool,
88
89 pub grid_area_size_cache: Option<Size<Option<f32>>>,
92 pub known_dimensions_cache: Option<KnownDimensionsCacheEntry>,
95 pub min_content_contribution_cache: Size<Option<f32>>,
97 pub minimum_contribution_cache: Size<Option<f32>>,
99 pub max_content_contribution_cache: Size<Option<f32>>,
101
102 pub y_position: f32,
105 pub height: f32,
107
108 pub oof_candidates: OofCandidates,
111}
112
113impl GridItem {
114 pub fn new_with_style_and_order<S: GridItemStyle>(
116 node: NodeId,
117 style: S,
118 parent_align_items: AlignItems,
119 parent_justify_items: AlignItems,
120 source_order: u16,
121 ) -> Self {
122 const UNPLACED: Line<OriginZeroLine> = Line { start: OriginZeroLine(0), end: OriginZeroLine(0) };
123 GridItem {
124 node,
125 source_order,
126 row: UNPLACED,
127 column: UNPLACED,
128 is_compressible_replaced: style.is_compressible_replaced(),
129 is_replaced: style.is_replaced(),
130 overflow: style.overflow(),
131 box_sizing: style.box_sizing(),
132 size: style.size(),
133 min_size: style.min_size(),
134 max_size: style.max_size(),
135 aspect_ratio: style.aspect_ratio(),
136 padding: style.padding(),
137 border: style.border(),
138 margin: style.margin(),
139 align_self: style.align_self().unwrap_or(parent_align_items),
140 justify_self: style.justify_self().unwrap_or(parent_justify_items),
141 baseline: None,
142 baseline_shim: 0.0,
143 row_indexes: Line { start: 0, end: 0 }, column_indexes: Line { start: 0, end: 0 }, crosses_flexible_row: false, crosses_flexible_column: false, crosses_intrinsic_row: false, crosses_intrinsic_column: false, grid_area_size_cache: None,
150 min_content_contribution_cache: Size::NONE,
151 max_content_contribution_cache: Size::NONE,
152 known_dimensions_cache: None,
153 minimum_contribution_cache: Size::NONE,
154 y_position: 0.0,
155 height: 0.0,
156 oof_candidates: OofCandidates::NONE,
157 }
158 }
159
160 #[inline(always)]
162 pub fn has_auto_block_margin(&self) -> bool {
163 self.margin.top.is_auto() || self.margin.bottom.is_auto()
164 }
165
166 #[inline(always)]
171 pub fn has_cyclic_block_size_dependency(&self) -> bool {
172 self.size.height.0.uses_percentage() && (self.crosses_intrinsic_row || self.crosses_flexible_row)
173 }
174
175 #[inline(always)]
179 pub fn participates_in_baseline_alignment(&self) -> bool {
180 self.align_self.keyword == AlignItemsKeyword::Baseline
181 && !self.has_auto_block_margin()
182 && !self.has_cyclic_block_size_dependency()
183 }
184
185 pub fn placement(&self, axis: AbstractAxis) -> Line<OriginZeroLine> {
187 match axis {
188 AbstractAxis::Block => self.row,
189 AbstractAxis::Inline => self.column,
190 }
191 }
192
193 pub fn placement_indexes(&self, axis: AbstractAxis) -> Line<u16> {
195 match axis {
196 AbstractAxis::Block => self.row_indexes,
197 AbstractAxis::Inline => self.column_indexes,
198 }
199 }
200
201 pub fn track_range_excluding_lines(&self, axis: AbstractAxis) -> Range<usize> {
205 let indexes = self.placement_indexes(axis);
206 (indexes.start as usize + 1)..(indexes.end as usize)
207 }
208
209 pub fn spans_track_matching(
211 &self,
212 axis: AbstractAxis,
213 axis_tracks: &[GridTrack],
214 predicate: impl Fn(&GridTrack) -> bool,
215 ) -> bool {
216 axis_tracks[self.track_range_excluding_lines(axis)].iter().any(predicate)
217 }
218
219 pub fn span(&self, axis: AbstractAxis) -> u16 {
221 match axis {
222 AbstractAxis::Block => self.row.span(),
223 AbstractAxis::Inline => self.column.span(),
224 }
225 }
226
227 pub fn crosses_flexible_track(&self, axis: AbstractAxis) -> bool {
230 match axis {
231 AbstractAxis::Inline => self.crosses_flexible_column,
232 AbstractAxis::Block => self.crosses_flexible_row,
233 }
234 }
235
236 pub fn crosses_intrinsic_track(&self, axis: AbstractAxis) -> bool {
239 match axis {
240 AbstractAxis::Inline => self.crosses_intrinsic_column,
241 AbstractAxis::Block => self.crosses_intrinsic_row,
242 }
243 }
244
245 pub fn spanned_track_limit(
248 &mut self,
249 axis: AbstractAxis,
250 axis_tracks: &[GridTrack],
251 axis_parent_size: Option<f32>,
252 resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
253 ) -> Option<f32> {
254 let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
255 let tracks_all_fixed = spanned_tracks.iter().all(|track| {
256 track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).is_some()
257 });
258 if tracks_all_fixed {
259 let limit: f32 = spanned_tracks
260 .iter()
261 .map(|track| {
262 track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).unwrap()
263 })
264 .sum();
265 Some(limit)
266 } else {
267 None
268 }
269 }
270
271 pub fn spanned_fixed_track_limit(
274 &mut self,
275 axis: AbstractAxis,
276 axis_tracks: &[GridTrack],
277 axis_parent_size: Option<f32>,
278 resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
279 ) -> Option<f32> {
280 let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
281 let tracks_all_fixed = spanned_tracks.iter().all(|track| {
282 track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).is_some()
283 });
284 if tracks_all_fixed {
285 let limit: f32 = spanned_tracks
286 .iter()
287 .map(|track| {
288 track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).unwrap()
289 })
290 .sum();
291 Some(limit)
292 } else {
293 None
294 }
295 }
296
297 #[inline(always)]
300 fn known_dimensions_cached(
301 &mut self,
302 tree: &mut impl LayoutPartialTree,
303 grid_area_size: Size<Option<f32>>,
304 ) -> Size<Option<f32>> {
305 if let Some(entry) = self.known_dimensions_cache {
306 if entry.grid_area_size == grid_area_size {
307 return entry.known_dimensions;
308 }
309 }
310 let known_dimensions = self.known_dimensions(tree, grid_area_size);
311 self.known_dimensions_cache = Some(KnownDimensionsCacheEntry { grid_area_size, known_dimensions });
312 known_dimensions
313 }
314
315 #[inline(always)]
317 fn is_stretch_aligned(&self, alignment: AlignSelf) -> bool {
318 match alignment.keyword {
319 AlignItemsKeyword::Stretch => true,
320 AlignItemsKeyword::Normal => !self.is_replaced,
321 _ => false,
322 }
323 }
324
325 fn known_dimensions(
329 &self,
330 tree: &mut impl LayoutPartialTree,
331 grid_area_size: Size<Option<f32>>,
332 ) -> Size<Option<f32>> {
333 let margins = self.margins_axis_sums_with_baseline_shims(grid_area_size.width, tree);
334
335 let aspect_ratio = self.aspect_ratio;
336 let padding = self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
343 let border = self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
344 let padding_border_size = (padding + border).sum_axes();
345 let box_sizing_adjustment =
346 if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
347 let inherent_size = self
348 .size
349 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
350 .maybe_apply_aspect_ratio(aspect_ratio)
351 .maybe_add(box_sizing_adjustment);
352 let min_size = self
353 .min_size
354 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
355 .maybe_apply_aspect_ratio(aspect_ratio)
356 .maybe_add(box_sizing_adjustment)
357 .or(padding_border_size.map(Some))
359 .maybe_max(padding_border_size);
360 let max_size = self
361 .max_size
362 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
363 .maybe_apply_aspect_ratio(aspect_ratio)
364 .maybe_add(box_sizing_adjustment);
365
366 let grid_area_minus_item_margins_size = grid_area_size.maybe_sub(margins).maybe_max(Size::ZERO);
367
368 let width = inherent_size.width.or_else(|| {
371 if self.size.width.is_sizing_keyword() {
374 return match resolve_sizing_keyword(
375 self.size.width,
376 grid_area_minus_item_margins_size.width,
377 grid_area_size.width,
378 |val, basis| tree.calc(val, basis),
379 ) {
380 Some(SizingKeywordResolution::Exact(width)) => Some(width),
381 _ => None,
382 };
383 }
384
385 if !self.margin.left.is_auto() && !self.margin.right.is_auto() && self.is_stretch_aligned(self.justify_self)
393 {
394 return grid_area_minus_item_margins_size.width;
395 }
396
397 None
398 });
399 let Size { width, height } =
401 Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
402
403 let height = height.or_else(|| {
404 if self.size.height.is_sizing_keyword() {
407 return match resolve_sizing_keyword(
408 self.size.height,
409 grid_area_minus_item_margins_size.height,
410 grid_area_size.height,
411 |val, basis| tree.calc(val, basis),
412 ) {
413 Some(SizingKeywordResolution::Exact(height)) => Some(height),
414 _ => None,
415 };
416 }
417
418 if !self.margin.top.is_auto() && !self.margin.bottom.is_auto() && self.is_stretch_aligned(self.align_self) {
423 return grid_area_minus_item_margins_size.height;
424 }
425
426 None
427 });
428 let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
430
431 let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
433
434 Size { width, height }
435 }
436
437 pub fn grid_area_size(
452 &self,
453 axis: AbstractAxis,
454 axis_tracks: &[GridTrack],
455 other_axis_tracks: &[GridTrack],
456 available_space: Size<Option<f32>>,
457 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
458 resolve_calc_value: &impl Fn(*const (), f32) -> f32,
459 ) -> Size<Option<f32>> {
460 let mut size = Size::NONE;
461 size.set(
462 axis,
463 axis_tracks[self.track_range_excluding_lines(axis)]
464 .iter()
465 .map(|track| {
466 let min_size = track
467 .min_track_sizing_function
468 .definite_value(available_space.get(axis), resolve_calc_value)?;
469 let max_size = track
470 .max_track_sizing_function
471 .definite_value(available_space.get(axis), resolve_calc_value)?;
472
473 if min_size == max_size {
474 Some(track.base_size)
475 } else {
476 None
477 }
478 })
479 .sum::<Option<f32>>(),
480 );
481
482 size.set(
483 axis.other(),
484 other_axis_tracks[self.track_range_excluding_lines(axis.other())]
485 .iter()
486 .map(|track| {
487 get_track_size_estimate(track, available_space.get(axis.other()))
488 .map(|size| size + track.content_alignment_adjustment)
489 })
490 .sum::<Option<f32>>(),
491 );
492
493 size
494 }
495
496 pub fn grid_area_size_cached(
498 &mut self,
499 axis: AbstractAxis,
500 axis_tracks: &[GridTrack],
501 other_axis_tracks: &[GridTrack],
502 available_space: Size<Option<f32>>,
503 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
504 resolve_calc_value: &impl Fn(*const (), f32) -> f32,
505 ) -> Size<Option<f32>> {
506 self.grid_area_size_cache.unwrap_or_else(|| {
507 let grid_area_size = self.grid_area_size(
508 axis,
509 axis_tracks,
510 other_axis_tracks,
511 available_space,
512 get_track_size_estimate,
513 resolve_calc_value,
514 );
515 self.grid_area_size_cache = Some(grid_area_size);
516 grid_area_size
517 })
518 }
519
520 #[inline(always)]
523 pub fn margins_axis_sums_with_baseline_shims(
524 &self,
525 inner_node_width: Option<f32>,
526 tree: &impl LayoutPartialTree,
527 ) -> Size<f32> {
528 Rect {
529 left: self.margin.left.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
530 right: self.margin.right.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
531 top: self.margin.top.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis))
532 + self.baseline_shim,
533 bottom: self.margin.bottom.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis)),
534 }
535 .sum_axes()
536 }
537
538 pub fn min_content_contribution(
540 &mut self,
541 axis: AbstractAxis,
542 tree: &mut impl LayoutPartialTree,
543 grid_area_size: Size<Option<f32>>,
544 available_space: Size<Option<f32>>,
545 ) -> f32 {
546 let known_dimensions = self.known_dimensions_cached(tree, grid_area_size);
547 if let Some(size) = known_dimensions.get(axis) {
550 return size;
551 }
552 tree.measure_child_size(
558 self.node,
559 known_dimensions,
560 grid_area_size,
561 self.keyword_adjusted_available_space(
562 grid_area_size,
563 self.available_space_minus_margins(grid_area_size, available_space, tree).map(|opt| match opt {
564 Some(size) => AvailableSpace::Definite(size),
565 None => AvailableSpace::MinContent,
566 }),
567 tree,
568 ),
569 SizingMode::InherentSize,
570 axis.as_abs_naive(),
571 Line::FALSE,
572 )
573 }
574
575 #[inline(always)]
577 pub fn min_content_contribution_cached(
578 &mut self,
579 axis: AbstractAxis,
580 tree: &mut impl LayoutPartialTree,
581 grid_area_size: Size<Option<f32>>,
582 available_space: Size<Option<f32>>,
583 ) -> f32 {
584 self.min_content_contribution_cache.get(axis).unwrap_or_else(|| {
585 let size = self.min_content_contribution(axis, tree, grid_area_size, available_space);
586 self.min_content_contribution_cache.set(axis, Some(size));
587 size
588 })
589 }
590
591 pub fn max_content_contribution(
593 &mut self,
594 axis: AbstractAxis,
595 tree: &mut impl LayoutPartialTree,
596 grid_area_size: Size<Option<f32>>,
597 available_space: Size<Option<f32>>,
598 ) -> f32 {
599 let known_dimensions = self.known_dimensions_cached(tree, grid_area_size);
600 if let Some(size) = known_dimensions.get(axis) {
603 return size;
604 }
605 tree.measure_child_size(
609 self.node,
610 known_dimensions,
611 grid_area_size,
612 self.keyword_adjusted_available_space(
613 grid_area_size,
614 self.available_space_minus_margins(grid_area_size, available_space, tree).map(|opt| match opt {
615 Some(size) => AvailableSpace::Definite(size),
616 None => AvailableSpace::MaxContent,
617 }),
618 tree,
619 ),
620 SizingMode::InherentSize,
621 axis.as_abs_naive(),
622 Line::FALSE,
623 )
624 }
625
626 #[inline(always)]
631 fn available_space_minus_margins(
632 &self,
633 grid_area_size: Size<Option<f32>>,
634 available_space: Size<Option<f32>>,
635 tree: &impl LayoutPartialTree,
636 ) -> Size<Option<f32>> {
637 if available_space.width.is_none() && available_space.height.is_none() {
638 return available_space;
639 }
640 let margin = self.margin.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
641 Size {
642 width: available_space.width.map(|width| f32_max(width - margin.horizontal_axis_sum(), 0.0)),
643 height: available_space
644 .height
645 .map(|height| f32_max(height - margin.vertical_axis_sum() - self.baseline_shim, 0.0)),
646 }
647 }
648
649 fn keyword_adjusted_available_space(
653 &self,
654 grid_area_size: Size<Option<f32>>,
655 available_space: Size<AvailableSpace>,
656 tree: &impl LayoutPartialTree,
657 ) -> Size<AvailableSpace> {
658 if !self.size.width.is_sizing_keyword() && !self.size.height.is_sizing_keyword() {
659 return available_space;
660 }
661 let margins = self.margins_axis_sums_with_baseline_shims(grid_area_size.width, tree);
662 let mut adjusted = available_space;
663 for axis in [AbstractAxis::Inline, AbstractAxis::Block] {
664 let size_style = self.size.get(axis);
665 if !size_style.is_sizing_keyword() {
666 continue;
667 }
668 let stretch_size = grid_area_size.get(axis).maybe_sub(margins.get(axis)).maybe_max(0.0);
669 if let Some(SizingKeywordResolution::Measure(available)) =
670 resolve_sizing_keyword(size_style, stretch_size, grid_area_size.get(axis), |val, basis| {
671 tree.calc(val, basis)
672 })
673 {
674 adjusted.set(axis, available);
675 }
676 }
677 adjusted
678 }
679
680 #[inline(always)]
682 pub fn max_content_contribution_cached(
683 &mut self,
684 axis: AbstractAxis,
685 tree: &mut impl LayoutPartialTree,
686 grid_area_size: Size<Option<f32>>,
687 available_space: Size<Option<f32>>,
688 ) -> f32 {
689 self.max_content_contribution_cache.get(axis).unwrap_or_else(|| {
690 let size = self.max_content_contribution(axis, tree, grid_area_size, available_space);
691 self.max_content_contribution_cache.set(axis, Some(size));
692 size
693 })
694 }
695
696 pub fn minimum_contribution(
705 &mut self,
706 tree: &mut impl LayoutPartialTree,
707 axis: AbstractAxis,
708 axis_tracks: &[GridTrack],
709 grid_area_size: Size<Option<f32>>,
710 inner_node_size: Size<Option<f32>>,
711 ) -> f32 {
712 let padding = self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
713 let border = self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
714 let padding_border_size = (padding + border).sum_axes();
715 let box_sizing_adjustment =
716 if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
717 self.size
718 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
719 .maybe_apply_aspect_ratio(self.aspect_ratio)
720 .maybe_add(box_sizing_adjustment)
721 .get(axis)
722 .map(|size| {
723 let min_size = self.min_size.get(axis);
728 let max_size = self.max_size.get(axis);
729 if min_size.is_auto() && max_size.is_auto() {
730 return size;
731 }
732 let basis = grid_area_size.get(axis);
733 let max_size_basis = if self.is_replaced { basis.or(Some(0.0)) } else { basis };
734 let adjustment = box_sizing_adjustment.get(axis);
735 let min_size = min_size.maybe_resolve(basis, |val, basis| tree.calc(val, basis));
736 let max_size = max_size.maybe_resolve(max_size_basis, |val, basis| tree.calc(val, basis));
737 size.maybe_clamp(min_size.maybe_add(adjustment), max_size.maybe_add(adjustment))
738 })
739 .or_else(|| {
740 let size = self.size.get(axis);
743 (size.is_sizing_keyword() && !size.is_stretch())
744 .then(|| self.min_content_contribution_cached(axis, tree, grid_area_size, grid_area_size))
745 })
746 .or_else(|| {
747 self.min_size
748 .maybe_resolve(grid_area_size.map(|size| size.unwrap_or(0.0)), |val, basis| tree.calc(val, basis))
749 .maybe_apply_aspect_ratio(self.aspect_ratio)
750 .maybe_add(box_sizing_adjustment)
751 .get(axis)
752 })
753 .or_else(|| self.overflow.get(axis).maybe_into_automatic_min_size())
754 .unwrap_or_else(|| {
755 let item_axis_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
760
761 let spans_auto_min_track = item_axis_tracks
765 .iter()
766 .any(|track| track.min_track_sizing_function.behaves_as_auto(inner_node_size.get(axis)));
767
768 let only_span_one_track = item_axis_tracks.len() == 1;
770 let use_content_based_minimum = spans_auto_min_track
771 && (only_span_one_track
772 || !item_axis_tracks.iter().any(|track| track.max_track_sizing_function.is_fr()));
773
774 if use_content_based_minimum {
776 let mut minimum_contribution =
777 self.min_content_contribution_cached(axis, tree, grid_area_size, grid_area_size);
778
779 if self.is_compressible_replaced {
783 let size = self.size.get(axis).maybe_resolve(Some(0.0), |val, basis| tree.calc(val, basis));
784 let max_size =
785 self.max_size.get(axis).maybe_resolve(Some(0.0), |val, basis| tree.calc(val, basis));
786 minimum_contribution = minimum_contribution.maybe_min(size).maybe_min(max_size);
787 }
788
789 let limit =
795 self.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis), &|val, basis| {
796 tree.resolve_calc_value(val, basis)
797 });
798 minimum_contribution.maybe_min(limit.map(|limit| f32_max(limit, padding_border_size.get(axis))))
799 } else {
800 0.0
801 }
802 })
803 }
804
805 #[inline(always)]
807 pub fn minimum_contribution_cached(
808 &mut self,
809 tree: &mut impl LayoutPartialTree,
810 axis: AbstractAxis,
811 axis_tracks: &[GridTrack],
812 grid_area_size: Size<Option<f32>>,
813 inner_node_size: Size<Option<f32>>,
814 ) -> f32 {
815 self.minimum_contribution_cache.get(axis).unwrap_or_else(|| {
816 let size = self.minimum_contribution(tree, axis, axis_tracks, grid_area_size, inner_node_size);
817 self.minimum_contribution_cache.set(axis, Some(size));
818 size
819 })
820 }
821}