1use crate::geometry::{AbsoluteAxis, AbstractAxis, InBothAbsAxis};
4use crate::geometry::{Line, Point, Rect, Size};
5use crate::style::{AlignContent, AlignItems, AvailableSpace, Contain, ContainingBlockClaims, Overflow};
6use crate::tree::{
7 AxisStaticPosition, Baselines, Layout, LayoutInput, LayoutOutput, LayoutPartialTreeExt, NodeId, OofCandidate,
8 OofCandidates, OofPositioningArea, RunMode, SizingMode,
9};
10use crate::util::debug::debug_log;
11use crate::util::sys::{f32_max, f32_min, GridTrackVec, Vec};
12use crate::util::MaybeMath;
13use crate::util::{MaybeResolve, ResolveOrZero};
14use crate::{
15 style_helpers::*, BoxGenerationMode, BoxSizing, CoreStyle, Direction, GridContainerStyle, GridItemStyle,
16 LayoutGridContainer, RequestedAxis,
17};
18use alignment::{align_and_position_item, align_tracks};
19use explicit_grid::{compute_explicit_grid_size_in_axis, initialize_grid_tracks, AutoRepeatStrategy};
20use implicit_grid::compute_grid_size_estimate;
21use placement::{place_grid_items, ItemPlacement};
22use track_sizing::{
23 determine_if_item_crosses_flexible_or_intrinsic_tracks, resolve_item_track_indexes, track_sizing_algorithm,
24};
25use types::{CellOccupancyMatrix, GridAreasMap, GridTrack, NamedLineResolver};
26
27use crate::sys::{DefaultCheapStr, String};
28use crate::{CheapCloneStr, GridAreaAxis, GridPlacement};
29use core::ops::Range;
30use types::{GridItem, GridTrackKind, TrackCounts};
31
32pub(crate) use types::{GridCoordinate, GridLine, OriginZeroLine, MAX_GRID_TRACKS, MAX_OZ_LINE, MIN_OZ_LINE};
33
34pub use types::{GridLineNames, GridLineNamesIter};
35
36mod alignment;
37mod explicit_grid;
38mod implicit_grid;
39mod placement;
40mod track_sizing;
41mod types;
42mod util;
43#[derive(Clone, Copy)]
46struct GridContainerConstants {
47 direction: Direction,
49 contain: Contain,
51 containing_block_claims: ContainingBlockClaims,
53 padding: Rect<f32>,
55 border: Rect<f32>,
57 padding_border_size: Size<f32>,
59 min_size: Size<Option<f32>>,
61 max_size: Size<Option<f32>>,
63 preferred_size: Size<Option<f32>>,
65 scrollbar_gutter: Point<f32>,
67 #[cfg(feature = "content_size")]
69 is_scroll_container: bool,
70 content_box_inset: Rect<f32>,
72 align_content: AlignContent,
74 justify_content: AlignContent,
76 align_items: AlignItems,
78 justify_items: AlignItems,
80 available_grid_space: Size<AvailableSpace>,
82 outer_node_size: Size<Option<f32>>,
84 inner_min_size: Size<Option<f32>>,
86 inner_max_size: Size<Option<f32>>,
88 inner_node_size: Size<Option<f32>>,
90}
91
92fn compute_container_constants<Tree: LayoutGridContainer>(
94 tree: &Tree,
95 style: &Tree::GridContainerStyle<'_>,
96 inputs: LayoutInput,
97) -> GridContainerConstants {
98 let LayoutInput { known_dimensions, parent_size, available_space, .. } = inputs;
99
100 let direction = style.direction();
101 let contain = style.contain();
102 let containing_block_claims = style.is_containing_block();
103
104 let aspect_ratio = style.aspect_ratio();
107 let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
108 let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
109 let padding_border = padding + border;
110 let padding_border_size = padding_border.sum_axes();
111 let box_sizing_adjustment =
112 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
113
114 let min_size = style
115 .min_size()
116 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
117 .maybe_apply_aspect_ratio(aspect_ratio)
118 .maybe_add(box_sizing_adjustment);
119 let max_size = style
120 .max_size()
121 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
122 .maybe_apply_aspect_ratio(aspect_ratio)
123 .maybe_add(box_sizing_adjustment);
124 let preferred_size = if inputs.sizing_mode == SizingMode::InherentSize {
125 style
126 .size()
127 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
128 .maybe_apply_aspect_ratio(style.aspect_ratio())
129 .maybe_add(box_sizing_adjustment)
130 } else {
131 Size::NONE
132 };
133
134 let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
138 Overflow::Scroll => style.scrollbar_width(),
139 _ => 0.0,
140 });
141 #[cfg(feature = "content_size")]
142 let is_scroll_container = {
143 let overflow = style.overflow();
144 overflow.x.is_scroll_container() || overflow.y.is_scroll_container()
145 };
146 let mut content_box_inset = padding_border;
147 content_box_inset.bottom += scrollbar_gutter.y;
148
149 match direction {
150 Direction::Ltr => content_box_inset.right += scrollbar_gutter.x,
151 Direction::Rtl => content_box_inset.left += scrollbar_gutter.x,
152 };
153
154 let align_content = style.grid_align_content(AbstractAxis::Block);
156 let justify_content = style.grid_align_content(AbstractAxis::Inline);
157 let align_items = style.align_items();
158 let justify_items = style.justify_items();
159 let constrained_available_space = known_dimensions
160 .or(preferred_size)
161 .map(|size| size.map(AvailableSpace::Definite))
162 .unwrap_or(available_space)
163 .maybe_clamp(min_size, max_size)
164 .maybe_max(padding_border_size);
165
166 let available_grid_space = Size {
167 width: constrained_available_space
168 .width
169 .map_definite_value(|space| space - content_box_inset.horizontal_axis_sum()),
170 height: constrained_available_space
171 .height
172 .map_definite_value(|space| space - content_box_inset.vertical_axis_sum()),
173 };
174
175 let outer_node_size =
176 known_dimensions.or(preferred_size).maybe_clamp(min_size, max_size).maybe_max(padding_border_size);
177
178 let inner_min_size = min_size.maybe_sub(content_box_inset.sum_axes());
181 let inner_max_size = max_size.maybe_sub(content_box_inset.sum_axes());
182 let inner_node_size = Size {
183 width: outer_node_size.width.map(|space| space - content_box_inset.horizontal_axis_sum()),
184 height: outer_node_size.height.map(|space| space - content_box_inset.vertical_axis_sum()),
185 };
186
187 GridContainerConstants {
188 direction,
189 contain,
190 containing_block_claims,
191 padding,
192 border,
193 padding_border_size,
194 min_size,
195 max_size,
196 preferred_size,
197 scrollbar_gutter,
198 #[cfg(feature = "content_size")]
199 is_scroll_container,
200 content_box_inset,
201 align_content,
202 justify_content,
203 align_items,
204 justify_items,
205 available_grid_space,
206 outer_node_size,
207 inner_min_size,
208 inner_max_size,
209 inner_node_size,
210 }
211}
212
213pub fn compute_grid_layout<Tree: LayoutGridContainer>(
220 tree: &mut Tree,
221 node: NodeId,
222 inputs: LayoutInput,
223) -> LayoutOutput {
224 let LayoutInput { known_dimensions, run_mode, .. } = inputs;
225
226 let style = tree.get_grid_container_style(node);
227 let constants = compute_container_constants(tree, &style, inputs);
228 let GridContainerConstants {
229 direction,
230 contain,
231 containing_block_claims,
232 padding,
233 border,
234 padding_border_size,
235 min_size,
236 max_size,
237 preferred_size,
238 scrollbar_gutter,
239 #[cfg(feature = "content_size")]
240 is_scroll_container,
241 content_box_inset,
242 align_content,
243 justify_content,
244 align_items,
245 justify_items,
246 available_grid_space,
247 outer_node_size,
248 inner_min_size,
249 inner_max_size,
250 mut inner_node_size,
251 } = constants;
252
253 let grid_template_columns = style.grid_template_columns();
256 let grid_template_rows = style.grid_template_rows();
257 let grid_auto_columns = style.grid_auto_columns();
258 let grid_auto_rows = style.grid_auto_rows();
259
260 debug_log!("parent_size", dbg:inputs.parent_size);
261 debug_log!("outer_node_size", dbg:outer_node_size);
262 debug_log!("inner_node_size", dbg:inner_node_size);
263
264 if run_mode == RunMode::ComputeSize {
267 if let Size { width: Some(width), height: Some(height) } = outer_node_size {
268 return LayoutOutput::from_outer_size(Size { width, height });
269 }
270
271 if inputs.axis == RequestedAxis::Horizontal {
273 if let Some(width) = outer_node_size.width {
274 return LayoutOutput::from_outer_size(Size { width, height: 0.0 });
275 }
276 }
277 }
278
279 let auto_fit_container_size = outer_node_size
284 .or(max_size)
285 .or(min_size)
286 .maybe_clamp(min_size, max_size)
287 .maybe_max(padding_border_size)
288 .maybe_sub(content_box_inset.sum_axes());
289
290 let auto_repeat_fit_strategy = outer_node_size.or(max_size).map(|val| match val {
297 Some(_) => AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
298 None => AutoRepeatStrategy::MinRepetitionsThatDoOverflow,
299 });
300
301 let (col_auto_repetition_count, grid_template_col_count) = compute_explicit_grid_size_in_axis(
304 &style,
305 auto_fit_container_size.width,
306 auto_repeat_fit_strategy.width,
307 |val, basis| tree.calc(val, basis),
308 AbsoluteAxis::Horizontal,
309 );
310 let (row_auto_repetition_count, grid_template_row_count) = compute_explicit_grid_size_in_axis(
311 &style,
312 auto_fit_container_size.height,
313 auto_repeat_fit_strategy.height,
314 |val, basis| tree.calc(val, basis),
315 AbsoluteAxis::Vertical,
316 );
317
318 let mut name_resolver = NamedLineResolver::new(&style, col_auto_repetition_count, row_auto_repetition_count);
320
321 let explicit_col_count = grid_template_col_count.max(name_resolver.area_column_count()).min(MAX_GRID_TRACKS);
324 let explicit_row_count = grid_template_row_count.max(name_resolver.area_row_count()).min(MAX_GRID_TRACKS);
325
326 name_resolver.set_explicit_column_count(explicit_col_count);
327 name_resolver.set_explicit_row_count(explicit_row_count);
328
329 let mut detailed_column_line_names = name_resolver.detailed_line_names(AbsoluteAxis::Horizontal);
331 let mut detailed_row_line_names = name_resolver.detailed_line_names(AbsoluteAxis::Vertical);
332
333 let child_count = tree.child_count(node);
338 let mut items: Vec<GridItem> = Vec::with_capacity(child_count);
339 let mut placements: Vec<ItemPlacement> = Vec::with_capacity(child_count);
340 for (index, child_node) in tree.child_ids(node).enumerate() {
341 let child_style = tree.get_grid_child_style(child_node);
342 if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position().is_out_of_flow() {
343 continue;
344 }
345 placements.push(InBothAbsAxis {
346 horizontal: name_resolver
347 .resolve_column_names(&child_style.grid_column())
348 .into_origin_zero(explicit_col_count),
349 vertical: name_resolver.resolve_row_names(&child_style.grid_row()).into_origin_zero(explicit_row_count),
350 });
351 items.push(GridItem::new_with_style_and_order(
352 child_node,
353 child_style,
354 align_items,
355 justify_items,
356 index as u16,
357 ));
358 }
359
360 let (est_col_counts, est_row_counts) =
364 compute_grid_size_estimate(explicit_col_count, explicit_row_count, &placements);
365
366 let mut cell_occupancy_matrix = CellOccupancyMatrix::with_track_counts(est_col_counts, est_row_counts);
369 place_grid_items(&mut cell_occupancy_matrix, &mut items, &placements, style.grid_auto_flow());
372
373 let final_col_counts = *cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal);
375 let final_row_counts = *cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical);
376
377 let mut columns = GridTrackVec::new();
381 let mut rows = GridTrackVec::new();
382 initialize_grid_tracks(
383 &mut columns,
384 final_col_counts,
385 &style,
386 AbsoluteAxis::Horizontal,
387 col_auto_repetition_count,
388 |column_index| cell_occupancy_matrix.column_is_occupied(column_index),
389 );
390 initialize_grid_tracks(
391 &mut rows,
392 final_row_counts,
393 &style,
394 AbsoluteAxis::Vertical,
395 row_auto_repetition_count,
396 |row_index| cell_occupancy_matrix.row_is_occupied(row_index),
397 );
398
399 drop(grid_template_rows);
400 drop(grid_template_columns);
401 drop(grid_auto_rows);
402 drop(grid_auto_columns);
403 drop(style);
404
405 resolve_item_track_indexes(&mut items, final_col_counts, final_row_counts);
411 determine_if_item_crosses_flexible_or_intrinsic_tracks(&mut items, &columns, &rows);
414
415 let has_baseline_aligned_item = items.iter().any(|item| item.participates_in_baseline_alignment());
417
418 track_sizing_algorithm(
420 tree,
421 AbstractAxis::Inline,
422 inner_min_size.get(AbstractAxis::Inline),
423 inner_max_size.get(AbstractAxis::Inline),
424 justify_content,
425 align_content,
426 available_grid_space,
427 inner_node_size,
428 &mut columns,
429 &mut rows,
430 &mut items,
431 |track: &GridTrack, parent_size: Option<f32>, tree: &Tree| {
432 track.max_track_sizing_function.definite_value(parent_size, |val, basis| tree.calc(val, basis))
433 },
434 has_baseline_aligned_item,
435 );
436 let initial_column_sum = columns.iter().map(|track| track.base_size).sum::<f32>();
437 inner_node_size.width = inner_node_size.width.or_else(|| initial_column_sum.into());
438
439 if run_mode == RunMode::ComputeSize && inputs.axis == RequestedAxis::Horizontal {
443 let width = (initial_column_sum + content_box_inset.horizontal_axis_sum())
444 .maybe_clamp(min_size.width, max_size.width)
445 .max(padding_border_size.width);
446 return LayoutOutput::from_outer_size(Size { width, height: 0.0 });
447 }
448
449 items.iter_mut().for_each(|item| item.grid_area_size_cache = None);
450
451 track_sizing_algorithm(
453 tree,
454 AbstractAxis::Block,
455 inner_min_size.get(AbstractAxis::Block),
456 inner_max_size.get(AbstractAxis::Block),
457 align_content,
458 justify_content,
459 available_grid_space,
460 inner_node_size,
461 &mut rows,
462 &mut columns,
463 &mut items,
464 |track: &GridTrack, _, _| Some(track.base_size),
465 false, );
467 let initial_row_sum = rows.iter().map(|track| track.base_size).sum::<f32>();
468
469 debug_log!("initial_column_sum", dbg:initial_column_sum);
470 debug_log!(dbg: columns.iter().map(|track| track.base_size).collect::<Vec<_>>());
471 debug_log!("initial_row_sum", dbg:initial_row_sum);
472 debug_log!(dbg: rows.iter().map(|track| track.base_size).collect::<Vec<_>>());
473
474 let resolved_style_size = known_dimensions.or(preferred_size);
476 let mut container_border_box = Size {
477 width: resolved_style_size
478 .get(AbstractAxis::Inline)
479 .unwrap_or_else(|| initial_column_sum + content_box_inset.horizontal_axis_sum())
480 .maybe_clamp(min_size.width, max_size.width)
481 .max(padding_border_size.width),
482 height: resolved_style_size
483 .get(AbstractAxis::Block)
484 .unwrap_or_else(|| initial_row_sum + content_box_inset.vertical_axis_sum())
485 .maybe_clamp(min_size.height, max_size.height)
486 .max(padding_border_size.height),
487 };
488 let mut container_content_box = Size {
489 width: f32_max(0.0, container_border_box.width - content_box_inset.horizontal_axis_sum()),
490 height: f32_max(0.0, container_border_box.height - content_box_inset.vertical_axis_sum()),
491 };
492
493 if run_mode == RunMode::ComputeSize {
495 return LayoutOutput::from_outer_size(container_border_box);
496 }
497
498 inner_node_size = container_content_box.map(Some);
501
502 let mut rerun_column_sizing;
507 let mut intrinsic_column_contribution_changed = false;
508
509 let has_percentage_column = columns.iter().any(|track| track.uses_percentage());
510 let has_percentage_row = rows.iter().any(|track| track.uses_percentage());
511 let parent_width_indefinite = outer_node_size.width.is_none();
512 rerun_column_sizing = parent_width_indefinite && has_percentage_column;
513
514 if !rerun_column_sizing {
515 intrinsic_column_contribution_changed =
517 items.iter_mut().filter(|item| item.crosses_intrinsic_column).fold(false, |any_changed, item| {
518 let grid_area_size = item.grid_area_size(
519 AbstractAxis::Inline,
520 &columns,
521 &rows,
522 inner_node_size,
523 |track: &GridTrack, _| Some(track.base_size),
524 &|val, basis| tree.calc(val, basis),
525 );
526 let available_space = grid_area_size.with(AbstractAxis::Inline, None);
527 let new_min_content_contribution =
528 item.min_content_contribution(AbstractAxis::Inline, tree, grid_area_size, available_space);
529
530 let has_changed = Some(new_min_content_contribution) != item.min_content_contribution_cache.width;
531
532 item.grid_area_size_cache = Some(grid_area_size);
533 item.min_content_contribution_cache.width = Some(new_min_content_contribution);
534 item.max_content_contribution_cache.width = None;
535 item.minimum_contribution_cache.width = None;
536
537 any_changed | has_changed
538 });
539 rerun_column_sizing = intrinsic_column_contribution_changed;
540 } else {
541 items.iter_mut().for_each(|item| {
543 item.grid_area_size_cache = None;
544 item.min_content_contribution_cache.width = None;
545 item.max_content_contribution_cache.width = None;
546 item.minimum_contribution_cache.width = None;
547 });
548 }
549
550 let parent_height_indefinite = outer_node_size.height.is_none();
555 let mut rerun_row_sizing = parent_height_indefinite && has_percentage_row;
556 let mut intrinsic_row_contribution_changed = false;
557
558 if rerun_row_sizing {
559 items.iter_mut().for_each(|item| {
561 item.grid_area_size_cache = None;
562 item.min_content_contribution_cache.height = None;
563 item.max_content_contribution_cache.height = None;
564 item.minimum_contribution_cache.height = None;
565 });
566 }
567
568 if rerun_column_sizing {
569 track_sizing_algorithm(
571 tree,
572 AbstractAxis::Inline,
573 inner_min_size.get(AbstractAxis::Inline),
574 inner_max_size.get(AbstractAxis::Inline),
575 justify_content,
576 align_content,
577 available_grid_space,
578 inner_node_size,
579 &mut columns,
580 &mut rows,
581 &mut items,
582 |track: &GridTrack, _, _| Some(track.base_size),
583 has_baseline_aligned_item,
584 );
585
586 if !rerun_row_sizing {
589 intrinsic_row_contribution_changed =
591 items.iter_mut().filter(|item| item.crosses_intrinsic_row).fold(false, |any_changed, item| {
592 let grid_area_size = item.grid_area_size(
593 AbstractAxis::Block,
594 &rows,
595 &columns,
596 inner_node_size,
597 |track: &GridTrack, _| Some(track.base_size),
598 &|val, basis| tree.calc(val, basis),
599 );
600 let available_space = grid_area_size.with(AbstractAxis::Block, None);
601 let new_min_content_contribution =
602 item.min_content_contribution(AbstractAxis::Block, tree, grid_area_size, available_space);
603
604 let has_changed = Some(new_min_content_contribution) != item.min_content_contribution_cache.height;
605
606 item.grid_area_size_cache = Some(grid_area_size);
607 item.min_content_contribution_cache.height = Some(new_min_content_contribution);
608 item.max_content_contribution_cache.height = None;
609 item.minimum_contribution_cache.height = None;
610
611 any_changed | has_changed
612 });
613 rerun_row_sizing = intrinsic_row_contribution_changed;
614 }
615 }
616
617 if rerun_row_sizing {
618 track_sizing_algorithm(
620 tree,
621 AbstractAxis::Block,
622 inner_min_size.get(AbstractAxis::Block),
623 inner_max_size.get(AbstractAxis::Block),
624 align_content,
625 justify_content,
626 available_grid_space,
627 inner_node_size,
628 &mut rows,
629 &mut columns,
630 &mut items,
631 |track: &GridTrack, _, _| Some(track.base_size),
632 false, );
634 }
635
636 if (intrinsic_column_contribution_changed && !has_percentage_column)
637 || (intrinsic_row_contribution_changed && !has_percentage_row)
638 {
639 let final_column_sum = columns.iter().map(|track| track.base_size).sum::<f32>();
640 let final_row_sum = rows.iter().map(|track| track.base_size).sum::<f32>();
641
642 if intrinsic_column_contribution_changed && !has_percentage_column {
643 container_border_box.width = resolved_style_size
644 .get(AbstractAxis::Inline)
645 .unwrap_or_else(|| final_column_sum + content_box_inset.horizontal_axis_sum())
646 .maybe_clamp(min_size.width, max_size.width)
647 .max(padding_border_size.width);
648 container_content_box.width =
649 f32_max(0.0, container_border_box.width - content_box_inset.horizontal_axis_sum());
650 }
651
652 if intrinsic_row_contribution_changed && !has_percentage_row {
653 container_border_box.height = resolved_style_size
654 .get(AbstractAxis::Block)
655 .unwrap_or_else(|| final_row_sum + content_box_inset.vertical_axis_sum())
656 .maybe_clamp(min_size.height, max_size.height)
657 .max(padding_border_size.height);
658 container_content_box.height =
659 f32_max(0.0, container_border_box.height - content_box_inset.vertical_axis_sum());
660 }
661 }
662
663 if run_mode == RunMode::ComputeSize {
665 return LayoutOutput::from_outer_size(container_border_box);
666 }
667
668 let inline_size_without_scrollbar = f32_max(container_border_box.width - padding_border_size.width, 0.0);
672 let inline_scrollbar_gutter_for_alignment = f32_min(scrollbar_gutter.x, inline_size_without_scrollbar);
673 align_tracks(
674 container_content_box.get(AbstractAxis::Inline),
675 Line {
676 start: padding.left + if direction.is_rtl() { inline_scrollbar_gutter_for_alignment } else { 0.0 },
677 end: padding.right + if direction.is_rtl() { 0.0 } else { inline_scrollbar_gutter_for_alignment },
678 },
679 Line { start: border.left, end: border.right },
680 &mut columns,
681 justify_content,
682 direction.is_rtl(),
683 );
684 align_tracks(
686 container_content_box.get(AbstractAxis::Block),
687 Line { start: padding.top, end: padding.bottom },
688 Line { start: border.top, end: border.bottom },
689 &mut rows,
690 align_content,
691 false,
692 );
693
694 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
697 let mut item_overflow_rect = Rect::ZERO;
698
699 let mut oof_candidates = OofCandidates::new();
703
704 let container_alignment_styles = InBothAbsAxis { horizontal: justify_items, vertical: align_items };
705
706 for (index, item) in items.iter_mut().enumerate() {
708 let grid_area = Rect {
712 top: rows[item.row_indexes.start as usize + 1].offset,
713 bottom: rows[item.row_indexes.end as usize].offset,
714 left: if direction.is_rtl() {
715 columns[item.column_indexes.end as usize - 1].offset
716 } else {
717 columns[item.column_indexes.start as usize + 1].offset
718 },
719 right: if direction.is_rtl() {
720 columns[item.column_indexes.start as usize].offset
721 } else {
722 columns[item.column_indexes.end as usize].offset
723 },
724 };
725 #[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
726 let (overflow_contribution, y_position, height, baseline) = align_and_position_item(
727 tree,
728 item.node,
729 index as u32,
730 grid_area,
731 Size { width: grid_area.right - grid_area.left, height: grid_area.bottom - grid_area.top },
732 container_alignment_styles,
733 item.baseline_shim,
734 direction,
735 container_border_box.width,
736 border,
737 #[cfg(feature = "content_size")]
738 is_scroll_container,
739 &mut item.oof_candidates,
740 );
741 item.y_position = y_position;
742 item.height = height;
743 item.baseline = baseline;
744
745 #[cfg(feature = "content_size")]
746 {
747 item_overflow_rect = item_overflow_rect.union(overflow_contribution);
748 }
749 }
750
751 let mut order = items.len() as u32;
754 let mut in_flow_items = items.iter_mut().peekable();
755 (0..tree.child_count(node)).for_each(|index| {
756 if let Some(item) = in_flow_items.next_if(|item| item.source_order as usize == index) {
757 oof_candidates.append(&mut item.oof_candidates);
758 return;
759 }
760
761 let child = tree.get_child_id(node, index);
762 let child_style = tree.get_grid_child_style(child);
763
764 if child_style.box_generation_mode() == BoxGenerationMode::None {
766 drop(child_style);
767 tree.set_unrounded_layout(child, &Layout::with_order(order));
768 tree.perform_child_layout(
769 child,
770 Size::NONE,
771 Size::NONE,
772 Size::MAX_CONTENT,
773 SizingMode::InherentSize,
774 Line::FALSE,
775 );
776 order += 1;
777 return;
778 }
779
780 if child_style.position().is_out_of_flow() {
785 let position = child_style.position();
786 let item_direction = child_style.direction();
787 let justify_self = child_style.justify_self().unwrap_or(justify_items).resolve_self_relative(
788 item_direction,
789 direction,
790 true,
791 );
792 let align_self =
793 child_style.align_self().unwrap_or(align_items).resolve_self_relative(item_direction, direction, false);
794
795 let area = if containing_block_claims.for_position(position) {
802 resolve_static_position_grid_area(
803 &child_style,
804 &name_resolver,
805 final_row_counts,
806 final_col_counts,
807 &rows,
808 &columns,
809 direction,
810 border,
811 scrollbar_gutter,
812 container_border_box,
813 )
814 } else {
815 Rect {
816 left: border.left + padding.left + if direction.is_rtl() { scrollbar_gutter.x } else { 0.0 },
817 right: container_border_box.width
818 - border.right
819 - padding.right
820 - if direction.is_rtl() { 0.0 } else { scrollbar_gutter.x },
821 top: border.top + padding.top,
822 bottom: container_border_box.height - border.bottom - padding.bottom - scrollbar_gutter.y,
823 }
824 };
825 drop(child_style);
826
827 oof_candidates.push(OofCandidate {
828 node: child,
829 order,
830 position,
831 static_position: Point {
832 x: AxisStaticPosition::from_alignment(
833 justify_self,
834 Line { start: area.left, end: area.right },
835 direction.is_rtl(),
836 ),
837 y: AxisStaticPosition::from_alignment(
838 align_self,
839 Line { start: area.top, end: area.bottom },
840 false,
841 ),
842 },
843 });
844
845 order += 1;
846 }
847 });
848
849 let absolute_position_inset = border
850 + Rect {
851 left: if direction.is_rtl() { scrollbar_gutter.x } else { 0.0 },
852 right: if direction.is_rtl() { 0.0 } else { scrollbar_gutter.x },
853 top: 0.0,
854 bottom: scrollbar_gutter.y,
855 };
856 let absolute_position_area = container_border_box - absolute_position_inset.sum_axes();
857 let absolute_position_offset = Point { x: absolute_position_inset.left, y: absolute_position_inset.top };
858 let areas =
861 name_resolver.populate_detailed_line_resolvers(&mut detailed_row_line_names, &mut detailed_column_line_names);
862 tree.set_detailed_grid_info(
863 node,
864 DetailedGridInfo {
865 rows: DetailedGridTracksInfo::from_grid_tracks_and_track_count(
866 final_row_counts,
867 rows,
868 detailed_row_line_names,
869 ),
870 columns: DetailedGridTracksInfo::from_grid_tracks_and_track_count(
871 final_col_counts,
872 columns,
873 detailed_column_line_names,
874 ),
875 items: items.iter().map(DetailedGridItemsInfo::from_grid_item).collect(),
876 areas,
877 },
878 );
879
880 let oof_positioning_area =
881 Some(OofPositioningArea { size: absolute_position_area, offset: absolute_position_offset });
882
883 if items.is_empty() {
885 #[cfg(feature = "content_size")]
886 let mut output = {
887 let mut overflow_rect = item_overflow_rect;
888 if is_scroll_container {
889 overflow_rect.right += if direction.is_rtl() { padding.left } else { padding.right };
890 overflow_rect.bottom += padding.bottom;
891 }
892 LayoutOutput::from_sizes(container_border_box, overflow_rect)
893 };
894 #[cfg(not(feature = "content_size"))]
895 let mut output = LayoutOutput::from_outer_size(container_border_box);
896 output.oof_candidates = oof_candidates;
897 output.oof_positioning_area = oof_positioning_area;
898 return output;
899 }
900
901 let grid_container_baseline: Option<f32> = if contain.suppresses_baseline() {
904 None
905 } else {
906 let first_row = items.iter().map(|item| item.row_indexes.start).min().unwrap();
908
909 let mut first_row_items = items.iter().filter(|item| item.row_indexes.start == first_row);
912 let first_item = first_row_items.next().unwrap();
913 let item = if first_item.participates_in_baseline_alignment() {
914 first_item
915 } else {
916 first_row_items.find(|item| item.participates_in_baseline_alignment()).unwrap_or(first_item)
917 };
918
919 Some(item.y_position + item.baseline.unwrap_or(item.height))
920 };
921
922 #[cfg(feature = "content_size")]
926 let scrollable_overflow_rect = {
927 let mut overflow_rect = item_overflow_rect;
928 if is_scroll_container {
929 overflow_rect.right += if direction.is_rtl() { padding.left } else { padding.right };
930 overflow_rect.bottom += padding.bottom;
931 }
932 overflow_rect
933 };
934 #[cfg(not(feature = "content_size"))]
935 let scrollable_overflow_rect = item_overflow_rect;
936
937 let mut output = LayoutOutput::from_sizes_and_baselines(
938 container_border_box,
939 scrollable_overflow_rect,
940 Baselines::from_first(grid_container_baseline),
941 );
942 output.oof_candidates = oof_candidates;
943 output.oof_positioning_area = oof_positioning_area;
944 output
945}
946
947#[allow(clippy::too_many_arguments)]
956fn resolve_static_position_grid_area<S: CheapCloneStr>(
957 child_style: &impl GridItemStyle<CustomIdent = S>,
958 name_resolver: &NamedLineResolver<S>,
959 final_row_counts: TrackCounts,
960 final_col_counts: TrackCounts,
961 rows: &[GridTrack],
962 columns: &[GridTrack],
963 direction: Direction,
964 border: Rect<f32>,
965 scrollbar_gutter: Point<f32>,
966 container_border_box: Size<f32>,
967) -> Rect<f32> {
968 let maybe_col_indexes = name_resolver
971 .resolve_column_names(&child_style.grid_column())
972 .into_origin_zero(final_col_counts.explicit)
973 .resolve_absolutely_positioned_grid_tracks()
974 .map(|maybe_grid_line| {
975 maybe_grid_line.and_then(|line: OriginZeroLine| line.try_into_track_vec_index(final_col_counts))
976 });
977 let maybe_row_indexes = name_resolver
980 .resolve_row_names(&child_style.grid_row())
981 .into_origin_zero(final_row_counts.explicit)
982 .resolve_absolutely_positioned_grid_tracks()
983 .map(|maybe_grid_line| {
984 maybe_grid_line.and_then(|line: OriginZeroLine| line.try_into_track_vec_index(final_row_counts))
985 });
986
987 fn line_as_start_edge(tracks: &[GridTrack], index: usize) -> f32 {
989 tracks.get(index + 1).unwrap_or(&tracks[index]).offset
990 }
991 fn line_as_end_edge(tracks: &[GridTrack], index: usize) -> f32 {
993 if index == 0 {
994 tracks.get(1).unwrap_or(&tracks[0]).offset
995 } else {
996 tracks[index].offset
997 }
998 }
999 fn rtl_line_as_start_edge(tracks: &[GridTrack], index: usize) -> f32 {
1006 if tracks.len() > index + 1 {
1007 tracks[index].offset
1009 } else if index == 0 {
1010 tracks[0].offset
1011 } else {
1012 tracks[index - 1].offset
1016 }
1017 }
1018 fn rtl_line_as_end_edge(tracks: &[GridTrack], index: usize) -> f32 {
1020 if index == 0 {
1021 tracks[0].offset
1022 } else {
1023 tracks[index - 1].offset
1024 }
1025 }
1026
1027 let (grid_area_left, grid_area_right) = if direction.is_rtl() {
1030 (
1031 maybe_col_indexes
1032 .end
1033 .map(|index| rtl_line_as_end_edge(columns, index))
1034 .unwrap_or(border.left + scrollbar_gutter.x),
1035 maybe_col_indexes
1036 .start
1037 .map(|index| rtl_line_as_start_edge(columns, index))
1038 .unwrap_or(container_border_box.width - border.right),
1039 )
1040 } else {
1041 (
1042 maybe_col_indexes.start.map(|index| line_as_start_edge(columns, index)).unwrap_or(border.left),
1043 maybe_col_indexes
1044 .end
1045 .map(|index| line_as_end_edge(columns, index))
1046 .unwrap_or(container_border_box.width - border.right - scrollbar_gutter.x),
1047 )
1048 };
1049
1050 Rect {
1051 top: maybe_row_indexes.start.map(|index| line_as_start_edge(rows, index)).unwrap_or(border.top),
1052 bottom: maybe_row_indexes
1053 .end
1054 .map(|index| line_as_end_edge(rows, index))
1055 .unwrap_or(container_border_box.height - border.bottom - scrollbar_gutter.y),
1056 left: grid_area_left,
1057 right: grid_area_right,
1058 }
1059}
1060
1061#[derive(Debug, Clone, PartialEq)]
1063pub struct DetailedGridInfo<S: CheapCloneStr = DefaultCheapStr> {
1064 pub rows: DetailedGridTracksInfo<S>,
1066 pub columns: DetailedGridTracksInfo<S>,
1068 pub items: Vec<DetailedGridItemsInfo>,
1070 areas: GridAreasMap<S>,
1072}
1073
1074impl<S: CheapCloneStr> DetailedGridTracksInfo<S> {
1075 fn resolve_absolute_grid_axis(
1077 &self,
1078 placement: Line<GridPlacement<S>>,
1079 areas: &GridAreasMap<S>,
1080 axis: GridAreaAxis,
1081 padding_start: f32,
1082 padding_end: f32,
1083 is_reversed: bool,
1084 ) -> Line<f32> {
1085 let track_counts = TrackCounts {
1086 negative_implicit: self.negative_implicit_tracks,
1087 explicit: self.explicit_tracks,
1088 positive_implicit: self.positive_implicit_tracks,
1089 };
1090 let min_line = -(track_counts.negative_implicit as i16);
1091 let max_line = (track_counts.explicit + track_counts.positive_implicit) as i16;
1092 let placement = self
1098 .line_names
1099 .resolve_line_names(&placement, areas, axis, self.explicit_tracks)
1100 .into_origin_zero(self.explicit_tracks)
1101 .resolve_absolutely_positioned_grid_tracks()
1102 .map(|line| line.filter(|line| line.0 >= min_line && line.0 <= max_line))
1103 .map(|line| line.and_then(|line| line.try_into_track_vec_index(track_counts).map(|index| index / 2)));
1104 let start_position = placement
1105 .start
1106 .and_then(|line| {
1107 (line < self.positions.len())
1108 .then(|| self.absolute_track_edge(line, true, is_reversed))
1109 .or_else(|| self.positions.last().map(|track| if is_reversed { track.start } else { track.end }))
1110 .or(self.empty_axis_line)
1111 })
1112 .unwrap_or(if is_reversed { padding_end } else { padding_start });
1113 let end_position = placement
1114 .end
1115 .and_then(|line| {
1116 line.checked_sub(1)
1117 .filter(|&line| line < self.positions.len())
1118 .map(|line| self.absolute_track_edge(line, false, is_reversed))
1119 .or_else(|| self.positions.first().map(|track| if is_reversed { track.end } else { track.start }))
1120 .or(self.empty_axis_line)
1121 })
1122 .unwrap_or(if is_reversed { padding_start } else { padding_end });
1123
1124 if is_reversed {
1127 Line { start: f32_min(start_position, end_position), end: start_position }
1128 } else {
1129 Line { start: start_position, end: f32_max(start_position, end_position) }
1130 }
1131 }
1132
1133 fn absolute_track_edge(&self, index: usize, is_start: bool, is_reversed: bool) -> f32 {
1135 let range_index = self.collapsed_tracks.partition_point(|range| range.end <= index);
1136 let range = self.collapsed_tracks.get(range_index).filter(|range| range.contains(&index));
1137 let track = range
1138 .and_then(|range| {
1139 let adjacent = if is_start { Some(range.end) } else { range.start.checked_sub(1) };
1140 adjacent.and_then(|index| self.positions.get(index))
1141 })
1142 .unwrap_or(&self.positions[index]);
1143 if is_start != is_reversed {
1146 track.start
1147 } else {
1148 track.end
1149 }
1150 }
1151}
1152
1153impl<S: CheapCloneStr> DetailedGridInfo<S> {
1154 pub fn write_grid_template_rows(&self, out: &mut impl core::fmt::Write, zoom: f32) -> core::fmt::Result {
1159 self.rows.write_track_list(out, zoom)
1160 }
1161
1162 pub fn write_grid_template_columns(&self, out: &mut impl core::fmt::Write, zoom: f32) -> core::fmt::Result {
1167 self.columns.write_track_list(out, zoom)
1168 }
1169
1170 pub fn grid_template_rows(&self, zoom: f32) -> String {
1174 self.rows.to_track_list_string(zoom)
1175 }
1176
1177 pub fn grid_template_columns(&self, zoom: f32) -> String {
1181 self.columns.to_track_list_string(zoom)
1182 }
1183
1184 pub fn resolve_absolute_grid_area(
1187 &self,
1188 grid_row: Line<GridPlacement<S>>,
1189 grid_column: Line<GridPlacement<S>>,
1190 direction: Direction,
1191 padding_box: Rect<f32>,
1192 ) -> Rect<f32> {
1193 let columns = self.columns.resolve_absolute_grid_axis(
1194 grid_column,
1195 &self.areas,
1196 GridAreaAxis::Column,
1197 padding_box.left,
1198 padding_box.right,
1199 direction.is_rtl(),
1200 );
1201 let rows = self.rows.resolve_absolute_grid_axis(
1202 grid_row,
1203 &self.areas,
1204 GridAreaAxis::Row,
1205 padding_box.top,
1206 padding_box.bottom,
1207 false,
1208 );
1209 Rect { left: columns.start, right: columns.end, top: rows.start, bottom: rows.end }
1210 }
1211
1212 pub fn item_grid_area(&self, item_index: usize) -> Option<(Point<f32>, Size<f32>)> {
1221 let item = self.items.get(item_index)?;
1222 let start_col = self.columns.positions[item.column_start as usize - 1];
1223 let end_col = self.columns.positions[item.column_end as usize - 2];
1224 let left = f32_min(start_col.start, end_col.start);
1225 let right = f32_max(start_col.end, end_col.end);
1226 let top = self.rows.positions[item.row_start as usize - 1].start;
1227 let bottom = self.rows.positions[item.row_end as usize - 2].end;
1228 Some((Point { x: left, y: top }, Size { width: right - left, height: bottom - top }))
1229 }
1230}
1231
1232#[derive(Debug, Clone, PartialEq)]
1234pub struct DetailedGridTracksInfo<S: CheapCloneStr = DefaultCheapStr> {
1235 pub negative_implicit_tracks: u16,
1237 pub explicit_tracks: u16,
1239 pub positive_implicit_tracks: u16,
1241
1242 pub positions: Vec<Line<f32>>,
1246
1247 pub collapsed_tracks: Vec<Range<usize>>,
1251
1252 pub empty_axis_line: Option<f32>,
1256
1257 pub line_names: GridLineNames<S>,
1262}
1263
1264impl<S: CheapCloneStr> DetailedGridTracksInfo<S> {
1265 fn positions_from_grid_track_layout(grid_tracks: &[GridTrack]) -> (Vec<Line<f32>>, Vec<Range<usize>>) {
1268 let mut positions = Vec::with_capacity(grid_tracks.len() / 2);
1269 let mut collapsed_tracks: Vec<Range<usize>> = Vec::new();
1270 for track in grid_tracks.iter().filter(|track| track.kind == GridTrackKind::Track) {
1271 if track.is_collapsed {
1272 let index = positions.len();
1273 match collapsed_tracks.last_mut() {
1274 Some(range) if range.end == index => range.end += 1,
1275 _ => collapsed_tracks.push(index..index + 1),
1276 }
1277 }
1278 positions.push(Line { start: track.offset, end: track.offset + track.base_size });
1279 }
1280 (positions, collapsed_tracks)
1281 }
1282
1283 fn from_grid_tracks_and_track_count(
1285 track_count: TrackCounts,
1286 grid_tracks: Vec<GridTrack>,
1287 line_names: GridLineNames<S>,
1288 ) -> Self {
1289 let (positions, collapsed_tracks) = DetailedGridTracksInfo::<S>::positions_from_grid_track_layout(&grid_tracks);
1290 let empty_axis_line = if positions.is_empty() { grid_tracks.first().map(|track| track.offset) } else { None };
1293 DetailedGridTracksInfo {
1294 negative_implicit_tracks: track_count.negative_implicit,
1295 explicit_tracks: track_count.explicit,
1296 positive_implicit_tracks: track_count.positive_implicit,
1297 positions,
1298 collapsed_tracks,
1299 empty_axis_line,
1300 line_names,
1301 }
1302 }
1303
1304 pub fn names_for_line(&self, line_index: usize) -> &[S] {
1308 match line_index.checked_sub(self.negative_implicit_tracks as usize) {
1309 Some(stored_index) => self.line_names.line(stored_index),
1310 None => &[],
1311 }
1312 }
1313
1314 pub fn iter_line_names(&self) -> GridLineNamesIter<'_, S> {
1318 if self.line_names.is_empty() {
1319 return self.line_names.iter();
1320 }
1321 let total_line_count = self.positions.len() + 1;
1322 let leading_empty = self.negative_implicit_tracks as usize;
1323 let trailing_empty = total_line_count.saturating_sub(leading_empty + self.line_names.line_count());
1324 self.line_names.iter_padded(leading_empty, trailing_empty)
1325 }
1326
1327 pub fn write_track_list(&self, out: &mut impl core::fmt::Write, zoom: f32) -> core::fmt::Result {
1332 fn write_line_names<S: CheapCloneStr>(out: &mut impl core::fmt::Write, names: &[S]) -> core::fmt::Result {
1334 out.write_char('[')?;
1335 for (i, name) in names.iter().enumerate() {
1336 if i != 0 {
1337 out.write_char(' ')?;
1338 }
1339 out.write_str(name.as_ref())?;
1340 }
1341 out.write_char(']')
1342 }
1343
1344 if self.positions.is_empty() {
1345 return out.write_str("none");
1346 }
1347
1348 let mut needs_space = false;
1349 for (track_index, position) in self.positions.iter().enumerate() {
1350 let names = self.names_for_line(track_index);
1351 if !names.is_empty() {
1352 if needs_space {
1353 out.write_char(' ')?;
1354 }
1355 write_line_names(out, names)?;
1356 needs_space = true;
1357 }
1358 if needs_space {
1359 out.write_char(' ')?;
1360 }
1361 write!(out, "{}px", (position.end - position.start) / zoom)?;
1362 needs_space = true;
1363 }
1364 let trailing_names = self.names_for_line(self.positions.len());
1365 if !trailing_names.is_empty() {
1366 out.write_char(' ')?;
1367 write_line_names(out, trailing_names)?;
1368 }
1369 Ok(())
1370 }
1371
1372 pub fn to_track_list_string(&self, zoom: f32) -> String {
1377 let mut out = String::new();
1378 self.write_track_list(&mut out, zoom).expect("writing to a String cannot fail");
1379 out
1380 }
1381}
1382
1383#[derive(Debug, Clone, PartialEq)]
1388pub struct DetailedGridItemsInfo {
1389 pub row_start: u16,
1391 pub row_end: u16,
1393 pub column_start: u16,
1395 pub column_end: u16,
1397}
1398
1399impl DetailedGridItemsInfo {
1401 #[inline(always)]
1403 fn from_grid_item(grid_item: &GridItem) -> Self {
1404 #[inline(always)]
1406 fn to_one_indexed_grid_line(grid_track_index: u16) -> u16 {
1407 grid_track_index / 2 + 1
1408 }
1409
1410 DetailedGridItemsInfo {
1411 row_start: to_one_indexed_grid_line(grid_item.row_indexes.start),
1412 row_end: to_one_indexed_grid_line(grid_item.row_indexes.end),
1413 column_start: to_one_indexed_grid_line(grid_item.column_indexes.start),
1414 column_end: to_one_indexed_grid_line(grid_item.column_indexes.end),
1415 }
1416 }
1417}