1use super::types::{CellOccupancyMatrix, CellOccupancyState, GridItem};
4use super::{NamedLineResolver, OriginZeroLine};
5use crate::geometry::Line;
6use crate::geometry::{AbsoluteAxis, InBothAbsAxis};
7use crate::style::{AlignItems, GridAutoFlow, OriginZeroGridPlacement};
8use crate::tree::NodeId;
9use crate::util::sys::Vec;
10use crate::{CoreStyle, GridItemStyle};
11
12pub(super) fn place_grid_items<'a, S, ChildIter>(
17 cell_occupancy_matrix: &mut CellOccupancyMatrix,
18 items: &mut Vec<GridItem>,
19 children_iter: impl Fn() -> ChildIter,
20 grid_auto_flow: GridAutoFlow,
21 align_items: AlignItems,
22 justify_items: AlignItems,
23 named_line_resolver: &NamedLineResolver<<S as CoreStyle>::CustomIdent>,
24) where
25 S: GridItemStyle + 'a,
26 ChildIter: Iterator<Item = (usize, NodeId, S)>,
27{
28 let primary_axis = grid_auto_flow.primary_axis();
29 let secondary_axis = primary_axis.other_axis();
30
31 let map_child_style_to_origin_zero_placement = {
32 let explicit_col_count = cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal).explicit;
33 let explicit_row_count = cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical).explicit;
34 move |(index, node, style): (usize, NodeId, S)| -> (_, _, _, S) {
35 let origin_zero_placement = InBothAbsAxis {
36 horizontal: named_line_resolver
37 .resolve_column_names(&style.grid_column())
38 .map(|placement| placement.into_origin_zero_placement(explicit_col_count)),
39 vertical: named_line_resolver
40 .resolve_row_names(&style.grid_row())
41 .map(|placement| placement.into_origin_zero_placement(explicit_row_count)),
42 };
43 (index, node, origin_zero_placement, style)
44 }
45 };
46
47 let mut idx = 0;
49 children_iter()
50 .map(map_child_style_to_origin_zero_placement)
51 .filter(|(_, _, placement, _)| placement.horizontal.is_definite() && placement.vertical.is_definite())
52 .for_each(|(index, child_node, child_placement, style)| {
53 idx += 1;
54 #[cfg(test)]
55 println!("Definite Item {idx}\n==============");
56
57 let (row_span, col_span) = place_definite_grid_item(child_placement, primary_axis);
58 record_grid_placement(
59 cell_occupancy_matrix,
60 items,
61 child_node,
62 index,
63 style,
64 align_items,
65 justify_items,
66 primary_axis,
67 row_span,
68 col_span,
69 CellOccupancyState::DefinitelyPlaced,
70 );
71 });
72
73 let mut idx = 0;
75 children_iter()
76 .map(map_child_style_to_origin_zero_placement)
77 .filter(|(_, _, placement, _)| {
78 placement.get(secondary_axis).is_definite() && !placement.get(primary_axis).is_definite()
79 })
80 .for_each(|(index, child_node, child_placement, style)| {
81 idx += 1;
82 #[cfg(test)]
83 println!("Definite Secondary Item {idx}\n==============");
84
85 let (primary_span, secondary_span) =
86 place_definite_secondary_axis_item(&*cell_occupancy_matrix, child_placement, grid_auto_flow);
87
88 record_grid_placement(
89 cell_occupancy_matrix,
90 items,
91 child_node,
92 index,
93 style,
94 align_items,
95 justify_items,
96 primary_axis,
97 primary_span,
98 secondary_span,
99 CellOccupancyState::AutoPlaced,
100 );
101 });
102
103 let primary_axis = grid_auto_flow.primary_axis();
123 let secondary_axis = primary_axis.other_axis();
124 let primary_neg_tracks = cell_occupancy_matrix.track_counts(primary_axis).negative_implicit as i16;
125 let secondary_neg_tracks = cell_occupancy_matrix.track_counts(secondary_axis).negative_implicit as i16;
126 let grid_start_position = (OriginZeroLine(-primary_neg_tracks), OriginZeroLine(-secondary_neg_tracks));
127 let mut grid_position = grid_start_position;
128 let mut idx = 0;
129 children_iter()
130 .map(map_child_style_to_origin_zero_placement)
131 .filter(|(_, _, placement, _)| !placement.get(secondary_axis).is_definite())
132 .for_each(|(index, child_node, child_placement, style)| {
133 idx += 1;
134 #[cfg(test)]
135 println!("\nAuto Item {idx}\n==============");
136
137 let (primary_span, secondary_span) = place_indefinitely_positioned_item(
139 &*cell_occupancy_matrix,
140 child_placement,
141 grid_auto_flow,
142 grid_position,
143 );
144
145 record_grid_placement(
147 cell_occupancy_matrix,
148 items,
149 child_node,
150 index,
151 style,
152 align_items,
153 justify_items,
154 primary_axis,
155 primary_span,
156 secondary_span,
157 CellOccupancyState::AutoPlaced,
158 );
159
160 grid_position = match grid_auto_flow.is_dense() {
163 true => grid_start_position,
164 false => (primary_span.end, secondary_span.start),
165 }
166 });
167}
168
169fn place_definite_grid_item(
172 placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
173 primary_axis: AbsoluteAxis,
174) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
175 let primary_span = placement.get(primary_axis).resolve_definite_grid_lines();
177 let secondary_span = placement.get(primary_axis.other_axis()).resolve_definite_grid_lines();
178
179 (primary_span, secondary_span)
180}
181
182fn place_definite_secondary_axis_item(
185 cell_occupancy_matrix: &CellOccupancyMatrix,
186 placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
187 auto_flow: GridAutoFlow,
188) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
189 let primary_axis = auto_flow.primary_axis();
190 let secondary_axis = primary_axis.other_axis();
191
192 let secondary_axis_placement = placement.get(secondary_axis).resolve_definite_grid_lines();
193 let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
194 let starting_position = match auto_flow.is_dense() {
195 true => primary_axis_grid_start_line,
196 false => cell_occupancy_matrix
197 .last_of_type(primary_axis, secondary_axis_placement.start, CellOccupancyState::AutoPlaced)
198 .unwrap_or(primary_axis_grid_start_line),
199 };
200
201 let mut position: OriginZeroLine = starting_position;
202 loop {
203 let primary_axis_placement = placement.get(primary_axis).resolve_indefinite_grid_tracks(position);
204
205 let does_fit = cell_occupancy_matrix.line_area_is_unoccupied(
206 primary_axis,
207 primary_axis_placement,
208 secondary_axis_placement,
209 );
210
211 if does_fit {
212 return (primary_axis_placement, secondary_axis_placement);
213 } else {
214 position += 1;
215 }
216 }
217}
218
219fn place_indefinitely_positioned_item(
222 cell_occupancy_matrix: &CellOccupancyMatrix,
223 placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
224 auto_flow: GridAutoFlow,
225 grid_position: (OriginZeroLine, OriginZeroLine),
226) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
227 let primary_axis = auto_flow.primary_axis();
228
229 let primary_placement_style = placement.get(primary_axis);
230 let secondary_placement_style = placement.get(primary_axis.other_axis());
231
232 let secondary_span = secondary_placement_style.indefinite_span();
233 let has_definite_primary_axis_position = primary_placement_style.is_definite();
234 let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
235 let primary_axis_grid_end_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_end_line();
236 let secondary_axis_grid_start_line =
237 cell_occupancy_matrix.track_counts(primary_axis.other_axis()).implicit_start_line();
238
239 let line_area_is_occupied = |primary_span, secondary_span| {
240 !cell_occupancy_matrix.line_area_is_unoccupied(primary_axis, primary_span, secondary_span)
241 };
242
243 let (mut primary_idx, mut secondary_idx) = grid_position;
244
245 if has_definite_primary_axis_position {
246 let primary_span = primary_placement_style.resolve_definite_grid_lines();
247
248 secondary_idx = match auto_flow.is_dense() {
250 true => secondary_axis_grid_start_line,
252 false => {
253 if primary_span.start < primary_idx {
254 secondary_idx + 1
255 } else {
256 secondary_idx
257 }
258 }
259 };
260
261 loop {
264 let secondary_span = Line { start: secondary_idx, end: secondary_idx + secondary_span };
265
266 if line_area_is_occupied(primary_span, secondary_span) {
268 secondary_idx += 1;
269 continue;
270 }
271
272 return (primary_span, secondary_span);
274 }
275 } else {
276 let primary_span = primary_placement_style.indefinite_span();
277
278 loop {
282 let primary_span = Line { start: primary_idx, end: primary_idx + primary_span };
283 let secondary_span = Line { start: secondary_idx, end: secondary_idx + secondary_span };
284
285 let primary_out_of_bounds = primary_span.end > primary_axis_grid_end_line;
288 if primary_out_of_bounds {
289 secondary_idx += 1;
290 primary_idx = primary_axis_grid_start_line;
291 continue;
292 }
293
294 if line_area_is_occupied(primary_span, secondary_span) {
296 primary_idx += 1;
297 continue;
298 }
299
300 return (primary_span, secondary_span);
302 }
303 }
304}
305
306#[allow(clippy::too_many_arguments)]
309fn record_grid_placement<S: GridItemStyle>(
310 cell_occupancy_matrix: &mut CellOccupancyMatrix,
311 items: &mut Vec<GridItem>,
312 node: NodeId,
313 index: usize,
314 style: S,
315 parent_align_items: AlignItems,
316 parent_justify_items: AlignItems,
317 primary_axis: AbsoluteAxis,
318 primary_span: Line<OriginZeroLine>,
319 secondary_span: Line<OriginZeroLine>,
320 placement_type: CellOccupancyState,
321) {
322 #[cfg(test)]
323 println!("BEFORE placement:");
324 #[cfg(test)]
325 println!("{cell_occupancy_matrix:?}");
326
327 cell_occupancy_matrix.mark_area_as(primary_axis, primary_span, secondary_span, placement_type);
329
330 let (col_span, row_span) = match primary_axis {
332 AbsoluteAxis::Horizontal => (primary_span, secondary_span),
333 AbsoluteAxis::Vertical => (secondary_span, primary_span),
334 };
335 items.push(GridItem::new_with_placement_style_and_order(
336 node,
337 col_span,
338 row_span,
339 style,
340 parent_align_items,
341 parent_justify_items,
342 index as u16,
343 ));
344
345 #[cfg(test)]
346 println!("AFTER placement:");
347 #[cfg(test)]
348 println!("{cell_occupancy_matrix:?}");
349 #[cfg(test)]
350 println!("\n");
351}
352
353#[cfg(test)]
354mod tests {
355
356 mod test_placement_algorithm {
357 use crate::compute::grid::implicit_grid::compute_grid_size_estimate;
358 use crate::compute::grid::types::TrackCounts;
359 use crate::compute::grid::util::*;
360 use crate::compute::grid::CellOccupancyMatrix;
361 use crate::compute::grid::NamedLineResolver;
362 use crate::prelude::*;
363 use crate::style::GridAutoFlow;
364
365 use super::super::place_grid_items;
366
367 type ExpectedPlacement = (i16, i16, i16, i16);
368
369 fn placement_test_runner(
370 explicit_col_count: u16,
371 explicit_row_count: u16,
372 children: Vec<(usize, Style, ExpectedPlacement)>,
373 expected_col_counts: TrackCounts,
374 expected_row_counts: TrackCounts,
375 flow: GridAutoFlow,
376 ) {
377 let children_iter = || children.iter().map(|(index, style, _)| (*index, NodeId::from(*index), style));
379 let child_styles_iter = children.iter().map(|(_, style, _)| style);
380 let estimated_sizes = compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles_iter);
381 let mut items = Vec::new();
382 let mut cell_occupancy_matrix =
383 CellOccupancyMatrix::with_track_counts(estimated_sizes.0, estimated_sizes.1);
384 let mut name_resolver = NamedLineResolver::new(&Style::DEFAULT, 0, 0);
385 name_resolver.set_explicit_column_count(explicit_col_count);
386 name_resolver.set_explicit_row_count(explicit_row_count);
387
388 place_grid_items(
390 &mut cell_occupancy_matrix,
391 &mut items,
392 children_iter,
393 flow,
394 AlignSelf::Start,
395 AlignSelf::Start,
396 &name_resolver,
398 );
399
400 let mut sorted_children = children.clone();
402 sorted_children.sort_by_key(|child| child.0);
403 for (idx, ((id, _style, expected_placement), item)) in sorted_children.iter().zip(items.iter()).enumerate()
404 {
405 assert_eq!(item.node, NodeId::from(*id));
406 let actual_placement = (item.column.start, item.column.end, item.row.start, item.row.end);
407 assert_eq!(actual_placement, (*expected_placement).into_oz(), "Item {idx} (0-indexed)");
408 }
409
410 let actual_row_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Vertical);
412 assert_eq!(actual_row_counts, expected_row_counts, "row track counts");
413 let actual_col_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Horizontal);
414 assert_eq!(actual_col_counts, expected_col_counts, "column track counts");
415 }
416
417 #[test]
418 fn test_only_fixed_placement() {
419 let flow = GridAutoFlow::Row;
420 let explicit_col_count = 2;
421 let explicit_row_count = 2;
422 let children = {
423 vec![
424 (1, (line(1), auto(), line(1), auto()).into_grid_child(), (0, 1, 0, 1)),
426 (2, (line(-4), auto(), line(-3), auto()).into_grid_child(), (-1, 0, 0, 1)),
427 (3, (line(-3), auto(), line(-4), auto()).into_grid_child(), (0, 1, -1, 0)),
428 (4, (line(3), span(2), line(5), auto()).into_grid_child(), (2, 4, 4, 5)),
429 ]
430 };
431 let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
432 let expected_rows = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 3 };
433 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
434 }
435
436 #[test]
437 fn test_placement_spanning_origin() {
438 let flow = GridAutoFlow::Row;
439 let explicit_col_count = 2;
440 let explicit_row_count = 2;
441 let children = {
442 vec![
443 (1, (line(-1), line(-1), line(-1), line(-1)).into_grid_child(), (2, 3, 2, 3)),
445 (2, (line(-1), span(2), line(-1), span(2)).into_grid_child(), (2, 4, 2, 4)),
446 (3, (line(-4), line(-4), line(-4), line(-4)).into_grid_child(), (-1, 0, -1, 0)),
447 (4, (line(-4), span(2), line(-4), span(2)).into_grid_child(), (-1, 1, -1, 1)),
448 ]
449 };
450 let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
451 let expected_rows = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
452 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
453 }
454
455 #[test]
456 fn test_only_auto_placement_row_flow() {
457 let flow = GridAutoFlow::Row;
458 let explicit_col_count = 2;
459 let explicit_row_count = 2;
460 let children = {
461 let auto_child = (auto(), auto(), auto(), auto()).into_grid_child();
462 vec![
463 (1, auto_child.clone(), (0, 1, 0, 1)),
465 (2, auto_child.clone(), (1, 2, 0, 1)),
466 (3, auto_child.clone(), (0, 1, 1, 2)),
467 (4, auto_child.clone(), (1, 2, 1, 2)),
468 (5, auto_child.clone(), (0, 1, 2, 3)),
469 (6, auto_child.clone(), (1, 2, 2, 3)),
470 (7, auto_child.clone(), (0, 1, 3, 4)),
471 (8, auto_child.clone(), (1, 2, 3, 4)),
472 ]
473 };
474 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
475 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
476 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
477 }
478
479 #[test]
480 fn test_only_auto_placement_column_flow() {
481 let flow = GridAutoFlow::Column;
482 let explicit_col_count = 2;
483 let explicit_row_count = 2;
484 let children = {
485 let auto_child = (auto(), auto(), auto(), auto()).into_grid_child();
486 vec![
487 (1, auto_child.clone(), (0, 1, 0, 1)),
489 (2, auto_child.clone(), (0, 1, 1, 2)),
490 (3, auto_child.clone(), (1, 2, 0, 1)),
491 (4, auto_child.clone(), (1, 2, 1, 2)),
492 (5, auto_child.clone(), (2, 3, 0, 1)),
493 (6, auto_child.clone(), (2, 3, 1, 2)),
494 (7, auto_child.clone(), (3, 4, 0, 1)),
495 (8, auto_child.clone(), (3, 4, 1, 2)),
496 ]
497 };
498 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
499 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
500 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
501 }
502
503 #[test]
504 fn test_oversized_item() {
505 let flow = GridAutoFlow::Row;
506 let explicit_col_count = 2;
507 let explicit_row_count = 2;
508 let children = {
509 vec![
510 (1, (span(5), auto(), auto(), auto()).into_grid_child(), (0, 5, 0, 1)),
512 ]
513 };
514 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 3 };
515 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
516 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
517 }
518
519 #[test]
520 fn test_fixed_in_secondary_axis() {
521 let flow = GridAutoFlow::Row;
522 let explicit_col_count = 2;
523 let explicit_row_count = 2;
524 let children = {
525 vec![
526 (1, (span(2), auto(), line(1), auto()).into_grid_child(), (0, 2, 0, 1)),
528 (2, (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
529 (3, (auto(), auto(), line(1), auto()).into_grid_child(), (2, 3, 0, 1)),
530 (4, (auto(), auto(), line(4), auto()).into_grid_child(), (0, 1, 3, 4)),
531 ]
532 };
533 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 1 };
534 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
535 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
536 }
537
538 #[test]
539 fn test_definite_in_secondary_axis_with_fully_definite_negative() {
540 let flow = GridAutoFlow::Row;
541 let explicit_col_count = 2;
542 let explicit_row_count = 2;
543 let children = {
544 vec![
545 (2, (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
547 (1, (line(-4), auto(), line(2), auto()).into_grid_child(), (-1, 0, 1, 2)),
548 (3, (auto(), auto(), line(1), auto()).into_grid_child(), (-1, 0, 0, 1)),
549 ]
550 };
551 let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 0 };
552 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
553 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
554 }
555
556 #[test]
557 fn test_dense_packing_algorithm() {
558 let flow = GridAutoFlow::RowDense;
559 let explicit_col_count = 4;
560 let explicit_row_count = 4;
561 let children = {
562 vec![
563 (1, (line(2), auto(), line(1), auto()).into_grid_child(), (1, 2, 0, 1)), (2, (span(2), auto(), auto(), auto()).into_grid_child(), (2, 4, 0, 1)), (3, (auto(), auto(), auto(), auto()).into_grid_child(), (0, 1, 0, 1)), ]
568 };
569 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
570 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
571 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
572 }
573
574 #[test]
575 fn test_sparse_packing_algorithm() {
576 let flow = GridAutoFlow::Row;
577 let explicit_col_count = 4;
578 let explicit_row_count = 4;
579 let children = {
580 vec![
581 (1, (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 0, 1)), (2, (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 1, 2)), (3, (auto(), span(1), auto(), auto()).into_grid_child(), (3, 4, 1, 2)), ]
586 };
587 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
588 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
589 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
590 }
591
592 #[test]
593 fn test_auto_placement_in_negative_tracks() {
594 let flow = GridAutoFlow::RowDense;
595 let explicit_col_count = 2;
596 let explicit_row_count = 2;
597 let children = {
598 vec![
599 (1, (line(-5), auto(), line(1), auto()).into_grid_child(), (-2, -1, 0, 1)), (2, (auto(), auto(), line(2), auto()).into_grid_child(), (-2, -1, 1, 2)), (3, (auto(), auto(), auto(), auto()).into_grid_child(), (-1, 0, 0, 1)), ]
604 };
605 let expected_cols = TrackCounts { negative_implicit: 2, explicit: 2, positive_implicit: 0 };
606 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
607 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
608 }
609 }
610}