1use super::types::{GridItem, GridTrack, TrackCounts};
4use crate::geometry::{AbstractAxis, Line, Size};
5use crate::style::{AlignContent, AlignContentKeyword, 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| {
303 track.base_size == track.growth_limit
304 && track
305 .min_track_sizing_function
306 .definite_value(percentage_basis, |val, basis| tree.calc(val, basis))
307 .is_some()
308 }) {
309 return;
310 }
311
312 let gutter_alignment_adjustment = compute_alignment_gutter_adjustment(
317 other_axis_alignment,
318 inner_node_size.get(axis.other()),
319 |track, basis| get_track_size_estimate(track, basis, tree),
320 other_axis_tracks,
321 );
322 if other_axis_tracks.len() > 3 {
323 let len = other_axis_tracks.len();
324 let inner_gutter_tracks = other_axis_tracks[2..len].iter_mut().step_by(2);
325 for track in inner_gutter_tracks {
326 track.content_alignment_adjustment = gutter_alignment_adjustment;
327 }
328 }
329
330 resolve_intrinsic_track_sizes(
332 tree,
333 axis,
334 axis_tracks,
335 other_axis_tracks,
336 items,
337 available_grid_space.get(axis),
338 inner_node_size,
339 get_track_size_estimate,
340 );
341
342 maximise_tracks(axis_tracks, inner_node_size.get(axis), available_grid_space.get(axis));
345
346 let axis_available_space_for_expansion = if let Some(available_space) = inner_node_size.get(axis) {
351 AvailableSpace::Definite(available_space)
352 } else {
353 match available_grid_space.get(axis) {
354 AvailableSpace::MinContent => AvailableSpace::MinContent,
355 AvailableSpace::MaxContent | AvailableSpace::Definite(_) => AvailableSpace::MaxContent,
356 }
357 };
358
359 expand_flexible_tracks(
362 tree,
363 axis,
364 axis_tracks,
365 items,
366 axis_min_size,
367 axis_max_size,
368 axis_available_space_for_expansion,
369 );
370
371 if axis_alignment == AlignContent::STRETCH {
374 stretch_auto_tracks(axis_tracks, axis_min_size, axis_available_space_for_expansion);
375 }
376}
377
378#[derive(Copy, Clone, Debug, PartialEq, Eq)]
382enum IntrinsicContributionType {
383 Minimum,
385 Maximum,
387}
388
389#[inline(always)]
392fn flush_planned_base_size_increases(tracks: &mut [GridTrack]) {
393 for track in tracks {
394 track.base_size += track.base_size_planned_increase;
395 track.base_size_planned_increase = 0.0;
396 }
397}
398
399#[inline(always)]
402fn flush_planned_growth_limit_increases(tracks: &mut [GridTrack], set_infinitely_growable: bool) {
403 for track in tracks {
404 if track.growth_limit_planned_increase > 0.0 {
405 track.growth_limit = if track.growth_limit == f32::INFINITY {
406 track.base_size + track.growth_limit_planned_increase
407 } else {
408 track.growth_limit + track.growth_limit_planned_increase
409 };
410 track.infinitely_growable = set_infinitely_growable;
411 } else {
412 track.infinitely_growable = false;
413 }
414 track.growth_limit_planned_increase = 0.0
415 }
416}
417
418#[inline(always)]
421fn initialize_track_sizes(
422 tree: &impl LayoutPartialTree,
423 axis_tracks: &mut [GridTrack],
424 axis_inner_node_size: Option<f32>,
425) {
426 for track in axis_tracks.iter_mut() {
427 track.base_size = track
434 .min_track_sizing_function
435 .definite_value(axis_inner_node_size, |val, basis| tree.calc(val, basis))
436 .unwrap_or(0.0);
437
438 track.growth_limit = track
446 .max_track_sizing_function
447 .definite_value(axis_inner_node_size, |val, basis| tree.calc(val, basis))
448 .unwrap_or(f32::INFINITY);
449
450 if track.growth_limit < track.base_size {
452 track.growth_limit = track.base_size;
453 }
454 }
455}
456
457fn resolve_item_baselines(
459 tree: &mut impl LayoutPartialTree,
460 axis: AbstractAxis,
461 items: &mut [GridItem],
462 inner_node_size: Size<Option<f32>>,
463) {
464 let other_axis = axis.other();
467 items.sort_by_key(|item| item.placement(other_axis).start);
468
469 let mut remaining_items = &mut items[0..];
471 while !remaining_items.is_empty() {
472 let current_row = remaining_items[0].placement(other_axis).start;
474
475 let next_row_first_item =
477 remaining_items.iter().position(|item| item.placement(other_axis).start != current_row);
478
479 let row_items = if let Some(index) = next_row_first_item {
484 let (row_items, tail) = remaining_items.split_at_mut(index);
485 remaining_items = tail;
486 row_items
487 } else {
488 let row_items = remaining_items;
489 remaining_items = &mut [];
490 row_items
491 };
492
493 let row_baseline_item_count = row_items.iter().filter(|item| item.participates_in_baseline_alignment()).count();
497 if row_baseline_item_count <= 1 {
498 continue;
499 }
500
501 for item in row_items.iter_mut() {
503 if !item.participates_in_baseline_alignment() {
504 continue;
505 }
506
507 let measured_size_and_baselines = tree.perform_child_layout(
508 item.node,
509 Size::NONE,
510 inner_node_size,
511 Size::MIN_CONTENT,
512 SizingMode::InherentSize,
513 Line::FALSE,
514 );
515
516 let baseline = measured_size_and_baselines.baselines.first;
517 let height = measured_size_and_baselines.size.height;
518
519 let baseline = if item.overflow.y.is_scroll_container() {
523 baseline.unwrap_or(height).min(height).max(0.0)
524 } else {
525 baseline.unwrap_or(height)
526 };
527
528 item.baseline = Some(
529 baseline + item.margin.top.resolve_or_zero(inner_node_size.width, |val, basis| tree.calc(val, basis)),
530 );
531 }
532
533 let row_max_baseline = row_items
535 .iter()
536 .filter(|item| item.participates_in_baseline_alignment())
537 .map(|item| item.baseline.unwrap_or(0.0))
538 .max_by(|a, b| a.total_cmp(b))
539 .unwrap();
540
541 for item in row_items.iter_mut() {
543 if item.participates_in_baseline_alignment() {
544 item.baseline_shim = row_max_baseline - item.baseline.unwrap_or(0.0);
545 }
546 }
547 }
548}
549
550#[allow(clippy::too_many_arguments)]
552fn resolve_intrinsic_track_sizes<Tree: LayoutPartialTree>(
553 tree: &mut Tree,
554 axis: AbstractAxis,
555 axis_tracks: &mut [GridTrack],
556 other_axis_tracks: &[GridTrack],
557 items: &mut [GridItem],
558 axis_available_grid_space: AvailableSpace,
559 inner_node_size: Size<Option<f32>>,
560 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>, &Tree) -> Option<f32>,
561) {
562 items.sort_by(cmp_by_cross_flex_then_span_then_start(axis));
572
573 let axis_inner_node_size = inner_node_size.get(axis);
585 let mut item_sizer =
586 IntrinsicSizeMeasurer { tree, other_axis_tracks, axis, inner_node_size, get_track_size_estimate };
587
588 let mut batched_item_iterator = ItemBatcher::new(axis);
589 while let Some((batch, is_flex)) = batched_item_iterator.next(items) {
590 let batch_span = batch[0].placement(axis).span();
593 if !is_flex && batch_span == 1 {
594 for item in batch.iter_mut() {
595 let track_index = item.placement_indexes(axis).start + 1;
596 let track = &axis_tracks[track_index as usize];
597
598 let new_base_size = match track.min_track_sizing_function.0.tag() {
600 CompactLength::MIN_CONTENT_TAG => {
601 f32_max(track.base_size, item_sizer.min_content_contribution(item, axis_tracks))
602 }
603 CompactLength::PERCENT_TAG => {
606 if axis_inner_node_size.is_none() {
607 f32_max(track.base_size, item_sizer.min_content_contribution(item, axis_tracks))
608 } else {
609 track.base_size
610 }
611 }
612 CompactLength::MAX_CONTENT_TAG => {
613 f32_max(track.base_size, item_sizer.max_content_contribution(item, axis_tracks))
614 }
615 CompactLength::AUTO_TAG => {
616 let space = match axis_available_grid_space {
617 AvailableSpace::MinContent | AvailableSpace::MaxContent
626 if !item.overflow.get(axis).is_scroll_container() =>
627 {
628 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
629 let axis_min_content_size = item_sizer.min_content_contribution(item, axis_tracks);
630 let limit = track
631 .max_track_sizing_function
632 .definite_limit(axis_inner_node_size, |val, basis| item_sizer.calc(val, basis));
633 axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
634 }
635 _ => item_sizer.minimum_contribution(item, axis_tracks),
636 };
637 f32_max(track.base_size, space)
638 }
639 CompactLength::LENGTH_TAG => {
640 track.base_size
642 }
643 #[cfg(feature = "calc")]
645 _ if track.min_track_sizing_function.0.is_calc() => {
646 if axis_inner_node_size.is_none() {
647 f32_max(track.base_size, item_sizer.min_content_contribution(item, axis_tracks))
648 } else {
649 track.base_size
650 }
651 }
652 _ => unreachable!(),
653 };
654 let growth_limit_min_content_contribution = if !item.overflow.get(axis).is_scroll_container() {
655 Some(item_sizer.min_content_contribution(item, axis_tracks))
656 } else {
657 None
658 };
659 let growth_limit_max_content_contribution = item_sizer.max_content_contribution(item, axis_tracks);
660 let growth_limit_intrinsic_min_content_contribution =
661 item_sizer.min_content_contribution(item, axis_tracks);
662 let track = &mut axis_tracks[track_index as usize];
663 track.base_size = new_base_size;
664
665 if track.max_track_sizing_function.is_fit_content() {
667 if let Some(min_content_contribution) = growth_limit_min_content_contribution {
670 track.growth_limit_planned_increase =
671 f32_max(track.growth_limit_planned_increase, min_content_contribution);
672 }
673
674 let fit_content_limit = track.fit_content_limit(axis_inner_node_size);
677 let max_content_contribution = f32_min(growth_limit_max_content_contribution, fit_content_limit);
678 track.growth_limit_planned_increase =
679 f32_max(track.growth_limit_planned_increase, max_content_contribution);
680 } else if track.max_track_sizing_function.is_max_content_alike()
681 || track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none()
682 {
683 track.growth_limit_planned_increase =
686 f32_max(track.growth_limit_planned_increase, growth_limit_max_content_contribution);
687 } else if track.max_track_sizing_function.is_intrinsic() {
688 track.growth_limit_planned_increase =
689 f32_max(track.growth_limit_planned_increase, growth_limit_intrinsic_min_content_contribution);
690 }
691 }
692
693 for track in axis_tracks.iter_mut() {
694 if track.growth_limit_planned_increase > 0.0 {
695 track.growth_limit = if track.growth_limit == f32::INFINITY {
696 track.growth_limit_planned_increase
697 } else {
698 f32_max(track.growth_limit, track.growth_limit_planned_increase)
699 };
700 }
701 track.infinitely_growable = false;
702 track.growth_limit_planned_increase = 0.0;
703 if track.growth_limit < track.base_size {
704 track.growth_limit = track.base_size;
705 }
706 }
707
708 continue;
709 }
710
711 for item in batch.iter_mut().filter(|item| item.crosses_intrinsic_track(axis)) {
714 let space = match axis_available_grid_space {
724 AvailableSpace::MinContent | AvailableSpace::MaxContent
725 if !item.overflow.get(axis).is_scroll_container() =>
726 {
727 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
728 let axis_min_content_size = item_sizer.min_content_contribution(item, axis_tracks);
729 let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size, &|val, basis| {
730 item_sizer.calc(val, basis)
731 });
732 let limited_min_content = axis_min_content_size.maybe_min(limit).max(axis_minimum_size);
733
734 if is_flex {
743 let spanned_tracks = &axis_tracks[item.track_range_excluding_lines(axis)];
744 let inflexible_sizes: f32 = spanned_tracks
745 .iter()
746 .filter(|track| !track.is_flexible())
747 .map(|track| track.base_size)
748 .sum();
749 let scale = f32_min(crossed_flex_factor_sum(spanned_tracks), 1.0);
750 let excess = f32_max(limited_min_content - inflexible_sizes, 0.0);
751 f32_max(axis_minimum_size, inflexible_sizes + excess * scale)
752 } else {
753 limited_min_content
754 }
755 }
756 _ => item_sizer.minimum_contribution(item, axis_tracks),
757 };
758 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
759 if space > 0.0 {
760 let has_intrinsic_min_track_sizing_function = |track: &GridTrack| {
761 track
762 .min_track_sizing_function
763 .definite_value(axis_inner_node_size, |val, basis| item_sizer.calc(val, basis))
764 .is_none()
765 };
766 if item.overflow.get(axis).is_scroll_container() {
767 let fit_content_limit =
768 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
769 distribute_item_space_to_base_size(
770 is_flex,
771 space,
772 tracks,
773 has_intrinsic_min_track_sizing_function,
774 fit_content_limit,
775 IntrinsicContributionType::Minimum,
776 axis_inner_node_size,
777 );
778 } else {
779 distribute_item_space_to_base_size(
780 is_flex,
781 space,
782 tracks,
783 has_intrinsic_min_track_sizing_function,
784 |track| track.growth_limit,
785 IntrinsicContributionType::Minimum,
786 axis_inner_node_size,
787 );
788 }
789 }
790 }
791 flush_planned_base_size_increases(axis_tracks);
792
793 let has_min_or_max_content_min_track_sizing_function =
797 move |track: &GridTrack| track.min_track_sizing_function.is_min_or_max_content();
798 for item in batch.iter_mut() {
799 let space = item_sizer.min_content_contribution(item, axis_tracks);
800 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
801 if space > 0.0 {
802 if item.overflow.get(axis).is_scroll_container() {
803 let fit_content_limit =
804 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
805 distribute_item_space_to_base_size(
806 is_flex,
807 space,
808 tracks,
809 has_min_or_max_content_min_track_sizing_function,
810 fit_content_limit,
811 IntrinsicContributionType::Minimum,
812 axis_inner_node_size,
813 );
814 } else {
815 distribute_item_space_to_base_size(
816 is_flex,
817 space,
818 tracks,
819 has_min_or_max_content_min_track_sizing_function,
820 |track| track.growth_limit,
821 IntrinsicContributionType::Minimum,
822 axis_inner_node_size,
823 );
824 }
825 }
826 }
827 flush_planned_base_size_increases(axis_tracks);
828
829 if axis_available_grid_space == AvailableSpace::MaxContent {
839 #[inline(always)]
848 fn has_auto_min_track_sizing_function(track: &GridTrack) -> bool {
849 track.min_track_sizing_function.is_auto() && !track.max_track_sizing_function.is_min_content()
850 }
851
852 #[inline(always)]
854 fn has_max_content_min_track_sizing_function(track: &GridTrack) -> bool {
855 track.min_track_sizing_function.is_max_content()
856 }
857
858 for item in batch.iter_mut() {
859 let axis_max_content_size = item_sizer.max_content_contribution(item, axis_tracks);
860 let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size, &|val, basis| {
861 item_sizer.calc(val, basis)
862 });
863 let mut space = axis_max_content_size.maybe_min(limit);
864
865 if is_flex {
870 let spanned_tracks = &axis_tracks[item.track_range_excluding_lines(axis)];
871 let inflexible_sizes: f32 =
872 spanned_tracks.iter().filter(|track| !track.is_flexible()).map(|track| track.base_size).sum();
873 let scale = f32_min(crossed_flex_factor_sum(spanned_tracks), 1.0);
874 space = inflexible_sizes + f32_max(space - inflexible_sizes, 0.0) * scale;
875 }
876 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
877 if space > 0.0 {
878 if tracks.iter().any(has_max_content_min_track_sizing_function) {
889 distribute_item_space_to_base_size(
890 is_flex,
891 space,
892 tracks,
893 has_max_content_min_track_sizing_function,
894 |_| f32::INFINITY,
895 IntrinsicContributionType::Maximum,
896 axis_inner_node_size,
897 );
898 } else {
899 let fit_content_limited_growth_limit =
900 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
901 distribute_item_space_to_base_size(
902 is_flex,
903 space,
904 tracks,
905 has_auto_min_track_sizing_function,
906 fit_content_limited_growth_limit,
907 IntrinsicContributionType::Maximum,
908 axis_inner_node_size,
909 );
910 }
911 }
912 }
913 flush_planned_base_size_increases(axis_tracks);
914 }
915
916 let has_max_content_min_track_sizing_function =
919 move |track: &GridTrack| track.min_track_sizing_function.is_max_content();
920 for item in batch.iter_mut() {
921 let axis_max_content_size = item_sizer.max_content_contribution(item, axis_tracks);
922 let space = axis_max_content_size;
923 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
924 if space > 0.0 {
925 distribute_item_space_to_base_size(
926 is_flex,
927 space,
928 tracks,
929 has_max_content_min_track_sizing_function,
930 |track| track.growth_limit,
931 IntrinsicContributionType::Maximum,
932 axis_inner_node_size,
933 );
934 }
935 }
936 flush_planned_base_size_increases(axis_tracks);
937
938 for track in axis_tracks.iter_mut() {
940 if track.growth_limit < track.base_size {
941 track.growth_limit = track.base_size;
942 }
943 }
944
945 if !is_flex {
948 let has_intrinsic_max_track_sizing_function =
951 move |track: &GridTrack| !track.max_track_sizing_function.has_definite_value(axis_inner_node_size);
952 for item in batch.iter_mut() {
953 let axis_min_content_size = item_sizer.min_content_contribution(item, axis_tracks);
954 let space = axis_min_content_size;
955 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
956 if space > 0.0 {
957 distribute_item_space_to_growth_limit(
958 space,
959 tracks,
960 has_intrinsic_max_track_sizing_function,
961 inner_node_size.get(axis),
962 );
963 }
964 }
965 flush_planned_growth_limit_increases(axis_tracks, true);
967
968 let has_max_content_max_track_sizing_function = |track: &GridTrack| {
972 track.max_track_sizing_function.is_max_content_alike()
973 || (track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none())
974 };
975 for item in batch.iter_mut() {
976 let axis_max_content_size = item_sizer.max_content_contribution(item, axis_tracks);
977 let space = axis_max_content_size;
978 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
979 if space > 0.0 {
980 distribute_item_space_to_growth_limit(
981 space,
982 tracks,
983 has_max_content_max_track_sizing_function,
984 inner_node_size.get(axis),
985 );
986 }
987 }
988 flush_planned_growth_limit_increases(axis_tracks, false);
990 }
991 }
992
993 axis_tracks
997 .iter_mut()
998 .filter(|track| track.growth_limit == f32::INFINITY)
999 .for_each(|track| track.growth_limit = track.base_size);
1000}
1001
1002#[inline(always)]
1004fn crossed_flex_factor_sum(tracks: &[GridTrack]) -> f32 {
1005 tracks.iter().filter(|track| track.is_flexible()).map(|track| track.flex_factor()).sum()
1006}
1007
1008#[inline(always)]
1011fn distribute_item_space_to_base_size(
1012 is_flex: bool,
1013 space: f32,
1014 tracks: &mut [GridTrack],
1015 track_is_affected: impl Fn(&GridTrack) -> bool,
1016 track_limit: impl Fn(&GridTrack) -> f32,
1017 intrinsic_contribution_type: IntrinsicContributionType,
1018 axis_inner_node_size: Option<f32>,
1019) {
1020 if is_flex {
1021 let filter = |track: &GridTrack| track.is_flexible() && track_is_affected(track);
1022
1023 let flex_factor_sum: f32 = tracks.iter().filter(|track| filter(track)).map(|track| track.flex_factor()).sum();
1031 if flex_factor_sum > 0.0 {
1032 distribute_item_space_to_base_size_inner(
1033 space,
1034 tracks,
1035 filter,
1036 |track| track.flex_factor(),
1037 track_limit,
1038 intrinsic_contribution_type,
1039 axis_inner_node_size,
1040 )
1041 } else {
1042 distribute_item_space_to_base_size_inner(
1043 space,
1044 tracks,
1045 filter,
1046 |_| 1.0,
1047 track_limit,
1048 intrinsic_contribution_type,
1049 axis_inner_node_size,
1050 )
1051 }
1052 } else {
1053 distribute_item_space_to_base_size_inner(
1054 space,
1055 tracks,
1056 track_is_affected,
1057 |_| 1.0,
1058 track_limit,
1059 intrinsic_contribution_type,
1060 axis_inner_node_size,
1061 )
1062 }
1063
1064 fn distribute_item_space_to_base_size_inner(
1067 space: f32,
1068 tracks: &mut [GridTrack],
1069 track_is_affected: impl Fn(&GridTrack) -> bool,
1070 track_distribution_proportion: impl Fn(&GridTrack) -> f32,
1071 track_limit: impl Fn(&GridTrack) -> f32,
1072 intrinsic_contribution_type: IntrinsicContributionType,
1073 axis_inner_node_size: Option<f32>,
1074 ) {
1075 if space == 0.0 || !tracks.iter().any(&track_is_affected) {
1079 return;
1080 }
1081
1082 let get_base_size = |track: &GridTrack| track.base_size;
1085
1086 let track_sizes: f32 = tracks.iter().map(|track| track.base_size).sum();
1088 let extra_space: f32 = f32_max(0.0, space - track_sizes);
1089
1090 const THRESHOLD: f32 = 0.000001;
1098
1099 let extra_space = distribute_space_up_to_limits(
1100 extra_space,
1101 tracks,
1102 &track_is_affected,
1103 &track_distribution_proportion,
1104 get_base_size,
1105 &track_limit,
1106 );
1107
1108 if extra_space > THRESHOLD {
1110 let mut filter = match intrinsic_contribution_type {
1115 IntrinsicContributionType::Minimum => {
1116 (|track: &GridTrack| track.max_track_sizing_function.is_intrinsic()) as fn(&GridTrack) -> bool
1117 }
1118 IntrinsicContributionType::Maximum => {
1119 (|track: &GridTrack| track.max_track_sizing_function.is_max_or_fit_content())
1120 as fn(&GridTrack) -> bool
1121 }
1122 };
1123
1124 let number_of_tracks =
1126 tracks.iter().filter(|track| track_is_affected(track)).filter(|track| filter(track)).count();
1127 if number_of_tracks == 0 {
1128 filter = (|_| true) as fn(&GridTrack) -> bool;
1129 }
1130
1131 distribute_space_up_to_limits(
1134 extra_space,
1135 tracks,
1136 |track| track_is_affected(track) && filter(track),
1137 &track_distribution_proportion,
1138 get_base_size,
1139 |track| track.fit_content_limit(axis_inner_node_size),
1140 );
1141 }
1142
1143 for track in tracks.iter_mut() {
1146 if track.item_incurred_increase > track.base_size_planned_increase {
1147 track.base_size_planned_increase = track.item_incurred_increase;
1148 }
1149
1150 track.item_incurred_increase = 0.0;
1152 }
1153 }
1154}
1155
1156fn distribute_item_space_to_growth_limit(
1160 space: f32,
1161 tracks: &mut [GridTrack],
1162 track_is_affected: impl Fn(&GridTrack) -> bool,
1163 axis_inner_node_size: Option<f32>,
1164) {
1165 if space == 0.0 || tracks.iter().filter(|track| track_is_affected(track)).count() == 0 {
1169 return;
1170 }
1171
1172 let track_sizes: f32 = tracks
1174 .iter()
1175 .map(|track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit })
1176 .sum();
1177 let extra_space: f32 = f32_max(0.0, space - track_sizes);
1178
1179 let number_of_growable_tracks = tracks
1184 .iter()
1185 .filter(|track| track_is_affected(track))
1186 .filter(|track| {
1187 track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1188 })
1189 .count();
1190 if number_of_growable_tracks > 0 {
1191 let item_incurred_increase = extra_space / number_of_growable_tracks as f32;
1192 for track in tracks.iter_mut().filter(|track| track_is_affected(track)).filter(|track| {
1193 track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1194 }) {
1195 track.item_incurred_increase = item_incurred_increase;
1196 }
1197 } else {
1198 distribute_space_up_to_limits(
1202 extra_space,
1203 tracks,
1204 track_is_affected,
1205 |_| 1.0,
1206 |track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit },
1207 move |track| track.fit_content_limit(axis_inner_node_size),
1208 );
1209 };
1210
1211 for track in tracks.iter_mut() {
1214 if track.item_incurred_increase > track.growth_limit_planned_increase {
1215 track.growth_limit_planned_increase = track.item_incurred_increase;
1216 }
1217
1218 track.item_incurred_increase = 0.0;
1220 }
1221}
1222
1223#[inline(always)]
1226fn maximise_tracks(
1227 axis_tracks: &mut [GridTrack],
1228 axis_inner_node_size: Option<f32>,
1229 axis_available_grid_space: AvailableSpace,
1230) {
1231 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1232 let free_space = axis_available_grid_space.compute_free_space(used_space);
1233 if free_space == f32::INFINITY {
1234 axis_tracks.iter_mut().for_each(|track| track.base_size = track.growth_limit);
1235 } else if free_space > 0.0 {
1236 distribute_space_up_to_limits(
1237 free_space,
1238 axis_tracks,
1239 |_| true,
1240 |_| 1.0,
1241 |track| track.base_size,
1242 move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size),
1243 );
1244 for track in axis_tracks.iter_mut() {
1245 track.base_size += track.item_incurred_increase;
1246 track.item_incurred_increase = 0.0;
1247 }
1248 }
1249}
1250
1251#[allow(clippy::too_many_arguments)]
1254#[inline(always)]
1255fn expand_flexible_tracks(
1256 tree: &mut impl LayoutPartialTree,
1257 axis: AbstractAxis,
1258 axis_tracks: &mut [GridTrack],
1259 items: &mut [GridItem],
1260 axis_min_size: Option<f32>,
1261 axis_max_size: Option<f32>,
1262 axis_available_space_for_expansion: AvailableSpace,
1263) {
1264 let flex_fraction = match axis_available_space_for_expansion {
1266 AvailableSpace::Definite(available_space) => {
1272 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1273 let free_space = available_space - used_space;
1274 if free_space <= 0.0 {
1275 0.0
1276 } else {
1277 find_size_of_fr(axis_tracks, available_space)
1278 }
1279 }
1280 AvailableSpace::MinContent => 0.0,
1282 AvailableSpace::MaxContent => {
1284 let flex_fraction = f32_max(
1286 axis_tracks
1289 .iter()
1290 .filter(|track| track.max_track_sizing_function.is_fr())
1291 .map(|track| {
1292 let flex_factor = track.flex_factor();
1293 if flex_factor > 1.0 {
1294 track.base_size / flex_factor
1295 } else {
1296 track.base_size
1297 }
1298 })
1299 .max_by(|a, b| a.total_cmp(b))
1300 .unwrap_or(0.0),
1301 items
1304 .iter_mut()
1305 .filter(|item| item.crosses_flexible_track(axis))
1306 .map(|item| {
1307 let tracks = &axis_tracks[item.track_range_excluding_lines(axis)];
1308 let max_content_contribution =
1310 item.max_content_contribution_cached(axis, tree, Size::NONE, Size::NONE);
1311 find_size_of_fr(tracks, max_content_contribution)
1312 })
1313 .max_by(|a, b| a.total_cmp(b))
1314 .unwrap_or(0.0),
1315 );
1316
1317 let hypothetical_grid_size: f32 = axis_tracks
1322 .iter()
1323 .map(|track| {
1324 if track.max_track_sizing_function.is_fr() {
1325 let track_flex_factor = track.max_track_sizing_function.0.value();
1326 f32_max(track.base_size, track_flex_factor * flex_fraction)
1327 } else {
1328 track.base_size
1329 }
1330 })
1331 .sum();
1332 let axis_min_size = axis_min_size.unwrap_or(0.0);
1333 let axis_max_size = axis_max_size.unwrap_or(f32::INFINITY);
1334 if hypothetical_grid_size < axis_min_size {
1335 find_size_of_fr(axis_tracks, axis_min_size)
1336 } else if hypothetical_grid_size > axis_max_size {
1337 find_size_of_fr(axis_tracks, axis_max_size)
1338 } else {
1339 flex_fraction
1340 }
1341 }
1342 };
1343
1344 for track in axis_tracks.iter_mut().filter(|track| track.max_track_sizing_function.is_fr()) {
1347 let track_flex_factor = track.max_track_sizing_function.0.value();
1348 track.base_size = f32_max(track.base_size, track_flex_factor * flex_fraction);
1349 }
1350}
1351
1352#[inline(always)]
1356fn find_size_of_fr(tracks: &[GridTrack], space_to_fill: f32) -> f32 {
1357 if space_to_fill == 0.0 {
1360 return 0.0;
1361 }
1362
1363 let mut hypothetical_fr_size = f32::INFINITY;
1368 let mut previous_iter_hypothetical_fr_size;
1369 let max_iterations = tracks.len() + 1;
1374 for _ in 0..max_iterations {
1375 let mut used_space = 0.0;
1379 let mut naive_flex_factor_sum = 0.0;
1380 for track in tracks.iter() {
1381 if track.max_track_sizing_function.is_fr()
1383 && track.max_track_sizing_function.0.value() * hypothetical_fr_size >= track.base_size
1384 {
1385 naive_flex_factor_sum += track.max_track_sizing_function.0.value();
1386 } else {
1387 used_space += track.base_size;
1388 };
1389 }
1390 let leftover_space = space_to_fill - used_space;
1391 let flex_factor = f32_max(naive_flex_factor_sum, 1.0);
1392
1393 previous_iter_hypothetical_fr_size = hypothetical_fr_size;
1395 hypothetical_fr_size = leftover_space / flex_factor;
1396
1397 let hypothetical_fr_size_is_valid = tracks.iter().all(|track| {
1401 if track.max_track_sizing_function.is_fr() {
1402 let flex_factor = track.max_track_sizing_function.0.value();
1403 flex_factor * hypothetical_fr_size >= track.base_size
1404 || flex_factor * previous_iter_hypothetical_fr_size < track.base_size
1405 } else {
1406 true
1407 }
1408 });
1409 if hypothetical_fr_size_is_valid {
1410 break;
1411 }
1412 }
1413
1414 hypothetical_fr_size
1416}
1417
1418#[inline(always)]
1421fn stretch_auto_tracks(
1422 axis_tracks: &mut [GridTrack],
1423 axis_min_size: Option<f32>,
1424 axis_available_space_for_expansion: AvailableSpace,
1425) {
1426 let num_auto_tracks = axis_tracks.iter().filter(|track| track.max_track_sizing_function.is_auto()).count();
1427 if num_auto_tracks > 0 {
1428 let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1429
1430 let free_space = if axis_available_space_for_expansion.is_definite() {
1433 axis_available_space_for_expansion.compute_free_space(used_space)
1434 } else {
1435 match axis_min_size {
1436 Some(size) => size - used_space,
1437 None => 0.0,
1438 }
1439 };
1440 if free_space > 0.0 {
1441 let extra_space_per_auto_track = free_space / num_auto_tracks as f32;
1442 axis_tracks
1443 .iter_mut()
1444 .filter(|track| track.max_track_sizing_function.is_auto())
1445 .for_each(|track| track.base_size += extra_space_per_auto_track);
1446 }
1447 }
1448}
1449
1450#[inline(always)]
1453fn distribute_space_up_to_limits(
1454 space_to_distribute: f32,
1455 tracks: &mut [GridTrack],
1456 track_is_affected: impl Fn(&GridTrack) -> bool,
1457 track_distribution_proportion: impl Fn(&GridTrack) -> f32,
1458 track_affected_property: impl Fn(&GridTrack) -> f32,
1459 track_limit: impl Fn(&GridTrack) -> f32,
1460) -> f32 {
1461 const THRESHOLD: f32 = 0.01;
1464
1465 let max_iterations = tracks.len() + 1;
1469
1470 let mut space_to_distribute = space_to_distribute;
1471 for _ in 0..max_iterations {
1472 if space_to_distribute <= THRESHOLD {
1473 break;
1474 }
1475 let track_distribution_proportion_sum: f32 = tracks
1476 .iter()
1477 .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1478 .filter(|track| track_is_affected(track))
1479 .map(&track_distribution_proportion)
1480 .sum();
1481
1482 if track_distribution_proportion_sum == 0.0 {
1483 break;
1484 }
1485
1486 let min_increase_limit = tracks
1488 .iter()
1489 .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1490 .filter(|track| track_is_affected(track))
1491 .map(|track| {
1492 (track_limit(track) - track_affected_property(track) - track.item_incurred_increase)
1493 / track_distribution_proportion(track)
1494 })
1495 .min_by(|a, b| a.total_cmp(b))
1496 .unwrap(); let iteration_item_incurred_increase =
1498 f32_min(min_increase_limit, space_to_distribute / track_distribution_proportion_sum);
1499
1500 for track in tracks.iter_mut().filter(|track| track_is_affected(track)) {
1501 let increase = iteration_item_incurred_increase * track_distribution_proportion(track);
1502 if increase > 0.0
1503 && track_affected_property(track) + track.item_incurred_increase + increase
1504 <= track_limit(track) + THRESHOLD
1505 {
1506 track.item_incurred_increase += increase;
1507 space_to_distribute -= increase;
1508 }
1509 }
1510 }
1511
1512 space_to_distribute
1513}