1use super::types::{GridItem, GridTrack, TrackCounts};
4use crate::geometry::{AbstractAxis, Line, Size};
5use crate::style::{
6 AlignContent, AlignSelf, AvailableSpace, LengthPercentage, MaxTrackSizingFunction, MinTrackSizingFunction,
7};
8use crate::style_helpers::TaffyMinContent;
9use crate::tree::{PartialLayoutTree, PartialLayoutTreeExt, SizingMode};
10use crate::util::sys::{f32_max, f32_min, Vec};
11use crate::util::{MaybeMath, ResolveOrZero};
12use core::cmp::Ordering;
13
14struct ItemBatcher {
18 axis: AbstractAxis,
20 index_offset: usize,
22 current_span: u16,
24 current_is_flex: bool,
26}
27
28impl ItemBatcher {
29 #[inline(always)]
31 fn new(axis: AbstractAxis) -> Self {
32 ItemBatcher { index_offset: 0, axis, current_span: 1, current_is_flex: false }
33 }
34
35 #[inline]
38 fn next<'items>(&mut self, items: &'items mut [GridItem]) -> Option<(&'items mut [GridItem], bool)> {
39 if self.current_is_flex || self.index_offset >= items.len() {
40 return None;
41 }
42
43 let item = &items[self.index_offset];
44 self.current_span = item.span(self.axis);
45 self.current_is_flex = item.crosses_flexible_track(self.axis);
46
47 let next_index_offset = if self.current_is_flex {
48 items.len()
49 } else {
50 items
51 .iter()
52 .position(|item: &GridItem| {
53 item.crosses_flexible_track(self.axis) || item.span(self.axis) > self.current_span
54 })
55 .unwrap_or(items.len())
56 };
57
58 let batch_range = self.index_offset..next_index_offset;
59 self.index_offset = next_index_offset;
60
61 let batch = &mut items[batch_range];
62 Some((batch, self.current_is_flex))
63 }
64}
65
66struct IntrisicSizeMeasurer<'tree, 'oat, Tree, EstimateFunction>
69where
70 Tree: PartialLayoutTree,
71 EstimateFunction: Fn(&GridTrack, Option<f32>) -> Option<f32>,
72{
73 tree: &'tree mut Tree,
75 other_axis_tracks: &'oat [GridTrack],
77 get_track_size_estimate: EstimateFunction,
80 axis: AbstractAxis,
82 inner_node_size: Size<Option<f32>>,
84}
85
86impl<'tree, 'oat, Tree, EstimateFunction> IntrisicSizeMeasurer<'tree, 'oat, Tree, EstimateFunction>
87where
88 Tree: PartialLayoutTree,
89 EstimateFunction: Fn(&GridTrack, Option<f32>) -> Option<f32>,
90{
91 #[inline(always)]
96 fn available_space(&self, item: &mut GridItem) -> Size<Option<f32>> {
97 item.available_space_cached(
98 self.axis,
99 self.other_axis_tracks,
100 self.inner_node_size.get(self.axis.other()),
101 &self.get_track_size_estimate,
102 )
103 }
104
105 #[inline(always)]
108 fn margins_axis_sums_with_baseline_shims(&self, item: &GridItem) -> Size<f32> {
109 item.margins_axis_sums_with_baseline_shims(self.inner_node_size.width)
110 }
111
112 #[inline(always)]
114 fn min_content_contribution(&mut self, item: &mut GridItem) -> f32 {
115 let available_space = self.available_space(item);
116 let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item);
117 let contribution =
118 item.min_content_contribution_cached(self.axis, self.tree, available_space, self.inner_node_size);
119 contribution + margin_axis_sums.get(self.axis)
120 }
121
122 #[inline(always)]
124 fn max_content_contribution(&mut self, item: &mut GridItem) -> f32 {
125 let available_space = self.available_space(item);
126 let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item);
127 let contribution =
128 item.max_content_contribution_cached(self.axis, self.tree, available_space, self.inner_node_size);
129 contribution + margin_axis_sums.get(self.axis)
130 }
131
132 #[inline(always)]
139 fn minimum_contribution(&mut self, item: &mut GridItem, axis_tracks: &[GridTrack]) -> f32 {
140 let available_space = self.available_space(item);
141 let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item);
142 let contribution =
143 item.minimum_contribution_cached(self.tree, self.axis, axis_tracks, available_space, self.inner_node_size);
144 contribution + margin_axis_sums.get(self.axis)
145 }
146}
147
148#[inline(always)]
151pub(super) fn cmp_by_cross_flex_then_span_then_start(
152 axis: AbstractAxis,
153) -> impl FnMut(&GridItem, &GridItem) -> Ordering {
154 move |item_a: &GridItem, item_b: &GridItem| -> Ordering {
155 match (item_a.crosses_flexible_track(axis), item_b.crosses_flexible_track(axis)) {
156 (false, true) => Ordering::Less,
157 (true, false) => Ordering::Greater,
158 _ => {
159 let placement_a = item_a.placement(axis);
160 let placement_b = item_b.placement(axis);
161 match placement_a.span().cmp(&placement_b.span()) {
162 Ordering::Less => Ordering::Less,
163 Ordering::Greater => Ordering::Greater,
164 Ordering::Equal => placement_a.start.cmp(&placement_b.start),
165 }
166 }
167 }
168 }
169}
170
171#[inline(always)]
175pub(super) fn compute_alignment_gutter_adjustment(
176 alignment: AlignContent,
177 axis_inner_node_size: Option<f32>,
178 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
179 tracks: &[GridTrack],
180) -> f32 {
181 if tracks.len() <= 1 {
182 return 0.0;
183 }
184
185 let outer_gutter_weight = match alignment {
188 AlignContent::Start => 1,
189 AlignContent::FlexStart => 1,
190 AlignContent::End => 1,
191 AlignContent::FlexEnd => 1,
192 AlignContent::Center => 1,
193 AlignContent::Stretch => 0,
194 AlignContent::SpaceBetween => 0,
195 AlignContent::SpaceAround => 1,
196 AlignContent::SpaceEvenly => 1,
197 };
198
199 let inner_gutter_weight = match alignment {
200 AlignContent::FlexStart => 0,
201 AlignContent::Start => 0,
202 AlignContent::FlexEnd => 0,
203 AlignContent::End => 0,
204 AlignContent::Center => 0,
205 AlignContent::Stretch => 0,
206 AlignContent::SpaceBetween => 1,
207 AlignContent::SpaceAround => 2,
208 AlignContent::SpaceEvenly => 1,
209 };
210
211 if inner_gutter_weight == 0 {
212 return 0.0;
213 }
214
215 if let Some(axis_inner_node_size) = axis_inner_node_size {
216 let free_space = tracks
217 .iter()
218 .map(|track| get_track_size_estimate(track, Some(axis_inner_node_size)))
219 .sum::<Option<f32>>()
220 .map(|track_size_sum| f32_max(0.0, axis_inner_node_size - track_size_sum))
221 .unwrap_or(0.0);
222
223 let weighted_track_count =
224 (((tracks.len() - 3) / 2) * inner_gutter_weight as usize) + (2 * outer_gutter_weight as usize);
225
226 return (free_space / weighted_track_count as f32) * inner_gutter_weight as f32;
227 }
228
229 0.0
230}
231
232#[inline(always)]
234pub(super) fn resolve_item_track_indexes(items: &mut [GridItem], column_counts: TrackCounts, row_counts: TrackCounts) {
235 for item in items {
236 item.column_indexes = item.column.map(|line| line.into_track_vec_index(column_counts) as u16);
237 item.row_indexes = item.row.map(|line| line.into_track_vec_index(row_counts) as u16);
238 }
239}
240
241#[inline(always)]
243pub(super) fn determine_if_item_crosses_flexible_or_intrinsic_tracks(
244 items: &mut Vec<GridItem>,
245 columns: &[GridTrack],
246 rows: &[GridTrack],
247) {
248 for item in items {
249 item.crosses_flexible_column =
250 item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].is_flexible());
251 item.crosses_intrinsic_column =
252 item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].has_intrinsic_sizing_function());
253 item.crosses_flexible_row =
254 item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].is_flexible());
255 item.crosses_intrinsic_row =
256 item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].has_intrinsic_sizing_function());
257 }
258}
259
260#[allow(clippy::too_many_arguments)]
263#[inline(always)]
264pub(super) fn track_sizing_algorithm<Tree: PartialLayoutTree>(
265 tree: &mut Tree,
266 axis: AbstractAxis,
267 axis_min_size: Option<f32>,
268 axis_max_size: Option<f32>,
269 other_axis_alignment: AlignContent,
270 available_grid_space: Size<AvailableSpace>,
271 inner_node_size: Size<Option<f32>>,
272 axis_tracks: &mut [GridTrack],
273 other_axis_tracks: &mut [GridTrack],
274 items: &mut [GridItem],
275 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
276 has_baseline_aligned_item: bool,
277) {
278 initialize_track_sizes(axis_tracks, inner_node_size.get(axis));
281
282 if has_baseline_aligned_item {
284 resolve_item_baselines(tree, axis, items, inner_node_size);
285 }
286
287 if axis_tracks.iter().all(|track| track.base_size == track.growth_limit) {
290 return;
291 }
292
293 let gutter_alignment_adjustment = compute_alignment_gutter_adjustment(
298 other_axis_alignment,
299 inner_node_size.get(axis.other()),
300 &get_track_size_estimate,
301 other_axis_tracks,
302 );
303 if other_axis_tracks.len() > 3 {
304 let len = other_axis_tracks.len();
305 let inner_gutter_tracks = other_axis_tracks[2..len].iter_mut().step_by(2);
306 for track in inner_gutter_tracks {
307 track.content_alignment_adjustment = gutter_alignment_adjustment;
308 }
309 }
310
311 resolve_intrinsic_track_sizes(
313 tree,
314 axis,
315 axis_tracks,
316 other_axis_tracks,
317 items,
318 available_grid_space.get(axis),
319 inner_node_size,
320 get_track_size_estimate,
321 );
322
323 maximise_tracks(axis_tracks, inner_node_size.get(axis), available_grid_space.get(axis));
326
327 let axis_available_space_for_expansion = if let Some(available_space) = inner_node_size.get(axis) {
332 AvailableSpace::Definite(available_space)
333 } else {
334 match available_grid_space.get(axis) {
335 AvailableSpace::MinContent => AvailableSpace::MinContent,
336 AvailableSpace::MaxContent | AvailableSpace::Definite(_) => AvailableSpace::MaxContent,
337 }
338 };
339
340 expand_flexible_tracks(
343 tree,
344 axis,
345 axis_tracks,
346 items,
347 axis_min_size,
348 axis_max_size,
349 axis_available_space_for_expansion,
350 inner_node_size,
351 );
352
353 stretch_auto_tracks(axis_tracks, axis_min_size, axis_available_space_for_expansion);
356}
357
358#[derive(Copy, Clone, Debug, PartialEq, Eq)]
362enum IntrinsicContributionType {
363 Minimum,
365 Maximum,
367}
368
369#[inline(always)]
372fn flush_planned_base_size_increases(tracks: &mut [GridTrack]) {
373 for track in tracks {
374 track.base_size += track.base_size_planned_increase;
375 track.base_size_planned_increase = 0.0;
376 }
377}
378
379#[inline(always)]
382fn flush_planned_growth_limit_increases(tracks: &mut [GridTrack], set_infinitely_growable: bool) {
383 for track in tracks {
384 if track.growth_limit_planned_increase > 0.0 {
385 track.growth_limit = if track.growth_limit == f32::INFINITY {
386 track.base_size + track.growth_limit_planned_increase
387 } else {
388 track.growth_limit + track.growth_limit_planned_increase
389 };
390 track.infinitely_growable = set_infinitely_growable;
391 } else {
392 track.infinitely_growable = false;
393 }
394 track.growth_limit_planned_increase = 0.0
395 }
396}
397
398#[inline(always)]
401fn initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: Option<f32>) {
402 let last_track_idx = axis_tracks.len() - 1;
403
404 axis_tracks[0].base_size = 0.0;
406 axis_tracks[0].growth_limit = 0.0;
407 axis_tracks[last_track_idx].base_size = 0.0;
408 axis_tracks[last_track_idx].growth_limit = 0.0;
409
410 let all_but_first_and_last = 1..last_track_idx;
411 for track in axis_tracks[all_but_first_and_last].iter_mut() {
412 track.base_size = track.min_track_sizing_function.definite_value(axis_inner_node_size).unwrap_or(0.0);
419
420 track.growth_limit =
428 track.max_track_sizing_function.definite_value(axis_inner_node_size).unwrap_or(f32::INFINITY);
429
430 if track.growth_limit < track.base_size {
432 track.growth_limit = track.base_size;
433 }
434 }
435}
436
437fn resolve_item_baselines(
439 tree: &mut impl PartialLayoutTree,
440 axis: AbstractAxis,
441 items: &mut [GridItem],
442 inner_node_size: Size<Option<f32>>,
443) {
444 let other_axis = axis.other();
447 items.sort_by_key(|item| item.placement(other_axis).start);
448
449 let mut remaining_items = &mut items[0..];
451 while !remaining_items.is_empty() {
452 let current_row = remaining_items[0].placement(other_axis).start;
454
455 let next_row_first_item =
457 remaining_items.iter().position(|item| item.placement(other_axis).start != current_row);
458
459 let row_items = if let Some(index) = next_row_first_item {
464 let (row_items, tail) = remaining_items.split_at_mut(index);
465 remaining_items = tail;
466 row_items
467 } else {
468 let row_items = remaining_items;
469 remaining_items = &mut [];
470 row_items
471 };
472
473 let row_baseline_item_count = row_items.iter().filter(|item| item.align_self == AlignSelf::Baseline).count();
477 if row_baseline_item_count <= 1 {
478 continue;
479 }
480
481 for item in row_items.iter_mut() {
483 let measured_size_and_baselines = tree.perform_child_layout(
484 item.node,
485 Size::NONE,
486 inner_node_size,
487 Size::MIN_CONTENT,
488 SizingMode::InherentSize,
489 Line::FALSE,
490 );
491
492 let baseline = measured_size_and_baselines.first_baselines.y;
493 let height = measured_size_and_baselines.size.height;
494
495 item.baseline = Some(baseline.unwrap_or(height) + item.margin.top.resolve_or_zero(inner_node_size.width));
496 }
497
498 let row_max_baseline =
500 row_items.iter().map(|item| item.baseline.unwrap_or(0.0)).max_by(|a, b| a.total_cmp(b)).unwrap();
501
502 for item in row_items.iter_mut() {
504 item.baseline_shim = row_max_baseline - item.baseline.unwrap_or(0.0);
505 }
506 }
507}
508
509#[allow(clippy::too_many_arguments)]
511fn resolve_intrinsic_track_sizes(
512 tree: &mut impl PartialLayoutTree,
513 axis: AbstractAxis,
514 axis_tracks: &mut [GridTrack],
515 other_axis_tracks: &[GridTrack],
516 items: &mut [GridItem],
517 axis_available_grid_space: AvailableSpace,
518 inner_node_size: Size<Option<f32>>,
519 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
520) {
521 items.sort_by(cmp_by_cross_flex_then_span_then_start(axis));
531
532 let axis_inner_node_size = inner_node_size.get(axis);
544 let flex_factor_sum = axis_tracks.iter().map(|track| track.flex_factor()).sum::<f32>();
545 let mut item_sizer =
546 IntrisicSizeMeasurer { tree, other_axis_tracks, axis, inner_node_size, get_track_size_estimate };
547
548 let mut batched_item_iterator = ItemBatcher::new(axis);
549 while let Some((batch, is_flex)) = batched_item_iterator.next(items) {
550 let batch_span = batch[0].placement(axis).span();
553 if !is_flex && batch_span == 1 {
554 for item in batch.iter_mut() {
555 let track_index = item.placement_indexes(axis).start + 1;
556 let track = &axis_tracks[track_index as usize];
557
558 let new_base_size = match track.min_track_sizing_function {
560 MinTrackSizingFunction::MinContent => {
561 f32_max(track.base_size, item_sizer.min_content_contribution(item))
562 }
563 MinTrackSizingFunction::Fixed(LengthPercentage::Percent(_)) => {
566 if axis_inner_node_size.is_none() {
567 f32_max(track.base_size, item_sizer.min_content_contribution(item))
568 } else {
569 track.base_size
570 }
571 }
572 MinTrackSizingFunction::MaxContent => {
573 f32_max(track.base_size, item_sizer.max_content_contribution(item))
574 }
575 MinTrackSizingFunction::Auto => {
576 let space = match axis_available_grid_space {
577 AvailableSpace::MinContent | AvailableSpace::MaxContent
586 if !item.overflow.get(axis).is_scroll_container() =>
587 {
588 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
589 let axis_min_content_size = item_sizer.min_content_contribution(item);
590 let limit = track.max_track_sizing_function.definite_limit(axis_inner_node_size);
591 axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
592 }
593 _ => item_sizer.minimum_contribution(item, axis_tracks),
594 };
595 f32_max(track.base_size, space)
596 }
597 MinTrackSizingFunction::Fixed(_) => {
598 track.base_size
600 }
601 };
602 let track = &mut axis_tracks[track_index as usize];
603 track.base_size = new_base_size;
604
605 if let MaxTrackSizingFunction::FitContent(_) = track.max_track_sizing_function {
607 if !item.overflow.get(axis).is_scroll_container() {
610 let min_content_contribution = item_sizer.min_content_contribution(item);
611 track.growth_limit_planned_increase =
612 f32_max(track.growth_limit_planned_increase, min_content_contribution);
613 }
614
615 let fit_content_limit = track.fit_content_limit(axis_inner_node_size);
618 let max_content_contribution =
619 f32_min(item_sizer.max_content_contribution(item), fit_content_limit);
620 track.growth_limit_planned_increase =
621 f32_max(track.growth_limit_planned_increase, max_content_contribution);
622 } else if track.max_track_sizing_function.is_max_content_alike()
623 || track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none()
624 {
625 track.growth_limit_planned_increase =
628 f32_max(track.growth_limit_planned_increase, item_sizer.max_content_contribution(item));
629 } else if track.max_track_sizing_function.is_intrinsic() {
630 track.growth_limit_planned_increase =
631 f32_max(track.growth_limit_planned_increase, item_sizer.min_content_contribution(item));
632 }
633 }
634
635 for track in axis_tracks.iter_mut() {
636 if track.growth_limit_planned_increase > 0.0 {
637 track.growth_limit = if track.growth_limit == f32::INFINITY {
638 track.growth_limit_planned_increase
639 } else {
640 f32_max(track.growth_limit, track.growth_limit_planned_increase)
641 };
642 }
643 track.infinitely_growable = false;
644 track.growth_limit_planned_increase = 0.0;
645 if track.growth_limit < track.base_size {
646 track.growth_limit = track.base_size;
647 }
648 }
649
650 continue;
651 }
652
653 let use_flex_factor_for_distribution = is_flex && flex_factor_sum != 0.0;
654
655 let has_intrinsic_min_track_sizing_function =
658 move |track: &GridTrack| track.min_track_sizing_function.definite_value(axis_inner_node_size).is_none();
659 for item in batch.iter_mut().filter(|item| item.crosses_intrinsic_track(axis)) {
660 let space = match axis_available_grid_space {
670 AvailableSpace::MinContent | AvailableSpace::MaxContent
671 if !item.overflow.get(axis).is_scroll_container() =>
672 {
673 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
674 let axis_min_content_size = item_sizer.min_content_contribution(item);
675 let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size);
676 axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
677 }
678 _ => item_sizer.minimum_contribution(item, axis_tracks),
679 };
680 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
681 if space > 0.0 {
682 if item.overflow.get(axis).is_scroll_container() {
683 let fit_content_limit = move |track: &GridTrack| track.fit_content_limit(axis_inner_node_size);
684 distribute_item_space_to_base_size(
685 is_flex,
686 use_flex_factor_for_distribution,
687 space,
688 tracks,
689 has_intrinsic_min_track_sizing_function,
690 fit_content_limit,
691 IntrinsicContributionType::Minimum,
692 );
693 } else {
694 distribute_item_space_to_base_size(
695 is_flex,
696 use_flex_factor_for_distribution,
697 space,
698 tracks,
699 has_intrinsic_min_track_sizing_function,
700 |_| f32::INFINITY,
701 IntrinsicContributionType::Minimum,
702 );
703 }
704 }
705 }
706 flush_planned_base_size_increases(axis_tracks);
707
708 let has_min_or_max_content_min_track_sizing_function = move |track: &GridTrack| {
712 use MinTrackSizingFunction::{MaxContent, MinContent};
713 matches!(track.min_track_sizing_function, MinContent | MaxContent)
714 };
715 for item in batch.iter_mut() {
716 let space = item_sizer.min_content_contribution(item);
717 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
718 if space > 0.0 {
719 if item.overflow.get(axis).is_scroll_container() {
720 let fit_content_limit = move |track: &GridTrack| track.fit_content_limit(axis_inner_node_size);
721 distribute_item_space_to_base_size(
722 is_flex,
723 use_flex_factor_for_distribution,
724 space,
725 tracks,
726 has_min_or_max_content_min_track_sizing_function,
727 fit_content_limit,
728 IntrinsicContributionType::Minimum,
729 );
730 } else {
731 distribute_item_space_to_base_size(
732 is_flex,
733 use_flex_factor_for_distribution,
734 space,
735 tracks,
736 has_min_or_max_content_min_track_sizing_function,
737 |_| f32::INFINITY,
738 IntrinsicContributionType::Minimum,
739 );
740 }
741 }
742 }
743 flush_planned_base_size_increases(axis_tracks);
744
745 if axis_available_grid_space == AvailableSpace::MaxContent {
755 #[inline(always)]
764 fn has_auto_min_track_sizing_function(track: &GridTrack) -> bool {
765 track.min_track_sizing_function == MinTrackSizingFunction::Auto
766 && track.max_track_sizing_function != MaxTrackSizingFunction::MinContent
767 }
768
769 #[inline(always)]
771 fn has_max_content_min_track_sizing_function(track: &GridTrack) -> bool {
772 track.min_track_sizing_function == MinTrackSizingFunction::MaxContent
773 }
774
775 for item in batch.iter_mut() {
776 let axis_max_content_size = item_sizer.max_content_contribution(item);
777 let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size);
778 let space = axis_max_content_size.maybe_min(limit);
779 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
780 if space > 0.0 {
781 if tracks.iter().any(has_max_content_min_track_sizing_function) {
792 distribute_item_space_to_base_size(
793 is_flex,
794 use_flex_factor_for_distribution,
795 space,
796 tracks,
797 has_max_content_min_track_sizing_function,
798 |_| f32::INFINITY,
799 IntrinsicContributionType::Maximum,
800 );
801 } else {
802 let fit_content_limited_growth_limit =
803 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
804 distribute_item_space_to_base_size(
805 is_flex,
806 use_flex_factor_for_distribution,
807 space,
808 tracks,
809 has_auto_min_track_sizing_function,
810 fit_content_limited_growth_limit,
811 IntrinsicContributionType::Maximum,
812 );
813 }
814 }
815 }
816 flush_planned_base_size_increases(axis_tracks);
817 }
818
819 let has_max_content_min_track_sizing_function =
822 move |track: &GridTrack| matches!(track.min_track_sizing_function, MinTrackSizingFunction::MaxContent);
823 for item in batch.iter_mut() {
824 let axis_max_content_size = item_sizer.max_content_contribution(item);
825 let space = axis_max_content_size;
826 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
827 if space > 0.0 {
828 distribute_item_space_to_base_size(
829 is_flex,
830 use_flex_factor_for_distribution,
831 space,
832 tracks,
833 has_max_content_min_track_sizing_function,
834 |_| f32::INFINITY,
835 IntrinsicContributionType::Maximum,
836 );
837 }
838 }
839 flush_planned_base_size_increases(axis_tracks);
840
841 for track in axis_tracks.iter_mut() {
843 if track.growth_limit < track.base_size {
844 track.growth_limit = track.base_size;
845 }
846 }
847
848 if !is_flex {
851 let has_intrinsic_max_track_sizing_function =
854 move |track: &GridTrack| track.max_track_sizing_function.definite_value(axis_inner_node_size).is_none();
855 for item in batch.iter_mut() {
856 let axis_min_content_size = item_sizer.min_content_contribution(item);
857 let space = axis_min_content_size;
858 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
859 if space > 0.0 {
860 distribute_item_space_to_growth_limit(
861 space,
862 tracks,
863 has_intrinsic_max_track_sizing_function,
864 inner_node_size.get(axis),
865 );
866 }
867 }
868 flush_planned_growth_limit_increases(axis_tracks, true);
870
871 let has_max_content_max_track_sizing_function = |track: &GridTrack| {
875 track.max_track_sizing_function.is_max_content_alike()
876 || (track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none())
877 };
878 for item in batch.iter_mut() {
879 let axis_max_content_size = item_sizer.max_content_contribution(item);
880 let space = axis_max_content_size;
881 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
882 if space > 0.0 {
883 distribute_item_space_to_growth_limit(
884 space,
885 tracks,
886 has_max_content_max_track_sizing_function,
887 inner_node_size.get(axis),
888 );
889 }
890 }
891 flush_planned_growth_limit_increases(axis_tracks, false);
893 }
894 }
895
896 axis_tracks
900 .iter_mut()
901 .filter(|track| track.growth_limit == f32::INFINITY)
902 .for_each(|track| track.growth_limit = track.base_size);
903}
904
905#[inline(always)]
908fn distribute_item_space_to_base_size(
909 is_flex: bool,
910 use_flex_factor_for_distribution: bool,
911 space: f32,
912 tracks: &mut [GridTrack],
913 track_is_affected: impl Fn(&GridTrack) -> bool,
914 track_limit: impl Fn(&GridTrack) -> f32,
915 intrinsic_contribution_type: IntrinsicContributionType,
916) {
917 if is_flex {
918 let filter = |track: &GridTrack| track.is_flexible() && track_is_affected(track);
919 if use_flex_factor_for_distribution {
920 distribute_item_space_to_base_size_inner(
921 space,
922 tracks,
923 filter,
924 |track| track.flex_factor(),
925 track_limit,
926 intrinsic_contribution_type,
927 )
928 } else {
929 distribute_item_space_to_base_size_inner(
930 space,
931 tracks,
932 filter,
933 |_| 1.0,
934 track_limit,
935 intrinsic_contribution_type,
936 )
937 }
938 } else {
939 distribute_item_space_to_base_size_inner(
940 space,
941 tracks,
942 track_is_affected,
943 |_| 1.0,
944 track_limit,
945 intrinsic_contribution_type,
946 )
947 }
948
949 fn distribute_item_space_to_base_size_inner(
952 space: f32,
953 tracks: &mut [GridTrack],
954 track_is_affected: impl Fn(&GridTrack) -> bool,
955 track_distribution_proportion: impl Fn(&GridTrack) -> f32,
956 track_limit: impl Fn(&GridTrack) -> f32,
957 intrinsic_contribution_type: IntrinsicContributionType,
958 ) {
959 if space == 0.0 || !tracks.iter().any(&track_is_affected) {
963 return;
964 }
965
966 let get_base_size = |track: &GridTrack| track.base_size;
969
970 let track_sizes: f32 = tracks.iter().map(|track| track.base_size).sum();
972 let extra_space: f32 = f32_max(0.0, space - track_sizes);
973
974 const THRESHOLD: f32 = 0.000001;
982
983 let extra_space = distribute_space_up_to_limits(
984 extra_space,
985 tracks,
986 &track_is_affected,
987 &track_distribution_proportion,
988 get_base_size,
989 &track_limit,
990 );
991
992 if extra_space > THRESHOLD {
994 let mut filter = match intrinsic_contribution_type {
999 IntrinsicContributionType::Minimum => {
1000 (|track: &GridTrack| track.max_track_sizing_function.is_intrinsic()) as fn(&GridTrack) -> bool
1001 }
1002 IntrinsicContributionType::Maximum => {
1003 use MaxTrackSizingFunction::{FitContent, MaxContent};
1004 (|track: &GridTrack| {
1005 matches!(track.max_track_sizing_function, MaxContent | FitContent(_))
1006 || track.min_track_sizing_function == MinTrackSizingFunction::MaxContent
1007 }) as fn(&GridTrack) -> bool
1008 }
1009 };
1010
1011 let number_of_tracks =
1013 tracks.iter().filter(|track| track_is_affected(track)).filter(|track| filter(track)).count();
1014 if number_of_tracks == 0 {
1015 filter = (|_| true) as fn(&GridTrack) -> bool;
1016 }
1017
1018 distribute_space_up_to_limits(
1019 extra_space,
1020 tracks,
1021 filter,
1022 &track_distribution_proportion,
1023 get_base_size,
1024 &track_limit, );
1026 }
1027
1028 for track in tracks.iter_mut() {
1031 if track.item_incurred_increase > track.base_size_planned_increase {
1032 track.base_size_planned_increase = track.item_incurred_increase;
1033 }
1034
1035 track.item_incurred_increase = 0.0;
1037 }
1038 }
1039}
1040
1041fn distribute_item_space_to_growth_limit(
1045 space: f32,
1046 tracks: &mut [GridTrack],
1047 track_is_affected: impl Fn(&GridTrack) -> bool,
1048 axis_inner_node_size: Option<f32>,
1049) {
1050 if space == 0.0 || tracks.iter().filter(|track| track_is_affected(track)).count() == 0 {
1054 return;
1055 }
1056
1057 let track_sizes: f32 = tracks
1059 .iter()
1060 .map(|track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit })
1061 .sum();
1062 let extra_space: f32 = f32_max(0.0, space - track_sizes);
1063
1064 let number_of_growable_tracks = tracks
1069 .iter()
1070 .filter(|track| track_is_affected(track))
1071 .filter(|track| {
1072 track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1073 })
1074 .count();
1075 if number_of_growable_tracks > 0 {
1076 let item_incurred_increase = extra_space / number_of_growable_tracks as f32;
1077 for track in tracks.iter_mut().filter(|track| track_is_affected(track)).filter(|track| {
1078 track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1079 }) {
1080 track.item_incurred_increase = item_incurred_increase;
1081 }
1082 } else {
1083 distribute_space_up_to_limits(
1087 extra_space,
1088 tracks,
1089 track_is_affected,
1090 |_| 1.0,
1091 |track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit },
1092 move |track| track.fit_content_limit(axis_inner_node_size),
1093 );
1094 };
1095
1096 for track in tracks.iter_mut() {
1099 if track.item_incurred_increase > track.growth_limit_planned_increase {
1100 track.growth_limit_planned_increase = track.item_incurred_increase;
1101 }
1102
1103 track.item_incurred_increase = 0.0;
1105 }
1106}
1107
1108#[inline(always)]
1111fn maximise_tracks(
1112 axis_tracks: &mut [GridTrack],
1113 axis_inner_node_size: Option<f32>,
1114 axis_available_grid_space: AvailableSpace,
1115) {
1116 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1117 let free_space = axis_available_grid_space.compute_free_space(used_space);
1118 if free_space == f32::INFINITY {
1119 axis_tracks.iter_mut().for_each(|track| track.base_size = track.growth_limit);
1120 } else if free_space > 0.0 {
1121 distribute_space_up_to_limits(
1122 free_space,
1123 axis_tracks,
1124 |_| true,
1125 |_| 1.0,
1126 |track| track.base_size,
1127 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size),
1128 );
1129 for track in axis_tracks.iter_mut() {
1130 track.base_size += track.item_incurred_increase;
1131 track.item_incurred_increase = 0.0;
1132 }
1133 }
1134}
1135
1136#[allow(clippy::too_many_arguments)]
1139#[inline(always)]
1140fn expand_flexible_tracks(
1141 tree: &mut impl PartialLayoutTree,
1142 axis: AbstractAxis,
1143 axis_tracks: &mut [GridTrack],
1144 items: &mut [GridItem],
1145 axis_min_size: Option<f32>,
1146 axis_max_size: Option<f32>,
1147 axis_available_space_for_expansion: AvailableSpace,
1148 inner_node_size: Size<Option<f32>>,
1149) {
1150 let flex_fraction = match axis_available_space_for_expansion {
1152 AvailableSpace::Definite(available_space) => {
1158 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1159 let free_space = available_space - used_space;
1160 if free_space <= 0.0 {
1161 0.0
1162 } else {
1163 find_size_of_fr(axis_tracks, available_space)
1164 }
1165 }
1166 AvailableSpace::MinContent => 0.0,
1168 AvailableSpace::MaxContent => {
1170 let flex_fraction = f32_max(
1172 axis_tracks
1175 .iter()
1176 .filter(|track| track.max_track_sizing_function.is_flexible())
1177 .map(|track| {
1178 let flex_factor = track.flex_factor();
1179 if flex_factor > 1.0 {
1180 track.base_size / flex_factor
1181 } else {
1182 track.base_size
1183 }
1184 })
1185 .max_by(|a, b| a.total_cmp(b))
1186 .unwrap_or(0.0),
1187 items
1190 .iter_mut()
1191 .filter(|item| item.crosses_flexible_track(axis))
1192 .map(|item| {
1193 let tracks = &axis_tracks[item.track_range_excluding_lines(axis)];
1194 let max_content_contribution =
1196 item.max_content_contribution_cached(axis, tree, Size::NONE, inner_node_size);
1197 find_size_of_fr(tracks, max_content_contribution)
1198 })
1199 .max_by(|a, b| a.total_cmp(b))
1200 .unwrap_or(0.0),
1201 );
1202
1203 let hypothetical_grid_size: f32 = axis_tracks
1208 .iter()
1209 .map(|track| match track.max_track_sizing_function {
1210 MaxTrackSizingFunction::Fraction(track_flex_factor) => {
1211 f32_max(track.base_size, track_flex_factor * flex_fraction)
1212 }
1213 _ => track.base_size,
1214 })
1215 .sum();
1216 let axis_min_size = axis_min_size.unwrap_or(0.0);
1217 let axis_max_size = axis_max_size.unwrap_or(f32::INFINITY);
1218 if hypothetical_grid_size < axis_min_size {
1219 find_size_of_fr(axis_tracks, axis_min_size)
1220 } else if hypothetical_grid_size > axis_max_size {
1221 find_size_of_fr(axis_tracks, axis_max_size)
1222 } else {
1223 flex_fraction
1224 }
1225 }
1226 };
1227
1228 for track in axis_tracks.iter_mut() {
1231 if let MaxTrackSizingFunction::Fraction(track_flex_factor) = track.max_track_sizing_function {
1232 track.base_size = f32_max(track.base_size, track_flex_factor * flex_fraction);
1233 }
1234 }
1235}
1236
1237#[inline(always)]
1241fn find_size_of_fr(tracks: &[GridTrack], space_to_fill: f32) -> f32 {
1242 if space_to_fill == 0.0 {
1245 return 0.0;
1246 }
1247
1248 let mut hypothetical_fr_size = f32::INFINITY;
1253 let mut previous_iter_hypothetical_fr_size;
1254 loop {
1255 let mut used_space = 0.0;
1259 let mut naive_flex_factor_sum = 0.0;
1260 for track in tracks.iter() {
1261 match track.max_track_sizing_function {
1262 MaxTrackSizingFunction::Fraction(flex_factor)
1264 if flex_factor * hypothetical_fr_size >= track.base_size =>
1265 {
1266 naive_flex_factor_sum += flex_factor;
1267 }
1268 _ => used_space += track.base_size,
1269 };
1270 }
1271 let leftover_space = space_to_fill - used_space;
1272 let flex_factor = f32_max(naive_flex_factor_sum, 1.0);
1273
1274 previous_iter_hypothetical_fr_size = hypothetical_fr_size;
1276 hypothetical_fr_size = leftover_space / flex_factor;
1277
1278 let hypotherical_fr_size_is_valid = tracks.iter().all(|track| match track.max_track_sizing_function {
1282 MaxTrackSizingFunction::Fraction(flex_factor) => {
1283 flex_factor * hypothetical_fr_size >= track.base_size
1284 || flex_factor * previous_iter_hypothetical_fr_size < track.base_size
1285 }
1286 _ => true,
1287 });
1288 if hypotherical_fr_size_is_valid {
1289 break;
1290 }
1291 }
1292
1293 hypothetical_fr_size
1295}
1296
1297#[inline(always)]
1300fn stretch_auto_tracks(
1301 axis_tracks: &mut [GridTrack],
1302 axis_min_size: Option<f32>,
1303 axis_available_space_for_expansion: AvailableSpace,
1304) {
1305 let num_auto_tracks =
1306 axis_tracks.iter().filter(|track| track.max_track_sizing_function == MaxTrackSizingFunction::Auto).count();
1307 if num_auto_tracks > 0 {
1308 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1309
1310 let free_space = if axis_available_space_for_expansion.is_definite() {
1313 axis_available_space_for_expansion.compute_free_space(used_space)
1314 } else {
1315 match axis_min_size {
1316 Some(size) => size - used_space,
1317 None => 0.0,
1318 }
1319 };
1320 if free_space > 0.0 {
1321 let extra_space_per_auto_track = free_space / num_auto_tracks as f32;
1322 axis_tracks
1323 .iter_mut()
1324 .filter(|track| track.max_track_sizing_function == MaxTrackSizingFunction::Auto)
1325 .for_each(|track| track.base_size += extra_space_per_auto_track);
1326 }
1327 }
1328}
1329
1330#[inline(always)]
1333fn distribute_space_up_to_limits(
1334 space_to_distribute: f32,
1335 tracks: &mut [GridTrack],
1336 track_is_affected: impl Fn(&GridTrack) -> bool,
1337 track_distribution_proportion: impl Fn(&GridTrack) -> f32,
1338 track_affected_property: impl Fn(&GridTrack) -> f32,
1339 track_limit: impl Fn(&GridTrack) -> f32,
1340) -> f32 {
1341 const THRESHOLD: f32 = 0.000001;
1344
1345 let mut space_to_distribute = space_to_distribute;
1346 while space_to_distribute > THRESHOLD {
1347 let track_distribution_proportion_sum: f32 = tracks
1348 .iter()
1349 .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1350 .filter(|track| track_is_affected(track))
1351 .map(&track_distribution_proportion)
1352 .sum();
1353
1354 if track_distribution_proportion_sum == 0.0 {
1355 break;
1356 }
1357
1358 let min_increase_limit = tracks
1360 .iter()
1361 .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1362 .filter(|track| track_is_affected(track))
1363 .map(|track| (track_limit(track) - track_affected_property(track)) / track_distribution_proportion(track))
1364 .min_by(|a, b| a.total_cmp(b))
1365 .unwrap(); let iteration_item_incurred_increase =
1367 f32_min(min_increase_limit, space_to_distribute / track_distribution_proportion_sum);
1368
1369 for track in tracks.iter_mut().filter(|track| track_is_affected(track)) {
1370 let increase = iteration_item_incurred_increase * track_distribution_proportion(track);
1371 if increase > 0.0 && track_affected_property(track) + increase <= track_limit(track) {
1372 track.item_incurred_increase += increase;
1373 space_to_distribute -= increase;
1374 }
1375 }
1376 }
1377
1378 space_to_distribute
1379}