1use super::types::{GridItem, GridTrack, TrackCounts};
4use crate::geometry::{AbstractAxis, Line, Size};
5use crate::style::{AlignContent, AlignContentKeyword, AlignSelf, AvailableSpace};
6use crate::style_helpers::TaffyMinContent;
7use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, SizingMode};
8use crate::util::sys::{f32_max, f32_min, Vec};
9use crate::util::{MaybeMath, ResolveOrZero};
10use crate::CompactLength;
11use core::cmp::Ordering;
12
13struct ItemBatcher {
17 axis: AbstractAxis,
19 index_offset: usize,
21 current_span: u16,
23 current_is_flex: bool,
25}
26
27impl ItemBatcher {
28 #[inline(always)]
30 fn new(axis: AbstractAxis) -> Self {
31 ItemBatcher { index_offset: 0, axis, current_span: 1, current_is_flex: false }
32 }
33
34 #[inline]
37 fn next<'items>(&mut self, items: &'items mut [GridItem]) -> Option<(&'items mut [GridItem], bool)> {
38 if self.current_is_flex || self.index_offset >= items.len() {
39 return None;
40 }
41
42 let item = &items[self.index_offset];
43 self.current_span = item.span(self.axis);
44 self.current_is_flex = item.crosses_flexible_track(self.axis);
45
46 let next_index_offset = if self.current_is_flex {
47 items.len()
48 } else {
49 items
50 .iter()
51 .position(|item: &GridItem| {
52 item.crosses_flexible_track(self.axis) || item.span(self.axis) > self.current_span
53 })
54 .unwrap_or(items.len())
55 };
56
57 let batch_range = self.index_offset..next_index_offset;
58 self.index_offset = next_index_offset;
59
60 let batch = &mut items[batch_range];
61 Some((batch, self.current_is_flex))
62 }
63}
64
65struct IntrinsicSizeMeasurer<'tree, 'oat, Tree, EstimateFunction>
68where
69 Tree: LayoutPartialTree,
70 EstimateFunction: Fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
71{
72 tree: &'tree mut Tree,
74 other_axis_tracks: &'oat [GridTrack],
76 get_track_size_estimate: EstimateFunction,
79 axis: AbstractAxis,
81 inner_node_size: Size<Option<f32>>,
83}
84
85impl<Tree, EstimateFunction> IntrinsicSizeMeasurer<'_, '_, Tree, EstimateFunction>
86where
87 Tree: LayoutPartialTree,
88 EstimateFunction: Fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
89{
90 #[inline(always)]
95 fn grid_area_size(&self, item: &mut GridItem, axis_tracks: &[GridTrack]) -> Size<Option<f32>> {
96 item.grid_area_size_cached(
97 self.axis,
98 axis_tracks,
99 self.other_axis_tracks,
100 self.inner_node_size,
101 |track, basis| (self.get_track_size_estimate)(track, basis, self.tree),
102 &|val, basis| self.tree.calc(val, basis),
103 )
104 }
105
106 #[inline(always)]
109 fn margins_axis_sums_with_baseline_shims(&self, item: &GridItem, percentage_basis: Option<f32>) -> Size<f32> {
110 item.margins_axis_sums_with_baseline_shims(percentage_basis, self.tree)
111 }
112
113 #[inline(always)]
115 fn calc(&self, val: *const (), basis: f32) -> f32 {
116 self.tree.calc(val, basis)
117 }
118
119 #[inline(always)]
121 fn min_content_contribution(&mut self, item: &mut GridItem, axis_tracks: &[GridTrack]) -> f32 {
122 let grid_area_size = self.grid_area_size(item, axis_tracks);
123 let available_space = grid_area_size.with(self.axis, None);
124 let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item, available_space.width);
125 let contribution = item.min_content_contribution_cached(self.axis, self.tree, grid_area_size, available_space);
126 contribution + margin_axis_sums.get(self.axis)
127 }
128
129 #[inline(always)]
131 fn max_content_contribution(&mut self, item: &mut GridItem, axis_tracks: &[GridTrack]) -> f32 {
132 let grid_area_size = self.grid_area_size(item, axis_tracks);
133 let available_space = grid_area_size.with(self.axis, None);
134 let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item, available_space.width);
135 let contribution = item.max_content_contribution_cached(self.axis, self.tree, grid_area_size, available_space);
136 contribution + margin_axis_sums.get(self.axis)
137 }
138
139 #[inline(always)]
147 fn minimum_contribution(&mut self, item: &mut GridItem, axis_tracks: &[GridTrack]) -> f32 {
148 let grid_area_size = self.grid_area_size(item, axis_tracks);
149 let available_space = grid_area_size.with(self.axis, None);
150 let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item, available_space.width);
151 let contribution =
152 item.minimum_contribution_cached(self.tree, self.axis, axis_tracks, grid_area_size, self.inner_node_size);
153 contribution + margin_axis_sums.get(self.axis)
154 }
155}
156
157#[inline(always)]
160pub(super) fn cmp_by_cross_flex_then_span_then_start(
161 axis: AbstractAxis,
162) -> impl FnMut(&GridItem, &GridItem) -> Ordering {
163 move |item_a: &GridItem, item_b: &GridItem| -> Ordering {
164 match (item_a.crosses_flexible_track(axis), item_b.crosses_flexible_track(axis)) {
165 (false, true) => Ordering::Less,
166 (true, false) => Ordering::Greater,
167 _ => {
168 let placement_a = item_a.placement(axis);
169 let placement_b = item_b.placement(axis);
170 match placement_a.span().cmp(&placement_b.span()) {
171 Ordering::Less => Ordering::Less,
172 Ordering::Greater => Ordering::Greater,
173 Ordering::Equal => placement_a.start.cmp(&placement_b.start),
174 }
175 }
176 }
177 }
178}
179
180#[inline(always)]
184pub(super) fn compute_alignment_gutter_adjustment(
185 alignment: AlignContent,
186 axis_inner_node_size: Option<f32>,
187 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
188 tracks: &[GridTrack],
189) -> f32 {
190 if tracks.len() <= 1 {
191 return 0.0;
192 }
193
194 let outer_gutter_weight = match alignment.keyword() {
198 AlignContentKeyword::Start
199 | AlignContentKeyword::FlexStart
200 | AlignContentKeyword::End
201 | AlignContentKeyword::FlexEnd
202 | AlignContentKeyword::Center => 1,
203 AlignContentKeyword::Stretch => 0,
204 AlignContentKeyword::SpaceBetween => 0,
205 AlignContentKeyword::SpaceAround => 1,
206 AlignContentKeyword::SpaceEvenly => 1,
207 };
208
209 let inner_gutter_weight = match alignment.keyword() {
210 AlignContentKeyword::FlexStart
211 | AlignContentKeyword::Start
212 | AlignContentKeyword::FlexEnd
213 | AlignContentKeyword::End
214 | AlignContentKeyword::Center
215 | AlignContentKeyword::Stretch => 0,
216 AlignContentKeyword::SpaceBetween => 1,
217 AlignContentKeyword::SpaceAround => 2,
218 AlignContentKeyword::SpaceEvenly => 1,
219 };
220
221 if inner_gutter_weight == 0 {
222 return 0.0;
223 }
224
225 if let Some(axis_inner_node_size) = axis_inner_node_size {
226 let free_space = tracks
227 .iter()
228 .map(|track| get_track_size_estimate(track, Some(axis_inner_node_size)))
229 .sum::<Option<f32>>()
230 .map(|track_size_sum| f32_max(0.0, axis_inner_node_size - track_size_sum))
231 .unwrap_or(0.0);
232
233 let weighted_track_count =
234 (((tracks.len() - 3) / 2) * inner_gutter_weight as usize) + (2 * outer_gutter_weight as usize);
235
236 return (free_space / weighted_track_count as f32) * inner_gutter_weight as f32;
237 }
238
239 0.0
240}
241
242#[inline(always)]
244pub(super) fn resolve_item_track_indexes(items: &mut [GridItem], column_counts: TrackCounts, row_counts: TrackCounts) {
245 for item in items {
246 item.column_indexes = item.column.map(|line| line.into_track_vec_index(column_counts) as u16);
247 item.row_indexes = item.row.map(|line| line.into_track_vec_index(row_counts) as u16);
248 }
249}
250
251#[inline(always)]
253pub(super) fn determine_if_item_crosses_flexible_or_intrinsic_tracks(
254 items: &mut Vec<GridItem>,
255 columns: &[GridTrack],
256 rows: &[GridTrack],
257) {
258 for item in items {
259 item.crosses_flexible_column =
260 item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].is_flexible());
261 item.crosses_intrinsic_column =
262 item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].has_intrinsic_sizing_function());
263 item.crosses_flexible_row =
264 item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].is_flexible());
265 item.crosses_intrinsic_row =
266 item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].has_intrinsic_sizing_function());
267 }
268}
269
270#[allow(clippy::too_many_arguments)]
273pub(super) fn track_sizing_algorithm<Tree: LayoutPartialTree>(
274 tree: &mut Tree,
275 axis: AbstractAxis,
276 axis_min_size: Option<f32>,
277 axis_max_size: Option<f32>,
278 axis_alignment: AlignContent,
279 other_axis_alignment: AlignContent,
280 available_grid_space: Size<AvailableSpace>,
281 inner_node_size: Size<Option<f32>>,
282 axis_tracks: &mut [GridTrack],
283 other_axis_tracks: &mut [GridTrack],
284 items: &mut [GridItem],
285 get_track_size_estimate: fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
286 has_baseline_aligned_item: bool,
287) {
288 let percentage_basis = inner_node_size.get(axis).or(axis_min_size);
291 initialize_track_sizes(tree, axis_tracks, percentage_basis);
292
293 if has_baseline_aligned_item {
295 resolve_item_baselines(tree, axis, items, inner_node_size);
296 }
297
298 if axis_tracks.iter().all(|track| track.base_size == track.growth_limit) {
301 return;
302 }
303
304 let gutter_alignment_adjustment = compute_alignment_gutter_adjustment(
309 other_axis_alignment,
310 inner_node_size.get(axis.other()),
311 |track, basis| get_track_size_estimate(track, basis, tree),
312 other_axis_tracks,
313 );
314 if other_axis_tracks.len() > 3 {
315 let len = other_axis_tracks.len();
316 let inner_gutter_tracks = other_axis_tracks[2..len].iter_mut().step_by(2);
317 for track in inner_gutter_tracks {
318 track.content_alignment_adjustment = gutter_alignment_adjustment;
319 }
320 }
321
322 resolve_intrinsic_track_sizes(
324 tree,
325 axis,
326 axis_tracks,
327 other_axis_tracks,
328 items,
329 available_grid_space.get(axis),
330 inner_node_size,
331 get_track_size_estimate,
332 );
333
334 maximise_tracks(axis_tracks, inner_node_size.get(axis), available_grid_space.get(axis));
337
338 let axis_available_space_for_expansion = if let Some(available_space) = inner_node_size.get(axis) {
343 AvailableSpace::Definite(available_space)
344 } else {
345 match available_grid_space.get(axis) {
346 AvailableSpace::MinContent => AvailableSpace::MinContent,
347 AvailableSpace::MaxContent | AvailableSpace::Definite(_) => AvailableSpace::MaxContent,
348 }
349 };
350
351 expand_flexible_tracks(
354 tree,
355 axis,
356 axis_tracks,
357 items,
358 axis_min_size,
359 axis_max_size,
360 axis_available_space_for_expansion,
361 );
362
363 if axis_alignment == AlignContent::STRETCH {
366 stretch_auto_tracks(axis_tracks, axis_min_size, axis_available_space_for_expansion);
367 }
368}
369
370#[derive(Copy, Clone, Debug, PartialEq, Eq)]
374enum IntrinsicContributionType {
375 Minimum,
377 Maximum,
379}
380
381#[inline(always)]
384fn flush_planned_base_size_increases(tracks: &mut [GridTrack]) {
385 for track in tracks {
386 track.base_size += track.base_size_planned_increase;
387 track.base_size_planned_increase = 0.0;
388 }
389}
390
391#[inline(always)]
394fn flush_planned_growth_limit_increases(tracks: &mut [GridTrack], set_infinitely_growable: bool) {
395 for track in tracks {
396 if track.growth_limit_planned_increase > 0.0 {
397 track.growth_limit = if track.growth_limit == f32::INFINITY {
398 track.base_size + track.growth_limit_planned_increase
399 } else {
400 track.growth_limit + track.growth_limit_planned_increase
401 };
402 track.infinitely_growable = set_infinitely_growable;
403 } else {
404 track.infinitely_growable = false;
405 }
406 track.growth_limit_planned_increase = 0.0
407 }
408}
409
410#[inline(always)]
413fn initialize_track_sizes(
414 tree: &impl LayoutPartialTree,
415 axis_tracks: &mut [GridTrack],
416 axis_inner_node_size: Option<f32>,
417) {
418 for track in axis_tracks.iter_mut() {
419 track.base_size = track
426 .min_track_sizing_function
427 .definite_value(axis_inner_node_size, |val, basis| tree.calc(val, basis))
428 .unwrap_or(0.0);
429
430 track.growth_limit = track
438 .max_track_sizing_function
439 .definite_value(axis_inner_node_size, |val, basis| tree.calc(val, basis))
440 .unwrap_or(f32::INFINITY);
441
442 if track.growth_limit < track.base_size {
444 track.growth_limit = track.base_size;
445 }
446 }
447}
448
449fn resolve_item_baselines(
451 tree: &mut impl LayoutPartialTree,
452 axis: AbstractAxis,
453 items: &mut [GridItem],
454 inner_node_size: Size<Option<f32>>,
455) {
456 let other_axis = axis.other();
459 items.sort_by_key(|item| item.placement(other_axis).start);
460
461 let mut remaining_items = &mut items[0..];
463 while !remaining_items.is_empty() {
464 let current_row = remaining_items[0].placement(other_axis).start;
466
467 let next_row_first_item =
469 remaining_items.iter().position(|item| item.placement(other_axis).start != current_row);
470
471 let row_items = if let Some(index) = next_row_first_item {
476 let (row_items, tail) = remaining_items.split_at_mut(index);
477 remaining_items = tail;
478 row_items
479 } else {
480 let row_items = remaining_items;
481 remaining_items = &mut [];
482 row_items
483 };
484
485 let row_baseline_item_count = row_items.iter().filter(|item| item.align_self == AlignSelf::BASELINE).count();
489 if row_baseline_item_count <= 1 {
490 continue;
491 }
492
493 for item in row_items.iter_mut() {
495 let measured_size_and_baselines = tree.perform_child_layout(
496 item.node,
497 Size::NONE,
498 inner_node_size,
499 Size::MIN_CONTENT,
500 SizingMode::InherentSize,
501 Line::FALSE,
502 );
503
504 let baseline = measured_size_and_baselines.first_baselines.y;
505 let height = measured_size_and_baselines.size.height;
506
507 item.baseline = Some(
508 baseline.unwrap_or(height)
509 + item.margin.top.resolve_or_zero(inner_node_size.width, |val, basis| tree.calc(val, basis)),
510 );
511 }
512
513 let row_max_baseline =
515 row_items.iter().map(|item| item.baseline.unwrap_or(0.0)).max_by(|a, b| a.total_cmp(b)).unwrap();
516
517 for item in row_items.iter_mut() {
519 item.baseline_shim = row_max_baseline - item.baseline.unwrap_or(0.0);
520 }
521 }
522}
523
524#[allow(clippy::too_many_arguments)]
526fn resolve_intrinsic_track_sizes<Tree: LayoutPartialTree>(
527 tree: &mut Tree,
528 axis: AbstractAxis,
529 axis_tracks: &mut [GridTrack],
530 other_axis_tracks: &[GridTrack],
531 items: &mut [GridItem],
532 axis_available_grid_space: AvailableSpace,
533 inner_node_size: Size<Option<f32>>,
534 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
535) {
536 items.sort_by(cmp_by_cross_flex_then_span_then_start(axis));
546
547 let axis_inner_node_size = inner_node_size.get(axis);
559 let mut item_sizer =
560 IntrinsicSizeMeasurer { tree, other_axis_tracks, axis, inner_node_size, get_track_size_estimate };
561
562 let mut batched_item_iterator = ItemBatcher::new(axis);
563 while let Some((batch, is_flex)) = batched_item_iterator.next(items) {
564 let batch_span = batch[0].placement(axis).span();
567 if !is_flex && batch_span == 1 {
568 for item in batch.iter_mut() {
569 let track_index = item.placement_indexes(axis).start + 1;
570 let track = &axis_tracks[track_index as usize];
571
572 let new_base_size = match track.min_track_sizing_function.0.tag() {
574 CompactLength::MIN_CONTENT_TAG => {
575 f32_max(track.base_size, item_sizer.min_content_contribution(item, axis_tracks))
576 }
577 CompactLength::PERCENT_TAG => {
580 if axis_inner_node_size.is_none() {
581 f32_max(track.base_size, item_sizer.min_content_contribution(item, axis_tracks))
582 } else {
583 track.base_size
584 }
585 }
586 CompactLength::MAX_CONTENT_TAG => {
587 f32_max(track.base_size, item_sizer.max_content_contribution(item, axis_tracks))
588 }
589 CompactLength::AUTO_TAG => {
590 let space = match axis_available_grid_space {
591 AvailableSpace::MinContent | AvailableSpace::MaxContent
600 if !item.overflow.get(axis).is_scroll_container() =>
601 {
602 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
603 let axis_min_content_size = item_sizer.min_content_contribution(item, axis_tracks);
604 let limit = track
605 .max_track_sizing_function
606 .definite_limit(axis_inner_node_size, |val, basis| item_sizer.calc(val, basis));
607 axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
608 }
609 _ => item_sizer.minimum_contribution(item, axis_tracks),
610 };
611 f32_max(track.base_size, space)
612 }
613 CompactLength::LENGTH_TAG => {
614 track.base_size
616 }
617 #[cfg(feature = "calc")]
619 _ if track.min_track_sizing_function.0.is_calc() => {
620 if axis_inner_node_size.is_none() {
621 f32_max(track.base_size, item_sizer.min_content_contribution(item, axis_tracks))
622 } else {
623 track.base_size
624 }
625 }
626 _ => unreachable!(),
627 };
628 let growth_limit_min_content_contribution = if !item.overflow.get(axis).is_scroll_container() {
629 Some(item_sizer.min_content_contribution(item, axis_tracks))
630 } else {
631 None
632 };
633 let growth_limit_max_content_contribution = item_sizer.max_content_contribution(item, axis_tracks);
634 let growth_limit_intrinsic_min_content_contribution =
635 item_sizer.min_content_contribution(item, axis_tracks);
636 let track = &mut axis_tracks[track_index as usize];
637 track.base_size = new_base_size;
638
639 if track.max_track_sizing_function.is_fit_content() {
641 if let Some(min_content_contribution) = growth_limit_min_content_contribution {
644 track.growth_limit_planned_increase =
645 f32_max(track.growth_limit_planned_increase, min_content_contribution);
646 }
647
648 let fit_content_limit = track.fit_content_limit(axis_inner_node_size);
651 let max_content_contribution = f32_min(growth_limit_max_content_contribution, fit_content_limit);
652 track.growth_limit_planned_increase =
653 f32_max(track.growth_limit_planned_increase, max_content_contribution);
654 } else if track.max_track_sizing_function.is_max_content_alike()
655 || track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none()
656 {
657 track.growth_limit_planned_increase =
660 f32_max(track.growth_limit_planned_increase, growth_limit_max_content_contribution);
661 } else if track.max_track_sizing_function.is_intrinsic() {
662 track.growth_limit_planned_increase =
663 f32_max(track.growth_limit_planned_increase, growth_limit_intrinsic_min_content_contribution);
664 }
665 }
666
667 for track in axis_tracks.iter_mut() {
668 if track.growth_limit_planned_increase > 0.0 {
669 track.growth_limit = if track.growth_limit == f32::INFINITY {
670 track.growth_limit_planned_increase
671 } else {
672 f32_max(track.growth_limit, track.growth_limit_planned_increase)
673 };
674 }
675 track.infinitely_growable = false;
676 track.growth_limit_planned_increase = 0.0;
677 if track.growth_limit < track.base_size {
678 track.growth_limit = track.base_size;
679 }
680 }
681
682 continue;
683 }
684
685 for item in batch.iter_mut().filter(|item| item.crosses_intrinsic_track(axis)) {
688 let space = match axis_available_grid_space {
698 AvailableSpace::MinContent | AvailableSpace::MaxContent
699 if !item.overflow.get(axis).is_scroll_container() =>
700 {
701 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
702 let axis_min_content_size = item_sizer.min_content_contribution(item, axis_tracks);
703 let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size, &|val, basis| {
704 item_sizer.calc(val, basis)
705 });
706 axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
707 }
708 _ => item_sizer.minimum_contribution(item, axis_tracks),
709 };
710 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
711 if space > 0.0 {
712 let has_intrinsic_min_track_sizing_function = |track: &GridTrack| {
713 track
714 .min_track_sizing_function
715 .definite_value(axis_inner_node_size, |val, basis| item_sizer.calc(val, basis))
716 .is_none()
717 };
718 if item.overflow.get(axis).is_scroll_container() {
719 let fit_content_limit =
720 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
721 distribute_item_space_to_base_size(
722 is_flex,
723 space,
724 tracks,
725 has_intrinsic_min_track_sizing_function,
726 fit_content_limit,
727 IntrinsicContributionType::Minimum,
728 axis_inner_node_size,
729 );
730 } else {
731 distribute_item_space_to_base_size(
732 is_flex,
733 space,
734 tracks,
735 has_intrinsic_min_track_sizing_function,
736 |track| track.growth_limit,
737 IntrinsicContributionType::Minimum,
738 axis_inner_node_size,
739 );
740 }
741 }
742 }
743 flush_planned_base_size_increases(axis_tracks);
744
745 let has_min_or_max_content_min_track_sizing_function =
749 move |track: &GridTrack| track.min_track_sizing_function.is_min_or_max_content();
750 for item in batch.iter_mut() {
751 let space = item_sizer.min_content_contribution(item, axis_tracks);
752 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
753 if space > 0.0 {
754 if item.overflow.get(axis).is_scroll_container() {
755 let fit_content_limit =
756 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
757 distribute_item_space_to_base_size(
758 is_flex,
759 space,
760 tracks,
761 has_min_or_max_content_min_track_sizing_function,
762 fit_content_limit,
763 IntrinsicContributionType::Minimum,
764 axis_inner_node_size,
765 );
766 } else {
767 distribute_item_space_to_base_size(
768 is_flex,
769 space,
770 tracks,
771 has_min_or_max_content_min_track_sizing_function,
772 |track| track.growth_limit,
773 IntrinsicContributionType::Minimum,
774 axis_inner_node_size,
775 );
776 }
777 }
778 }
779 flush_planned_base_size_increases(axis_tracks);
780
781 if axis_available_grid_space == AvailableSpace::MaxContent {
791 #[inline(always)]
800 fn has_auto_min_track_sizing_function(track: &GridTrack) -> bool {
801 track.min_track_sizing_function.is_auto() && !track.max_track_sizing_function.is_min_content()
802 }
803
804 #[inline(always)]
806 fn has_max_content_min_track_sizing_function(track: &GridTrack) -> bool {
807 track.min_track_sizing_function.is_max_content()
808 }
809
810 for item in batch.iter_mut() {
811 let axis_max_content_size = item_sizer.max_content_contribution(item, axis_tracks);
812 let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size, &|val, basis| {
813 item_sizer.calc(val, basis)
814 });
815 let space = axis_max_content_size.maybe_min(limit);
816 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
817 if space > 0.0 {
818 if tracks.iter().any(has_max_content_min_track_sizing_function) {
829 distribute_item_space_to_base_size(
830 is_flex,
831 space,
832 tracks,
833 has_max_content_min_track_sizing_function,
834 |_| f32::INFINITY,
835 IntrinsicContributionType::Maximum,
836 axis_inner_node_size,
837 );
838 } else {
839 let fit_content_limited_growth_limit =
840 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
841 distribute_item_space_to_base_size(
842 is_flex,
843 space,
844 tracks,
845 has_auto_min_track_sizing_function,
846 fit_content_limited_growth_limit,
847 IntrinsicContributionType::Maximum,
848 axis_inner_node_size,
849 );
850 }
851 }
852 }
853 flush_planned_base_size_increases(axis_tracks);
854 }
855
856 let has_max_content_min_track_sizing_function =
859 move |track: &GridTrack| track.min_track_sizing_function.is_max_content();
860 for item in batch.iter_mut() {
861 let axis_max_content_size = item_sizer.max_content_contribution(item, axis_tracks);
862 let space = axis_max_content_size;
863 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
864 if space > 0.0 {
865 distribute_item_space_to_base_size(
866 is_flex,
867 space,
868 tracks,
869 has_max_content_min_track_sizing_function,
870 |track| track.growth_limit,
871 IntrinsicContributionType::Maximum,
872 axis_inner_node_size,
873 );
874 }
875 }
876 flush_planned_base_size_increases(axis_tracks);
877
878 for track in axis_tracks.iter_mut() {
880 if track.growth_limit < track.base_size {
881 track.growth_limit = track.base_size;
882 }
883 }
884
885 if !is_flex {
888 let has_intrinsic_max_track_sizing_function =
891 move |track: &GridTrack| !track.max_track_sizing_function.has_definite_value(axis_inner_node_size);
892 for item in batch.iter_mut() {
893 let axis_min_content_size = item_sizer.min_content_contribution(item, axis_tracks);
894 let space = axis_min_content_size;
895 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
896 if space > 0.0 {
897 distribute_item_space_to_growth_limit(
898 space,
899 tracks,
900 has_intrinsic_max_track_sizing_function,
901 inner_node_size.get(axis),
902 );
903 }
904 }
905 flush_planned_growth_limit_increases(axis_tracks, true);
907
908 let has_max_content_max_track_sizing_function = |track: &GridTrack| {
912 track.max_track_sizing_function.is_max_content_alike()
913 || (track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none())
914 };
915 for item in batch.iter_mut() {
916 let axis_max_content_size = item_sizer.max_content_contribution(item, axis_tracks);
917 let space = axis_max_content_size;
918 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
919 if space > 0.0 {
920 distribute_item_space_to_growth_limit(
921 space,
922 tracks,
923 has_max_content_max_track_sizing_function,
924 inner_node_size.get(axis),
925 );
926 }
927 }
928 flush_planned_growth_limit_increases(axis_tracks, false);
930 }
931 }
932
933 axis_tracks
937 .iter_mut()
938 .filter(|track| track.growth_limit == f32::INFINITY)
939 .for_each(|track| track.growth_limit = track.base_size);
940}
941
942#[inline(always)]
945fn distribute_item_space_to_base_size(
946 is_flex: bool,
947 space: f32,
948 tracks: &mut [GridTrack],
949 track_is_affected: impl Fn(&GridTrack) -> bool,
950 track_limit: impl Fn(&GridTrack) -> f32,
951 intrinsic_contribution_type: IntrinsicContributionType,
952 axis_inner_node_size: Option<f32>,
953) {
954 if is_flex {
955 let filter = |track: &GridTrack| track.is_flexible() && track_is_affected(track);
956
957 let flex_factor_sum: f32 = tracks.iter().filter(|track| filter(track)).map(|track| track.flex_factor()).sum();
965 if flex_factor_sum > 0.0 {
966 distribute_item_space_to_base_size_inner(
967 space,
968 tracks,
969 filter,
970 |track| track.flex_factor(),
971 track_limit,
972 intrinsic_contribution_type,
973 axis_inner_node_size,
974 )
975 } else {
976 distribute_item_space_to_base_size_inner(
977 space,
978 tracks,
979 filter,
980 |_| 1.0,
981 track_limit,
982 intrinsic_contribution_type,
983 axis_inner_node_size,
984 )
985 }
986 } else {
987 distribute_item_space_to_base_size_inner(
988 space,
989 tracks,
990 track_is_affected,
991 |_| 1.0,
992 track_limit,
993 intrinsic_contribution_type,
994 axis_inner_node_size,
995 )
996 }
997
998 fn distribute_item_space_to_base_size_inner(
1001 space: f32,
1002 tracks: &mut [GridTrack],
1003 track_is_affected: impl Fn(&GridTrack) -> bool,
1004 track_distribution_proportion: impl Fn(&GridTrack) -> f32,
1005 track_limit: impl Fn(&GridTrack) -> f32,
1006 intrinsic_contribution_type: IntrinsicContributionType,
1007 axis_inner_node_size: Option<f32>,
1008 ) {
1009 if space == 0.0 || !tracks.iter().any(&track_is_affected) {
1013 return;
1014 }
1015
1016 let get_base_size = |track: &GridTrack| track.base_size;
1019
1020 let track_sizes: f32 = tracks.iter().map(|track| track.base_size).sum();
1022 let extra_space: f32 = f32_max(0.0, space - track_sizes);
1023
1024 const THRESHOLD: f32 = 0.000001;
1032
1033 let extra_space = distribute_space_up_to_limits(
1034 extra_space,
1035 tracks,
1036 &track_is_affected,
1037 &track_distribution_proportion,
1038 get_base_size,
1039 &track_limit,
1040 );
1041
1042 if extra_space > THRESHOLD {
1044 let mut filter = match intrinsic_contribution_type {
1049 IntrinsicContributionType::Minimum => {
1050 (|track: &GridTrack| track.max_track_sizing_function.is_intrinsic()) as fn(&GridTrack) -> bool
1051 }
1052 IntrinsicContributionType::Maximum => {
1053 (|track: &GridTrack| track.max_track_sizing_function.is_max_or_fit_content())
1054 as fn(&GridTrack) -> bool
1055 }
1056 };
1057
1058 let number_of_tracks =
1060 tracks.iter().filter(|track| track_is_affected(track)).filter(|track| filter(track)).count();
1061 if number_of_tracks == 0 {
1062 filter = (|_| true) as fn(&GridTrack) -> bool;
1063 }
1064
1065 distribute_space_up_to_limits(
1068 extra_space,
1069 tracks,
1070 |track| track_is_affected(track) && filter(track),
1071 &track_distribution_proportion,
1072 get_base_size,
1073 |track| track.fit_content_limit(axis_inner_node_size),
1074 );
1075 }
1076
1077 for track in tracks.iter_mut() {
1080 if track.item_incurred_increase > track.base_size_planned_increase {
1081 track.base_size_planned_increase = track.item_incurred_increase;
1082 }
1083
1084 track.item_incurred_increase = 0.0;
1086 }
1087 }
1088}
1089
1090fn distribute_item_space_to_growth_limit(
1094 space: f32,
1095 tracks: &mut [GridTrack],
1096 track_is_affected: impl Fn(&GridTrack) -> bool,
1097 axis_inner_node_size: Option<f32>,
1098) {
1099 if space == 0.0 || tracks.iter().filter(|track| track_is_affected(track)).count() == 0 {
1103 return;
1104 }
1105
1106 let track_sizes: f32 = tracks
1108 .iter()
1109 .map(|track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit })
1110 .sum();
1111 let extra_space: f32 = f32_max(0.0, space - track_sizes);
1112
1113 let number_of_growable_tracks = tracks
1118 .iter()
1119 .filter(|track| track_is_affected(track))
1120 .filter(|track| {
1121 track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1122 })
1123 .count();
1124 if number_of_growable_tracks > 0 {
1125 let item_incurred_increase = extra_space / number_of_growable_tracks as f32;
1126 for track in tracks.iter_mut().filter(|track| track_is_affected(track)).filter(|track| {
1127 track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1128 }) {
1129 track.item_incurred_increase = item_incurred_increase;
1130 }
1131 } else {
1132 distribute_space_up_to_limits(
1136 extra_space,
1137 tracks,
1138 track_is_affected,
1139 |_| 1.0,
1140 |track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit },
1141 move |track| track.fit_content_limit(axis_inner_node_size),
1142 );
1143 };
1144
1145 for track in tracks.iter_mut() {
1148 if track.item_incurred_increase > track.growth_limit_planned_increase {
1149 track.growth_limit_planned_increase = track.item_incurred_increase;
1150 }
1151
1152 track.item_incurred_increase = 0.0;
1154 }
1155}
1156
1157#[inline(always)]
1160fn maximise_tracks(
1161 axis_tracks: &mut [GridTrack],
1162 axis_inner_node_size: Option<f32>,
1163 axis_available_grid_space: AvailableSpace,
1164) {
1165 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1166 let free_space = axis_available_grid_space.compute_free_space(used_space);
1167 if free_space == f32::INFINITY {
1168 axis_tracks.iter_mut().for_each(|track| track.base_size = track.growth_limit);
1169 } else if free_space > 0.0 {
1170 distribute_space_up_to_limits(
1171 free_space,
1172 axis_tracks,
1173 |_| true,
1174 |_| 1.0,
1175 |track| track.base_size,
1176 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size),
1177 );
1178 for track in axis_tracks.iter_mut() {
1179 track.base_size += track.item_incurred_increase;
1180 track.item_incurred_increase = 0.0;
1181 }
1182 }
1183}
1184
1185#[allow(clippy::too_many_arguments)]
1188#[inline(always)]
1189fn expand_flexible_tracks(
1190 tree: &mut impl LayoutPartialTree,
1191 axis: AbstractAxis,
1192 axis_tracks: &mut [GridTrack],
1193 items: &mut [GridItem],
1194 axis_min_size: Option<f32>,
1195 axis_max_size: Option<f32>,
1196 axis_available_space_for_expansion: AvailableSpace,
1197) {
1198 let flex_fraction = match axis_available_space_for_expansion {
1200 AvailableSpace::Definite(available_space) => {
1206 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1207 let free_space = available_space - used_space;
1208 if free_space <= 0.0 {
1209 0.0
1210 } else {
1211 find_size_of_fr(axis_tracks, available_space)
1212 }
1213 }
1214 AvailableSpace::MinContent => 0.0,
1216 AvailableSpace::MaxContent => {
1218 let flex_fraction = f32_max(
1220 axis_tracks
1223 .iter()
1224 .filter(|track| track.max_track_sizing_function.is_fr())
1225 .map(|track| {
1226 let flex_factor = track.flex_factor();
1227 if flex_factor > 1.0 {
1228 track.base_size / flex_factor
1229 } else {
1230 track.base_size
1231 }
1232 })
1233 .max_by(|a, b| a.total_cmp(b))
1234 .unwrap_or(0.0),
1235 items
1238 .iter_mut()
1239 .filter(|item| item.crosses_flexible_track(axis))
1240 .map(|item| {
1241 let tracks = &axis_tracks[item.track_range_excluding_lines(axis)];
1242 let max_content_contribution =
1244 item.max_content_contribution_cached(axis, tree, Size::NONE, Size::NONE);
1245 find_size_of_fr(tracks, max_content_contribution)
1246 })
1247 .max_by(|a, b| a.total_cmp(b))
1248 .unwrap_or(0.0),
1249 );
1250
1251 let hypothetical_grid_size: f32 = axis_tracks
1256 .iter()
1257 .map(|track| {
1258 if track.max_track_sizing_function.is_fr() {
1259 let track_flex_factor = track.max_track_sizing_function.0.value();
1260 f32_max(track.base_size, track_flex_factor * flex_fraction)
1261 } else {
1262 track.base_size
1263 }
1264 })
1265 .sum();
1266 let axis_min_size = axis_min_size.unwrap_or(0.0);
1267 let axis_max_size = axis_max_size.unwrap_or(f32::INFINITY);
1268 if hypothetical_grid_size < axis_min_size {
1269 find_size_of_fr(axis_tracks, axis_min_size)
1270 } else if hypothetical_grid_size > axis_max_size {
1271 find_size_of_fr(axis_tracks, axis_max_size)
1272 } else {
1273 flex_fraction
1274 }
1275 }
1276 };
1277
1278 for track in axis_tracks.iter_mut().filter(|track| track.max_track_sizing_function.is_fr()) {
1281 let track_flex_factor = track.max_track_sizing_function.0.value();
1282 track.base_size = f32_max(track.base_size, track_flex_factor * flex_fraction);
1283 }
1284}
1285
1286#[inline(always)]
1290fn find_size_of_fr(tracks: &[GridTrack], space_to_fill: f32) -> f32 {
1291 if space_to_fill == 0.0 {
1294 return 0.0;
1295 }
1296
1297 let mut hypothetical_fr_size = f32::INFINITY;
1302 let mut previous_iter_hypothetical_fr_size;
1303 loop {
1304 let mut used_space = 0.0;
1308 let mut naive_flex_factor_sum = 0.0;
1309 for track in tracks.iter() {
1310 if track.max_track_sizing_function.is_fr()
1312 && track.max_track_sizing_function.0.value() * hypothetical_fr_size >= track.base_size
1313 {
1314 naive_flex_factor_sum += track.max_track_sizing_function.0.value();
1315 } else {
1316 used_space += track.base_size;
1317 };
1318 }
1319 let leftover_space = space_to_fill - used_space;
1320 let flex_factor = f32_max(naive_flex_factor_sum, 1.0);
1321
1322 previous_iter_hypothetical_fr_size = hypothetical_fr_size;
1324 hypothetical_fr_size = leftover_space / flex_factor;
1325
1326 let hypothetical_fr_size_is_valid = tracks.iter().all(|track| {
1330 if track.max_track_sizing_function.is_fr() {
1331 let flex_factor = track.max_track_sizing_function.0.value();
1332 flex_factor * hypothetical_fr_size >= track.base_size
1333 || flex_factor * previous_iter_hypothetical_fr_size < track.base_size
1334 } else {
1335 true
1336 }
1337 });
1338 if hypothetical_fr_size_is_valid {
1339 break;
1340 }
1341 }
1342
1343 hypothetical_fr_size
1345}
1346
1347#[inline(always)]
1350fn stretch_auto_tracks(
1351 axis_tracks: &mut [GridTrack],
1352 axis_min_size: Option<f32>,
1353 axis_available_space_for_expansion: AvailableSpace,
1354) {
1355 let num_auto_tracks = axis_tracks.iter().filter(|track| track.max_track_sizing_function.is_auto()).count();
1356 if num_auto_tracks > 0 {
1357 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1358
1359 let free_space = if axis_available_space_for_expansion.is_definite() {
1362 axis_available_space_for_expansion.compute_free_space(used_space)
1363 } else {
1364 match axis_min_size {
1365 Some(size) => size - used_space,
1366 None => 0.0,
1367 }
1368 };
1369 if free_space > 0.0 {
1370 let extra_space_per_auto_track = free_space / num_auto_tracks as f32;
1371 axis_tracks
1372 .iter_mut()
1373 .filter(|track| track.max_track_sizing_function.is_auto())
1374 .for_each(|track| track.base_size += extra_space_per_auto_track);
1375 }
1376 }
1377}
1378
1379#[inline(always)]
1382fn distribute_space_up_to_limits(
1383 space_to_distribute: f32,
1384 tracks: &mut [GridTrack],
1385 track_is_affected: impl Fn(&GridTrack) -> bool,
1386 track_distribution_proportion: impl Fn(&GridTrack) -> f32,
1387 track_affected_property: impl Fn(&GridTrack) -> f32,
1388 track_limit: impl Fn(&GridTrack) -> f32,
1389) -> f32 {
1390 const THRESHOLD: f32 = 0.01;
1393
1394 let mut space_to_distribute = space_to_distribute;
1395 while space_to_distribute > THRESHOLD {
1396 let track_distribution_proportion_sum: f32 = tracks
1397 .iter()
1398 .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1399 .filter(|track| track_is_affected(track))
1400 .map(&track_distribution_proportion)
1401 .sum();
1402
1403 if track_distribution_proportion_sum == 0.0 {
1404 break;
1405 }
1406
1407 let min_increase_limit = tracks
1409 .iter()
1410 .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1411 .filter(|track| track_is_affected(track))
1412 .map(|track| {
1413 (track_limit(track) - track_affected_property(track) - track.item_incurred_increase)
1414 / track_distribution_proportion(track)
1415 })
1416 .min_by(|a, b| a.total_cmp(b))
1417 .unwrap(); let iteration_item_incurred_increase =
1419 f32_min(min_increase_limit, space_to_distribute / track_distribution_proportion_sum);
1420
1421 for track in tracks.iter_mut().filter(|track| track_is_affected(track)) {
1422 let increase = iteration_item_incurred_increase * track_distribution_proportion(track);
1423 if increase > 0.0
1424 && track_affected_property(track) + track.item_incurred_increase + increase
1425 <= track_limit(track) + THRESHOLD
1426 {
1427 track.item_incurred_increase += increase;
1428 space_to_distribute -= increase;
1429 }
1430 }
1431 }
1432
1433 space_to_distribute
1434}