1use super::types::{GridItem, GridTrack, TrackCounts};
4use crate::geometry::{AbstractAxis, Line, Size};
5use crate::style::{AlignContent, 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 IntrisicSizeMeasurer<'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> IntrisicSizeMeasurer<'_, '_, Tree, EstimateFunction>
86where
87 Tree: LayoutPartialTree,
88 EstimateFunction: Fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
89{
90 #[inline(always)]
95 fn available_space(&self, item: &mut GridItem) -> Size<Option<f32>> {
96 item.available_space_cached(
97 self.axis,
98 self.other_axis_tracks,
99 self.inner_node_size.get(self.axis.other()),
100 |track, basis| (self.get_track_size_estimate)(track, basis, self.tree),
101 )
102 }
103
104 #[inline(always)]
107 fn margins_axis_sums_with_baseline_shims(&self, item: &GridItem) -> Size<f32> {
108 item.margins_axis_sums_with_baseline_shims(self.inner_node_size.width, self.tree)
109 }
110
111 #[inline(always)]
113 fn calc(&self, val: *const (), basis: f32) -> f32 {
114 self.tree.calc(val, basis)
115 }
116
117 #[inline(always)]
119 fn min_content_contribution(&mut self, item: &mut GridItem) -> f32 {
120 let available_space = self.available_space(item);
121 let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item);
122 let contribution =
123 item.min_content_contribution_cached(self.axis, self.tree, available_space, self.inner_node_size);
124 contribution + margin_axis_sums.get(self.axis)
125 }
126
127 #[inline(always)]
129 fn max_content_contribution(&mut self, item: &mut GridItem) -> f32 {
130 let available_space = self.available_space(item);
131 let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item);
132 let contribution =
133 item.max_content_contribution_cached(self.axis, self.tree, available_space, self.inner_node_size);
134 contribution + margin_axis_sums.get(self.axis)
135 }
136
137 #[inline(always)]
145 fn minimum_contribution(&mut self, item: &mut GridItem, axis_tracks: &[GridTrack]) -> f32 {
146 let available_space = self.available_space(item);
147 let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item);
148 let contribution =
149 item.minimum_contribution_cached(self.tree, self.axis, axis_tracks, available_space, self.inner_node_size);
150 contribution + margin_axis_sums.get(self.axis)
151 }
152}
153
154#[inline(always)]
157pub(super) fn cmp_by_cross_flex_then_span_then_start(
158 axis: AbstractAxis,
159) -> impl FnMut(&GridItem, &GridItem) -> Ordering {
160 move |item_a: &GridItem, item_b: &GridItem| -> Ordering {
161 match (item_a.crosses_flexible_track(axis), item_b.crosses_flexible_track(axis)) {
162 (false, true) => Ordering::Less,
163 (true, false) => Ordering::Greater,
164 _ => {
165 let placement_a = item_a.placement(axis);
166 let placement_b = item_b.placement(axis);
167 match placement_a.span().cmp(&placement_b.span()) {
168 Ordering::Less => Ordering::Less,
169 Ordering::Greater => Ordering::Greater,
170 Ordering::Equal => placement_a.start.cmp(&placement_b.start),
171 }
172 }
173 }
174 }
175}
176
177#[inline(always)]
181pub(super) fn compute_alignment_gutter_adjustment(
182 alignment: AlignContent,
183 axis_inner_node_size: Option<f32>,
184 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
185 tracks: &[GridTrack],
186) -> f32 {
187 if tracks.len() <= 1 {
188 return 0.0;
189 }
190
191 let outer_gutter_weight = match alignment {
194 AlignContent::Start => 1,
195 AlignContent::FlexStart => 1,
196 AlignContent::End => 1,
197 AlignContent::FlexEnd => 1,
198 AlignContent::Center => 1,
199 AlignContent::Stretch => 0,
200 AlignContent::SpaceBetween => 0,
201 AlignContent::SpaceAround => 1,
202 AlignContent::SpaceEvenly => 1,
203 };
204
205 let inner_gutter_weight = match alignment {
206 AlignContent::FlexStart => 0,
207 AlignContent::Start => 0,
208 AlignContent::FlexEnd => 0,
209 AlignContent::End => 0,
210 AlignContent::Center => 0,
211 AlignContent::Stretch => 0,
212 AlignContent::SpaceBetween => 1,
213 AlignContent::SpaceAround => 2,
214 AlignContent::SpaceEvenly => 1,
215 };
216
217 if inner_gutter_weight == 0 {
218 return 0.0;
219 }
220
221 if let Some(axis_inner_node_size) = axis_inner_node_size {
222 let free_space = tracks
223 .iter()
224 .map(|track| get_track_size_estimate(track, Some(axis_inner_node_size)))
225 .sum::<Option<f32>>()
226 .map(|track_size_sum| f32_max(0.0, axis_inner_node_size - track_size_sum))
227 .unwrap_or(0.0);
228
229 let weighted_track_count =
230 (((tracks.len() - 3) / 2) * inner_gutter_weight as usize) + (2 * outer_gutter_weight as usize);
231
232 return (free_space / weighted_track_count as f32) * inner_gutter_weight as f32;
233 }
234
235 0.0
236}
237
238#[inline(always)]
240pub(super) fn resolve_item_track_indexes(items: &mut [GridItem], column_counts: TrackCounts, row_counts: TrackCounts) {
241 for item in items {
242 item.column_indexes = item.column.map(|line| line.into_track_vec_index(column_counts) as u16);
243 item.row_indexes = item.row.map(|line| line.into_track_vec_index(row_counts) as u16);
244 }
245}
246
247#[inline(always)]
249pub(super) fn determine_if_item_crosses_flexible_or_intrinsic_tracks(
250 items: &mut Vec<GridItem>,
251 columns: &[GridTrack],
252 rows: &[GridTrack],
253) {
254 for item in items {
255 item.crosses_flexible_column =
256 item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].is_flexible());
257 item.crosses_intrinsic_column =
258 item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].has_intrinsic_sizing_function());
259 item.crosses_flexible_row =
260 item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].is_flexible());
261 item.crosses_intrinsic_row =
262 item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].has_intrinsic_sizing_function());
263 }
264}
265
266#[allow(clippy::too_many_arguments)]
269pub(super) fn track_sizing_algorithm<Tree: LayoutPartialTree>(
270 tree: &mut Tree,
271 axis: AbstractAxis,
272 axis_min_size: Option<f32>,
273 axis_max_size: Option<f32>,
274 axis_alignment: AlignContent,
275 other_axis_alignment: AlignContent,
276 available_grid_space: Size<AvailableSpace>,
277 inner_node_size: Size<Option<f32>>,
278 axis_tracks: &mut [GridTrack],
279 other_axis_tracks: &mut [GridTrack],
280 items: &mut [GridItem],
281 get_track_size_estimate: fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
282 has_baseline_aligned_item: bool,
283) {
284 let percentage_basis = inner_node_size.get(axis).or(axis_min_size);
287 initialize_track_sizes(tree, axis_tracks, percentage_basis);
288
289 if has_baseline_aligned_item {
291 resolve_item_baselines(tree, axis, items, inner_node_size);
292 }
293
294 if axis_tracks.iter().all(|track| track.base_size == track.growth_limit) {
297 return;
298 }
299
300 let gutter_alignment_adjustment = compute_alignment_gutter_adjustment(
305 other_axis_alignment,
306 inner_node_size.get(axis.other()),
307 |track, basis| get_track_size_estimate(track, basis, tree),
308 other_axis_tracks,
309 );
310 if other_axis_tracks.len() > 3 {
311 let len = other_axis_tracks.len();
312 let inner_gutter_tracks = other_axis_tracks[2..len].iter_mut().step_by(2);
313 for track in inner_gutter_tracks {
314 track.content_alignment_adjustment = gutter_alignment_adjustment;
315 }
316 }
317
318 resolve_intrinsic_track_sizes(
320 tree,
321 axis,
322 axis_tracks,
323 other_axis_tracks,
324 items,
325 available_grid_space.get(axis),
326 inner_node_size,
327 get_track_size_estimate,
328 );
329
330 maximise_tracks(axis_tracks, inner_node_size.get(axis), available_grid_space.get(axis));
333
334 let axis_available_space_for_expansion = if let Some(available_space) = inner_node_size.get(axis) {
339 AvailableSpace::Definite(available_space)
340 } else {
341 match available_grid_space.get(axis) {
342 AvailableSpace::MinContent => AvailableSpace::MinContent,
343 AvailableSpace::MaxContent | AvailableSpace::Definite(_) => AvailableSpace::MaxContent,
344 }
345 };
346
347 expand_flexible_tracks(
350 tree,
351 axis,
352 axis_tracks,
353 items,
354 axis_min_size,
355 axis_max_size,
356 axis_available_space_for_expansion,
357 inner_node_size,
358 );
359
360 if axis_alignment == AlignContent::Stretch {
363 stretch_auto_tracks(axis_tracks, axis_min_size, axis_available_space_for_expansion);
364 }
365}
366
367#[derive(Copy, Clone, Debug, PartialEq, Eq)]
371enum IntrinsicContributionType {
372 Minimum,
374 Maximum,
376}
377
378#[inline(always)]
381fn flush_planned_base_size_increases(tracks: &mut [GridTrack]) {
382 for track in tracks {
383 track.base_size += track.base_size_planned_increase;
384 track.base_size_planned_increase = 0.0;
385 }
386}
387
388#[inline(always)]
391fn flush_planned_growth_limit_increases(tracks: &mut [GridTrack], set_infinitely_growable: bool) {
392 for track in tracks {
393 if track.growth_limit_planned_increase > 0.0 {
394 track.growth_limit = if track.growth_limit == f32::INFINITY {
395 track.base_size + track.growth_limit_planned_increase
396 } else {
397 track.growth_limit + track.growth_limit_planned_increase
398 };
399 track.infinitely_growable = set_infinitely_growable;
400 } else {
401 track.infinitely_growable = false;
402 }
403 track.growth_limit_planned_increase = 0.0
404 }
405}
406
407#[inline(always)]
410fn initialize_track_sizes(
411 tree: &impl LayoutPartialTree,
412 axis_tracks: &mut [GridTrack],
413 axis_inner_node_size: Option<f32>,
414) {
415 for track in axis_tracks.iter_mut() {
416 track.base_size = track
423 .min_track_sizing_function
424 .definite_value(axis_inner_node_size, |val, basis| tree.calc(val, basis))
425 .unwrap_or(0.0);
426
427 track.growth_limit = track
435 .max_track_sizing_function
436 .definite_value(axis_inner_node_size, |val, basis| tree.calc(val, basis))
437 .unwrap_or(f32::INFINITY);
438
439 if track.growth_limit < track.base_size {
441 track.growth_limit = track.base_size;
442 }
443 }
444}
445
446fn resolve_item_baselines(
448 tree: &mut impl LayoutPartialTree,
449 axis: AbstractAxis,
450 items: &mut [GridItem],
451 inner_node_size: Size<Option<f32>>,
452) {
453 let other_axis = axis.other();
456 items.sort_by_key(|item| item.placement(other_axis).start);
457
458 let mut remaining_items = &mut items[0..];
460 while !remaining_items.is_empty() {
461 let current_row = remaining_items[0].placement(other_axis).start;
463
464 let next_row_first_item =
466 remaining_items.iter().position(|item| item.placement(other_axis).start != current_row);
467
468 let row_items = if let Some(index) = next_row_first_item {
473 let (row_items, tail) = remaining_items.split_at_mut(index);
474 remaining_items = tail;
475 row_items
476 } else {
477 let row_items = remaining_items;
478 remaining_items = &mut [];
479 row_items
480 };
481
482 let row_baseline_item_count = row_items.iter().filter(|item| item.align_self == AlignSelf::Baseline).count();
486 if row_baseline_item_count <= 1 {
487 continue;
488 }
489
490 for item in row_items.iter_mut() {
492 let measured_size_and_baselines = tree.perform_child_layout(
493 item.node,
494 Size::NONE,
495 inner_node_size,
496 Size::MIN_CONTENT,
497 SizingMode::InherentSize,
498 Line::FALSE,
499 );
500
501 let baseline = measured_size_and_baselines.first_baselines.y;
502 let height = measured_size_and_baselines.size.height;
503
504 item.baseline = Some(
505 baseline.unwrap_or(height)
506 + item.margin.top.resolve_or_zero(inner_node_size.width, |val, basis| tree.calc(val, basis)),
507 );
508 }
509
510 let row_max_baseline =
512 row_items.iter().map(|item| item.baseline.unwrap_or(0.0)).max_by(|a, b| a.total_cmp(b)).unwrap();
513
514 for item in row_items.iter_mut() {
516 item.baseline_shim = row_max_baseline - item.baseline.unwrap_or(0.0);
517 }
518 }
519}
520
521#[allow(clippy::too_many_arguments)]
523fn resolve_intrinsic_track_sizes<Tree: LayoutPartialTree>(
524 tree: &mut Tree,
525 axis: AbstractAxis,
526 axis_tracks: &mut [GridTrack],
527 other_axis_tracks: &[GridTrack],
528 items: &mut [GridItem],
529 axis_available_grid_space: AvailableSpace,
530 inner_node_size: Size<Option<f32>>,
531 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
532) {
533 items.sort_by(cmp_by_cross_flex_then_span_then_start(axis));
543
544 let axis_inner_node_size = inner_node_size.get(axis);
556 let flex_factor_sum = axis_tracks.iter().map(|track| track.flex_factor()).sum::<f32>();
557 let mut item_sizer =
558 IntrisicSizeMeasurer { tree, other_axis_tracks, axis, inner_node_size, get_track_size_estimate };
559
560 let mut batched_item_iterator = ItemBatcher::new(axis);
561 while let Some((batch, is_flex)) = batched_item_iterator.next(items) {
562 let batch_span = batch[0].placement(axis).span();
565 if !is_flex && batch_span == 1 {
566 for item in batch.iter_mut() {
567 let track_index = item.placement_indexes(axis).start + 1;
568 let track = &axis_tracks[track_index as usize];
569
570 let new_base_size = match track.min_track_sizing_function.0.tag() {
572 CompactLength::MIN_CONTENT_TAG => {
573 f32_max(track.base_size, item_sizer.min_content_contribution(item))
574 }
575 CompactLength::PERCENT_TAG => {
578 if axis_inner_node_size.is_none() {
579 f32_max(track.base_size, item_sizer.min_content_contribution(item))
580 } else {
581 track.base_size
582 }
583 }
584 CompactLength::MAX_CONTENT_TAG => {
585 f32_max(track.base_size, item_sizer.max_content_contribution(item))
586 }
587 CompactLength::AUTO_TAG => {
588 let space = match axis_available_grid_space {
589 AvailableSpace::MinContent | AvailableSpace::MaxContent
598 if !item.overflow.get(axis).is_scroll_container() =>
599 {
600 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
601 let axis_min_content_size = item_sizer.min_content_contribution(item);
602 let limit = track
603 .max_track_sizing_function
604 .definite_limit(axis_inner_node_size, |val, basis| item_sizer.calc(val, basis));
605 axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
606 }
607 _ => item_sizer.minimum_contribution(item, axis_tracks),
608 };
609 f32_max(track.base_size, space)
610 }
611 CompactLength::LENGTH_TAG => {
612 track.base_size
614 }
615 #[cfg(feature = "calc")]
617 _ if track.min_track_sizing_function.0.is_calc() => {
618 if axis_inner_node_size.is_none() {
619 f32_max(track.base_size, item_sizer.min_content_contribution(item))
620 } else {
621 track.base_size
622 }
623 }
624 _ => unreachable!(),
625 };
626 let track = &mut axis_tracks[track_index as usize];
627 track.base_size = new_base_size;
628
629 if track.max_track_sizing_function.is_fit_content() {
631 if !item.overflow.get(axis).is_scroll_container() {
634 let min_content_contribution = item_sizer.min_content_contribution(item);
635 track.growth_limit_planned_increase =
636 f32_max(track.growth_limit_planned_increase, min_content_contribution);
637 }
638
639 let fit_content_limit = track.fit_content_limit(axis_inner_node_size);
642 let max_content_contribution =
643 f32_min(item_sizer.max_content_contribution(item), fit_content_limit);
644 track.growth_limit_planned_increase =
645 f32_max(track.growth_limit_planned_increase, max_content_contribution);
646 } else if track.max_track_sizing_function.is_max_content_alike()
647 || track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none()
648 {
649 track.growth_limit_planned_increase =
652 f32_max(track.growth_limit_planned_increase, item_sizer.max_content_contribution(item));
653 } else if track.max_track_sizing_function.is_intrinsic() {
654 track.growth_limit_planned_increase =
655 f32_max(track.growth_limit_planned_increase, item_sizer.min_content_contribution(item));
656 }
657 }
658
659 for track in axis_tracks.iter_mut() {
660 if track.growth_limit_planned_increase > 0.0 {
661 track.growth_limit = if track.growth_limit == f32::INFINITY {
662 track.growth_limit_planned_increase
663 } else {
664 f32_max(track.growth_limit, track.growth_limit_planned_increase)
665 };
666 }
667 track.infinitely_growable = false;
668 track.growth_limit_planned_increase = 0.0;
669 if track.growth_limit < track.base_size {
670 track.growth_limit = track.base_size;
671 }
672 }
673
674 continue;
675 }
676
677 let use_flex_factor_for_distribution = is_flex && flex_factor_sum != 0.0;
678
679 for item in batch.iter_mut().filter(|item| item.crosses_intrinsic_track(axis)) {
682 let space = match axis_available_grid_space {
692 AvailableSpace::MinContent | AvailableSpace::MaxContent
693 if !item.overflow.get(axis).is_scroll_container() =>
694 {
695 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
696 let axis_min_content_size = item_sizer.min_content_contribution(item);
697 let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size, &|val, basis| {
698 item_sizer.calc(val, basis)
699 });
700 axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
701 }
702 _ => item_sizer.minimum_contribution(item, axis_tracks),
703 };
704 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
705 if space > 0.0 {
706 let has_intrinsic_min_track_sizing_function = |track: &GridTrack| {
707 track
708 .min_track_sizing_function
709 .definite_value(axis_inner_node_size, |val, basis| item_sizer.calc(val, basis))
710 .is_none()
711 };
712 if item.overflow.get(axis).is_scroll_container() {
713 let fit_content_limit =
714 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
715 distribute_item_space_to_base_size(
716 is_flex,
717 use_flex_factor_for_distribution,
718 space,
719 tracks,
720 has_intrinsic_min_track_sizing_function,
721 fit_content_limit,
722 IntrinsicContributionType::Minimum,
723 );
724 } else {
725 distribute_item_space_to_base_size(
726 is_flex,
727 use_flex_factor_for_distribution,
728 space,
729 tracks,
730 has_intrinsic_min_track_sizing_function,
731 |track| track.growth_limit,
732 IntrinsicContributionType::Minimum,
733 );
734 }
735 }
736 }
737 flush_planned_base_size_increases(axis_tracks);
738
739 let has_min_or_max_content_min_track_sizing_function =
743 move |track: &GridTrack| track.min_track_sizing_function.is_min_or_max_content();
744 for item in batch.iter_mut() {
745 let space = item_sizer.min_content_contribution(item);
746 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
747 if space > 0.0 {
748 if item.overflow.get(axis).is_scroll_container() {
749 let fit_content_limit =
750 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
751 distribute_item_space_to_base_size(
752 is_flex,
753 use_flex_factor_for_distribution,
754 space,
755 tracks,
756 has_min_or_max_content_min_track_sizing_function,
757 fit_content_limit,
758 IntrinsicContributionType::Minimum,
759 );
760 } else {
761 distribute_item_space_to_base_size(
762 is_flex,
763 use_flex_factor_for_distribution,
764 space,
765 tracks,
766 has_min_or_max_content_min_track_sizing_function,
767 |track| track.growth_limit,
768 IntrinsicContributionType::Minimum,
769 );
770 }
771 }
772 }
773 flush_planned_base_size_increases(axis_tracks);
774
775 if axis_available_grid_space == AvailableSpace::MaxContent {
785 #[inline(always)]
794 fn has_auto_min_track_sizing_function(track: &GridTrack) -> bool {
795 track.min_track_sizing_function.is_auto() && !track.max_track_sizing_function.is_min_content()
796 }
797
798 #[inline(always)]
800 fn has_max_content_min_track_sizing_function(track: &GridTrack) -> bool {
801 track.min_track_sizing_function.is_max_content()
802 }
803
804 for item in batch.iter_mut() {
805 let axis_max_content_size = item_sizer.max_content_contribution(item);
806 let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size, &|val, basis| {
807 item_sizer.calc(val, basis)
808 });
809 let space = axis_max_content_size.maybe_min(limit);
810 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
811 if space > 0.0 {
812 if tracks.iter().any(has_max_content_min_track_sizing_function) {
823 distribute_item_space_to_base_size(
824 is_flex,
825 use_flex_factor_for_distribution,
826 space,
827 tracks,
828 has_max_content_min_track_sizing_function,
829 |_| f32::INFINITY,
830 IntrinsicContributionType::Maximum,
831 );
832 } else {
833 let fit_content_limited_growth_limit =
834 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
835 distribute_item_space_to_base_size(
836 is_flex,
837 use_flex_factor_for_distribution,
838 space,
839 tracks,
840 has_auto_min_track_sizing_function,
841 fit_content_limited_growth_limit,
842 IntrinsicContributionType::Maximum,
843 );
844 }
845 }
846 }
847 flush_planned_base_size_increases(axis_tracks);
848 }
849
850 let has_max_content_min_track_sizing_function =
853 move |track: &GridTrack| track.min_track_sizing_function.is_max_content();
854 for item in batch.iter_mut() {
855 let axis_max_content_size = item_sizer.max_content_contribution(item);
856 let space = axis_max_content_size;
857 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
858 if space > 0.0 {
859 distribute_item_space_to_base_size(
860 is_flex,
861 use_flex_factor_for_distribution,
862 space,
863 tracks,
864 has_max_content_min_track_sizing_function,
865 |track| track.growth_limit,
866 IntrinsicContributionType::Maximum,
867 );
868 }
869 }
870 flush_planned_base_size_increases(axis_tracks);
871
872 for track in axis_tracks.iter_mut() {
874 if track.growth_limit < track.base_size {
875 track.growth_limit = track.base_size;
876 }
877 }
878
879 if !is_flex {
882 let has_intrinsic_max_track_sizing_function =
885 move |track: &GridTrack| !track.max_track_sizing_function.has_definite_value(axis_inner_node_size);
886 for item in batch.iter_mut() {
887 let axis_min_content_size = item_sizer.min_content_contribution(item);
888 let space = axis_min_content_size;
889 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
890 if space > 0.0 {
891 distribute_item_space_to_growth_limit(
892 space,
893 tracks,
894 has_intrinsic_max_track_sizing_function,
895 inner_node_size.get(axis),
896 );
897 }
898 }
899 flush_planned_growth_limit_increases(axis_tracks, true);
901
902 let has_max_content_max_track_sizing_function = |track: &GridTrack| {
906 track.max_track_sizing_function.is_max_content_alike()
907 || (track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none())
908 };
909 for item in batch.iter_mut() {
910 let axis_max_content_size = item_sizer.max_content_contribution(item);
911 let space = axis_max_content_size;
912 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
913 if space > 0.0 {
914 distribute_item_space_to_growth_limit(
915 space,
916 tracks,
917 has_max_content_max_track_sizing_function,
918 inner_node_size.get(axis),
919 );
920 }
921 }
922 flush_planned_growth_limit_increases(axis_tracks, false);
924 }
925 }
926
927 axis_tracks
931 .iter_mut()
932 .filter(|track| track.growth_limit == f32::INFINITY)
933 .for_each(|track| track.growth_limit = track.base_size);
934}
935
936#[inline(always)]
939fn distribute_item_space_to_base_size(
940 is_flex: bool,
941 use_flex_factor_for_distribution: bool,
942 space: f32,
943 tracks: &mut [GridTrack],
944 track_is_affected: impl Fn(&GridTrack) -> bool,
945 track_limit: impl Fn(&GridTrack) -> f32,
946 intrinsic_contribution_type: IntrinsicContributionType,
947) {
948 if is_flex {
949 let filter = |track: &GridTrack| track.is_flexible() && track_is_affected(track);
950 if use_flex_factor_for_distribution {
951 distribute_item_space_to_base_size_inner(
952 space,
953 tracks,
954 filter,
955 |track| track.flex_factor(),
956 track_limit,
957 intrinsic_contribution_type,
958 )
959 } else {
960 distribute_item_space_to_base_size_inner(
961 space,
962 tracks,
963 filter,
964 |_| 1.0,
965 track_limit,
966 intrinsic_contribution_type,
967 )
968 }
969 } else {
970 distribute_item_space_to_base_size_inner(
971 space,
972 tracks,
973 track_is_affected,
974 |_| 1.0,
975 track_limit,
976 intrinsic_contribution_type,
977 )
978 }
979
980 fn distribute_item_space_to_base_size_inner(
983 space: f32,
984 tracks: &mut [GridTrack],
985 track_is_affected: impl Fn(&GridTrack) -> bool,
986 track_distribution_proportion: impl Fn(&GridTrack) -> f32,
987 track_limit: impl Fn(&GridTrack) -> f32,
988 intrinsic_contribution_type: IntrinsicContributionType,
989 ) {
990 if space == 0.0 || !tracks.iter().any(&track_is_affected) {
994 return;
995 }
996
997 let get_base_size = |track: &GridTrack| track.base_size;
1000
1001 let track_sizes: f32 = tracks.iter().map(|track| track.base_size).sum();
1003 let extra_space: f32 = f32_max(0.0, space - track_sizes);
1004
1005 const THRESHOLD: f32 = 0.000001;
1013
1014 let extra_space = distribute_space_up_to_limits(
1015 extra_space,
1016 tracks,
1017 &track_is_affected,
1018 &track_distribution_proportion,
1019 get_base_size,
1020 &track_limit,
1021 );
1022
1023 if extra_space > THRESHOLD {
1025 let mut filter = match intrinsic_contribution_type {
1030 IntrinsicContributionType::Minimum => {
1031 (|track: &GridTrack| track.max_track_sizing_function.is_intrinsic()) as fn(&GridTrack) -> bool
1032 }
1033 IntrinsicContributionType::Maximum => {
1034 (|track: &GridTrack| {
1035 track.min_track_sizing_function.is_max_content()
1036 || track.max_track_sizing_function.is_max_or_fit_content()
1037 }) as fn(&GridTrack) -> bool
1038 }
1039 };
1040
1041 let number_of_tracks =
1043 tracks.iter().filter(|track| track_is_affected(track)).filter(|track| filter(track)).count();
1044 if number_of_tracks == 0 {
1045 filter = (|_| true) as fn(&GridTrack) -> bool;
1046 }
1047
1048 distribute_space_up_to_limits(
1049 extra_space,
1050 tracks,
1051 filter,
1052 &track_distribution_proportion,
1053 get_base_size,
1054 &track_limit, );
1056 }
1057
1058 for track in tracks.iter_mut() {
1061 if track.item_incurred_increase > track.base_size_planned_increase {
1062 track.base_size_planned_increase = track.item_incurred_increase;
1063 }
1064
1065 track.item_incurred_increase = 0.0;
1067 }
1068 }
1069}
1070
1071fn distribute_item_space_to_growth_limit(
1075 space: f32,
1076 tracks: &mut [GridTrack],
1077 track_is_affected: impl Fn(&GridTrack) -> bool,
1078 axis_inner_node_size: Option<f32>,
1079) {
1080 if space == 0.0 || tracks.iter().filter(|track| track_is_affected(track)).count() == 0 {
1084 return;
1085 }
1086
1087 let track_sizes: f32 = tracks
1089 .iter()
1090 .map(|track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit })
1091 .sum();
1092 let extra_space: f32 = f32_max(0.0, space - track_sizes);
1093
1094 let number_of_growable_tracks = tracks
1099 .iter()
1100 .filter(|track| track_is_affected(track))
1101 .filter(|track| {
1102 track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1103 })
1104 .count();
1105 if number_of_growable_tracks > 0 {
1106 let item_incurred_increase = extra_space / number_of_growable_tracks as f32;
1107 for track in tracks.iter_mut().filter(|track| track_is_affected(track)).filter(|track| {
1108 track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1109 }) {
1110 track.item_incurred_increase = item_incurred_increase;
1111 }
1112 } else {
1113 distribute_space_up_to_limits(
1117 extra_space,
1118 tracks,
1119 track_is_affected,
1120 |_| 1.0,
1121 |track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit },
1122 move |track| track.fit_content_limit(axis_inner_node_size),
1123 );
1124 };
1125
1126 for track in tracks.iter_mut() {
1129 if track.item_incurred_increase > track.growth_limit_planned_increase {
1130 track.growth_limit_planned_increase = track.item_incurred_increase;
1131 }
1132
1133 track.item_incurred_increase = 0.0;
1135 }
1136}
1137
1138#[inline(always)]
1141fn maximise_tracks(
1142 axis_tracks: &mut [GridTrack],
1143 axis_inner_node_size: Option<f32>,
1144 axis_available_grid_space: AvailableSpace,
1145) {
1146 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1147 let free_space = axis_available_grid_space.compute_free_space(used_space);
1148 if free_space == f32::INFINITY {
1149 axis_tracks.iter_mut().for_each(|track| track.base_size = track.growth_limit);
1150 } else if free_space > 0.0 {
1151 distribute_space_up_to_limits(
1152 free_space,
1153 axis_tracks,
1154 |_| true,
1155 |_| 1.0,
1156 |track| track.base_size,
1157 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size),
1158 );
1159 for track in axis_tracks.iter_mut() {
1160 track.base_size += track.item_incurred_increase;
1161 track.item_incurred_increase = 0.0;
1162 }
1163 }
1164}
1165
1166#[allow(clippy::too_many_arguments)]
1169#[inline(always)]
1170fn expand_flexible_tracks(
1171 tree: &mut impl LayoutPartialTree,
1172 axis: AbstractAxis,
1173 axis_tracks: &mut [GridTrack],
1174 items: &mut [GridItem],
1175 axis_min_size: Option<f32>,
1176 axis_max_size: Option<f32>,
1177 axis_available_space_for_expansion: AvailableSpace,
1178 inner_node_size: Size<Option<f32>>,
1179) {
1180 let flex_fraction = match axis_available_space_for_expansion {
1182 AvailableSpace::Definite(available_space) => {
1188 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1189 let free_space = available_space - used_space;
1190 if free_space <= 0.0 {
1191 0.0
1192 } else {
1193 find_size_of_fr(axis_tracks, available_space)
1194 }
1195 }
1196 AvailableSpace::MinContent => 0.0,
1198 AvailableSpace::MaxContent => {
1200 let flex_fraction = f32_max(
1202 axis_tracks
1205 .iter()
1206 .filter(|track| track.max_track_sizing_function.is_fr())
1207 .map(|track| {
1208 let flex_factor = track.flex_factor();
1209 if flex_factor > 1.0 {
1210 track.base_size / flex_factor
1211 } else {
1212 track.base_size
1213 }
1214 })
1215 .max_by(|a, b| a.total_cmp(b))
1216 .unwrap_or(0.0),
1217 items
1220 .iter_mut()
1221 .filter(|item| item.crosses_flexible_track(axis))
1222 .map(|item| {
1223 let tracks = &axis_tracks[item.track_range_excluding_lines(axis)];
1224 let max_content_contribution =
1226 item.max_content_contribution_cached(axis, tree, Size::NONE, inner_node_size);
1227 find_size_of_fr(tracks, max_content_contribution)
1228 })
1229 .max_by(|a, b| a.total_cmp(b))
1230 .unwrap_or(0.0),
1231 );
1232
1233 let hypothetical_grid_size: f32 = axis_tracks
1238 .iter()
1239 .map(|track| {
1240 if track.max_track_sizing_function.is_fr() {
1241 let track_flex_factor = track.max_track_sizing_function.0.value();
1242 f32_max(track.base_size, track_flex_factor * flex_fraction)
1243 } else {
1244 track.base_size
1245 }
1246 })
1247 .sum();
1248 let axis_min_size = axis_min_size.unwrap_or(0.0);
1249 let axis_max_size = axis_max_size.unwrap_or(f32::INFINITY);
1250 if hypothetical_grid_size < axis_min_size {
1251 find_size_of_fr(axis_tracks, axis_min_size)
1252 } else if hypothetical_grid_size > axis_max_size {
1253 find_size_of_fr(axis_tracks, axis_max_size)
1254 } else {
1255 flex_fraction
1256 }
1257 }
1258 };
1259
1260 for track in axis_tracks.iter_mut().filter(|track| track.max_track_sizing_function.is_fr()) {
1263 let track_flex_factor = track.max_track_sizing_function.0.value();
1264 track.base_size = f32_max(track.base_size, track_flex_factor * flex_fraction);
1265 }
1266}
1267
1268#[inline(always)]
1272fn find_size_of_fr(tracks: &[GridTrack], space_to_fill: f32) -> f32 {
1273 if space_to_fill == 0.0 {
1276 return 0.0;
1277 }
1278
1279 let mut hypothetical_fr_size = f32::INFINITY;
1284 let mut previous_iter_hypothetical_fr_size;
1285 loop {
1286 let mut used_space = 0.0;
1290 let mut naive_flex_factor_sum = 0.0;
1291 for track in tracks.iter() {
1292 if track.max_track_sizing_function.is_fr()
1294 && track.max_track_sizing_function.0.value() * hypothetical_fr_size >= track.base_size
1295 {
1296 naive_flex_factor_sum += track.max_track_sizing_function.0.value();
1297 } else {
1298 used_space += track.base_size;
1299 };
1300 }
1301 let leftover_space = space_to_fill - used_space;
1302 let flex_factor = f32_max(naive_flex_factor_sum, 1.0);
1303
1304 previous_iter_hypothetical_fr_size = hypothetical_fr_size;
1306 hypothetical_fr_size = leftover_space / flex_factor;
1307
1308 let hypothetical_fr_size_is_valid = tracks.iter().all(|track| {
1312 if track.max_track_sizing_function.is_fr() {
1313 let flex_factor = track.max_track_sizing_function.0.value();
1314 flex_factor * hypothetical_fr_size >= track.base_size
1315 || flex_factor * previous_iter_hypothetical_fr_size < track.base_size
1316 } else {
1317 true
1318 }
1319 });
1320 if hypothetical_fr_size_is_valid {
1321 break;
1322 }
1323 }
1324
1325 hypothetical_fr_size
1327}
1328
1329#[inline(always)]
1332fn stretch_auto_tracks(
1333 axis_tracks: &mut [GridTrack],
1334 axis_min_size: Option<f32>,
1335 axis_available_space_for_expansion: AvailableSpace,
1336) {
1337 let num_auto_tracks = axis_tracks.iter().filter(|track| track.max_track_sizing_function.is_auto()).count();
1338 if num_auto_tracks > 0 {
1339 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1340
1341 let free_space = if axis_available_space_for_expansion.is_definite() {
1344 axis_available_space_for_expansion.compute_free_space(used_space)
1345 } else {
1346 match axis_min_size {
1347 Some(size) => size - used_space,
1348 None => 0.0,
1349 }
1350 };
1351 if free_space > 0.0 {
1352 let extra_space_per_auto_track = free_space / num_auto_tracks as f32;
1353 axis_tracks
1354 .iter_mut()
1355 .filter(|track| track.max_track_sizing_function.is_auto())
1356 .for_each(|track| track.base_size += extra_space_per_auto_track);
1357 }
1358 }
1359}
1360
1361#[inline(always)]
1364fn distribute_space_up_to_limits(
1365 space_to_distribute: f32,
1366 tracks: &mut [GridTrack],
1367 track_is_affected: impl Fn(&GridTrack) -> bool,
1368 track_distribution_proportion: impl Fn(&GridTrack) -> f32,
1369 track_affected_property: impl Fn(&GridTrack) -> f32,
1370 track_limit: impl Fn(&GridTrack) -> f32,
1371) -> f32 {
1372 const THRESHOLD: f32 = 0.01;
1375
1376 let mut space_to_distribute = space_to_distribute;
1377 while space_to_distribute > THRESHOLD {
1378 let track_distribution_proportion_sum: f32 = tracks
1379 .iter()
1380 .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1381 .filter(|track| track_is_affected(track))
1382 .map(&track_distribution_proportion)
1383 .sum();
1384
1385 if track_distribution_proportion_sum == 0.0 {
1386 break;
1387 }
1388
1389 let min_increase_limit = tracks
1391 .iter()
1392 .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1393 .filter(|track| track_is_affected(track))
1394 .map(|track| (track_limit(track) - track_affected_property(track)) / track_distribution_proportion(track))
1395 .min_by(|a, b| a.total_cmp(b))
1396 .unwrap(); let iteration_item_incurred_increase =
1398 f32_min(min_increase_limit, space_to_distribute / track_distribution_proportion_sum);
1399
1400 for track in tracks.iter_mut().filter(|track| track_is_affected(track)) {
1401 let increase = iteration_item_incurred_increase * track_distribution_proportion(track);
1402 if increase > 0.0 && track_affected_property(track) + increase <= track_limit(track) + THRESHOLD {
1403 track.item_incurred_increase += increase;
1404 space_to_distribute -= increase;
1405 }
1406 }
1407 }
1408
1409 space_to_distribute
1410}