⎇ ae87fd0283feff32a5ceffe4c77394ff6ca88d39
src/compute/grid/placement.rs
History
Who touched what
raw
Fix `cargo doc` warnings + add `cargo doc` to CI (#360)
1//! Implements placing items in the grid and resolving the implicit grid.
2//! <https://www.w3.org/TR/css-grid-1/#placement>
3use super::types::{CellOccupancyMatrix, CellOccupancyState, GridItem};
4use super::OriginZeroLine;
5use crate::axis::{AbsoluteAxis, InBothAbsAxis};
6use crate::geometry::Line;
7use crate::node::Node;
8use crate::style::{AlignItems, GridAutoFlow, OriginZeroGridPlacement, Style};
9use crate::sys::Vec;
10
11/// 8.5. Grid Item Placement Algorithm
12/// Place items into the grid, generating new rows/column into the implicit grid as required
13///
14/// [Specification](https://www.w3.org/TR/css-grid-2/#auto-placement-algo)
15pub(super) fn place_grid_items<'a, ChildIter>(
16 cell_occupancy_matrix: &mut CellOccupancyMatrix,
17 items: &mut Vec<GridItem>,
18 children_iter: impl Fn() -> ChildIter,
19 grid_auto_flow: GridAutoFlow,
20 align_items: AlignItems,
21 justify_items: AlignItems,
22) where
23 ChildIter: Iterator<Item = (usize, Node, &'a Style)>,
24{
25 let primary_axis = grid_auto_flow.primary_axis();
26 let secondary_axis = primary_axis.other_axis();
27
28 let map_child_style_to_origin_zero_placement = {
29 let explicit_col_count = cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal).explicit;
30 let explicit_row_count = cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical).explicit;
31 move |(index, node, style): (usize, Node, &'a Style)| -> (_, _, _, &'a Style) {
32 let origin_zero_placement = InBothAbsAxis {
33 horizontal: style.grid_column.map(|placement| placement.into_origin_zero_placement(explicit_col_count)),
34 vertical: style.grid_row.map(|placement| placement.into_origin_zero_placement(explicit_row_count)),
35 };
36 (index, node, origin_zero_placement, style)
37 }
38 };
39
40 // 1. Place children with definite positions
41 let mut idx = 0;
42 children_iter()
43 .filter(|(_, _, child_style)| child_style.grid_row.is_definite() && child_style.grid_column.is_definite())
44 .map(map_child_style_to_origin_zero_placement)
45 .for_each(|(index, child_node, child_placement, style)| {
46 idx += 1;
47 #[cfg(test)]
48 println!("Definite Item {idx}\n==============");
49
50 let (row_span, col_span) = place_definite_grid_item(child_placement, primary_axis);
51 record_grid_placement(
52 cell_occupancy_matrix,
53 items,
54 child_node,
55 index,
56 style,
57 align_items,
58 justify_items,
59 primary_axis,
60 row_span,
61 col_span,
62 CellOccupancyState::DefinitelyPlaced,
63 );
64 });
65
66 // 2. Place remaining children with definite secondary axis positions
67 let mut idx = 0;
68 children_iter()
69 .filter(|(_, _, child_style)| {
70 child_style.grid_placement(secondary_axis).is_definite()
71 && !child_style.grid_placement(primary_axis).is_definite()
72 })
73 .map(map_child_style_to_origin_zero_placement)
74 .for_each(|(index, child_node, child_placement, style)| {
75 idx += 1;
76 #[cfg(test)]
77 println!("Definite Secondary Item {idx}\n==============");
78
79 let (primary_span, secondary_span) =
80 place_definite_secondary_axis_item(&*cell_occupancy_matrix, child_placement, grid_auto_flow);
81
82 record_grid_placement(
83 cell_occupancy_matrix,
84 items,
85 child_node,
86 index,
87 style,
88 align_items,
89 justify_items,
90 primary_axis,
91 primary_span,
92 secondary_span,
93 CellOccupancyState::AutoPlaced,
94 );
95 });
96
97 // 3. Determine the number of columns in the implicit grid
98 // By the time we get to this point in the execution, this is actually already accounted for:
99 //
100 // 3.1 Start with the columns from the explicit grid
101 // => Handled by grid size estimate which is used to pre-size the GridOccupancyMatrix
102 //
103 // 3.2 Among all the items with a definite column position (explicitly positioned items, items positioned in the previous step,
104 // and items not yet positioned but with a definite column) add columns to the beginning and end of the implicit grid as necessary
105 // to accommodate those items.
106 // => Handled by expand_to_fit_range which expands the GridOccupancyMatrix as necessary
107 // -> Called by mark_area_as
108 // -> Called by record_grid_placement
109 //
110 // 3.3 If the largest column span among all the items without a definite column position is larger than the width of
111 // the implicit grid, add columns to the end of the implicit grid to accommodate that column span.
112 // => Handled by grid size estimate which is used to pre-size the GridOccupancyMatrix
113
114 // 4. Position the remaining grid items
115 // (which either have definite position only in the secondary axis or indefinite positions in both axis)
116 let x_neg_tracks = cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal).negative_implicit as i16;
117 let y_neg_tracks = cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical).negative_implicit as i16;
118 let grid_start_position = (OriginZeroLine(-x_neg_tracks), OriginZeroLine(-y_neg_tracks));
119 let mut grid_position = grid_start_position;
120 let mut idx = 0;
121 children_iter()
122 .filter(|(_, _, child_style)| !child_style.grid_placement(secondary_axis).is_definite())
123 .map(map_child_style_to_origin_zero_placement)
124 .for_each(|(index, child_node, child_placement, style)| {
125 idx += 1;
126 #[cfg(test)]
127 println!("\nAuto Item {idx}\n==============");
128
129 // Compute placement
130 let (primary_span, secondary_span) = place_indefinitely_positioned_item(
131 &*cell_occupancy_matrix,
132 child_placement,
133 grid_auto_flow,
134 grid_position,
135 );
136
137 // Record item
138 record_grid_placement(
139 cell_occupancy_matrix,
140 items,
141 child_node,
142 index,
143 style,
144 align_items,
145 justify_items,
146 primary_axis,
147 primary_span,
148 secondary_span,
149 CellOccupancyState::AutoPlaced,
150 );
151
152 // If using the "dense" placement algorithm then reset the grid position back to grid_start_position ready for the next item
153 // Otherwise set it to the position of the current item so that the next item it placed after it.
154 grid_position = match grid_auto_flow.is_dense() {
155 true => grid_start_position,
156 false => (primary_span.end, secondary_span.start),
157 }
158 });
159}
160
161/// 8.5. Grid Item Placement Algorithm
162/// Place a single definitely placed item into the grid
163fn place_definite_grid_item(
164 placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
165 primary_axis: AbsoluteAxis,
166) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
167 // Resolve spans to tracks
168 let primary_span = placement.get(primary_axis).resolve_definite_grid_lines();
169 let secondary_span = placement.get(primary_axis.other_axis()).resolve_definite_grid_lines();
170
171 (primary_span, secondary_span)
172}
173
174/// 8.5. Grid Item Placement Algorithm
175/// Step 2. Place remaining children with definite secondary axis positions
176fn place_definite_secondary_axis_item(
177 cell_occupancy_matrix: &CellOccupancyMatrix,
178 placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
179 auto_flow: GridAutoFlow,
180) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
181 let primary_axis = auto_flow.primary_axis();
182 let secondary_axis = primary_axis.other_axis();
183
184 let secondary_axis_placement = placement.get(secondary_axis).resolve_definite_grid_lines();
185 let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
186 let starting_position = match auto_flow.is_dense() {
187 true => primary_axis_grid_start_line,
188 false => cell_occupancy_matrix
189 .last_of_type(primary_axis, secondary_axis_placement.start, CellOccupancyState::AutoPlaced)
190 .unwrap_or(primary_axis_grid_start_line),
191 };
192
193 let mut position: OriginZeroLine = starting_position;
194 loop {
195 let primary_axis_placement = placement.get(primary_axis).resolve_indefinite_grid_tracks(position);
196
197 let does_fit = cell_occupancy_matrix.line_area_is_unoccupied(
198 primary_axis,
199 primary_axis_placement,
200 secondary_axis_placement,
201 );
202
203 if does_fit {
204 return (primary_axis_placement, secondary_axis_placement);
205 } else {
206 position += 1;
207 }
208 }
209}
210
211/// 8.5. Grid Item Placement Algorithm
212/// Step 4. Position the remaining grid items.
213fn place_indefinitely_positioned_item(
214 cell_occupancy_matrix: &CellOccupancyMatrix,
215 placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
216 auto_flow: GridAutoFlow,
217 grid_position: (OriginZeroLine, OriginZeroLine),
218) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
219 let primary_axis = auto_flow.primary_axis();
220
221 let primary_placement_style = placement.get(primary_axis);
222 let secondary_placement_style = placement.get(primary_axis.other_axis());
223
224 let primary_span = primary_placement_style.indefinite_span();
225 let secondary_span = secondary_placement_style.indefinite_span();
226 let has_definite_primary_axis_position = primary_placement_style.is_definite();
227 let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
228 let primary_axis_grid_end_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_end_line();
229 let secondary_axis_grid_start_line =
230 cell_occupancy_matrix.track_counts(primary_axis.other_axis()).implicit_start_line();
231
232 let line_area_is_occupied = |primary_span, secondary_span| {
233 !cell_occupancy_matrix.line_area_is_unoccupied(primary_axis, primary_span, secondary_span)
234 };
235
236 let (mut primary_idx, mut secondary_idx) = grid_position;
237
238 if has_definite_primary_axis_position {
239 let definite_primary_placement = primary_placement_style.resolve_definite_grid_lines();
240 let defined_primary_idx = definite_primary_placement.start;
241
242 // Compute starting position for search
243 if defined_primary_idx < primary_idx && secondary_idx != secondary_axis_grid_start_line {
244 secondary_idx = secondary_axis_grid_start_line;
245 primary_idx = defined_primary_idx + 1;
246 } else {
247 primary_idx = defined_primary_idx;
248 }
249
250 // Item has fixed primary axis position: so we simply increment the secondary axis position
251 // until we find a space that the item fits in
252 loop {
253 let primary_span = Line { start: primary_idx, end: primary_idx + primary_span };
254 let secondary_span = Line { start: secondary_idx, end: secondary_idx + secondary_span };
255
256 // If area is occupied, increment the index and try again
257 if line_area_is_occupied(primary_span, secondary_span) {
258 secondary_idx += 1;
259 continue;
260 }
261
262 // Once we find a free space, return that position
263 return (primary_span, secondary_span);
264 }
265 } else {
266 // Item does not have any fixed axis, so we search along the primary axis until we hit the end of the already
267 // existent tracks, and then we reset the primary axis back to zero and increment the secondary axis index.
268 // We continue in this vein until we find a space that the item fits in.
269 loop {
270 let primary_span = Line { start: primary_idx, end: primary_idx + primary_span };
271 let secondary_span = Line { start: secondary_idx, end: secondary_idx + secondary_span };
272
273 // If the primary index is out of bounds, then increment the secondary index and reset the primary
274 // index back to the start of the grid
275 let primary_out_of_bounds = primary_span.end > primary_axis_grid_end_line;
276 if primary_out_of_bounds {
277 secondary_idx += 1;
278 primary_idx = primary_axis_grid_start_line;
279 continue;
280 }
281
282 // If area is occupied, increment the primary index and try again
283 if line_area_is_occupied(primary_span, secondary_span) {
284 primary_idx += 1;
285 continue;
286 }
287
288 // Once we find a free space that's in bounds, return that position
289 return (primary_span, secondary_span);
290 }
291 }
292}
293
294/// Record the grid item in both CellOccupancyMatric and the GridItems list
295/// once a definite placement has been determined
296#[allow(clippy::too_many_arguments)]
297fn record_grid_placement(
298 cell_occupancy_matrix: &mut CellOccupancyMatrix,
299 items: &mut Vec<GridItem>,
300 node: Node,
301 index: usize,
302 style: &Style,
303 parent_align_items: AlignItems,
304 parent_justify_items: AlignItems,
305 primary_axis: AbsoluteAxis,
306 primary_span: Line<OriginZeroLine>,
307 secondary_span: Line<OriginZeroLine>,
308 placement_type: CellOccupancyState,
309) {
310 #[cfg(test)]
311 println!("BEFORE placement:");
312 #[cfg(test)]
313 println!("{cell_occupancy_matrix:?}");
314
315 // Mark area of grid as occupied
316 cell_occupancy_matrix.mark_area_as(primary_axis, primary_span, secondary_span, placement_type);
317
318 // Create grid item
319 let (col_span, row_span) = match primary_axis {
320 AbsoluteAxis::Horizontal => (primary_span, secondary_span),
321 AbsoluteAxis::Vertical => (secondary_span, primary_span),
322 };
323 items.push(GridItem::new_with_placement_style_and_order(
324 node,
325 col_span,
326 row_span,
327 style,
328 parent_align_items,
329 parent_justify_items,
330 index as u16,
331 ));
332
333 #[cfg(test)]
334 println!("AFTER placement:");
335 #[cfg(test)]
336 println!("{cell_occupancy_matrix:?}");
337 #[cfg(test)]
338 println!("\n");
339}
340
341#[allow(clippy::bool_assert_comparison)]
342#[cfg(test)]
343mod tests {
344 // It's more readable if the test code is uniform, so we tolerate unnecessary clones in tests
345 #![allow(clippy::redundant_clone)]
346
347 mod test_placement_algorithm {
348 use crate::compute::grid::implicit_grid::compute_grid_size_estimate;
349 use crate::compute::grid::types::TrackCounts;
350 use crate::compute::grid::util::*;
351 use crate::compute::grid::CellOccupancyMatrix;
352 use crate::prelude::*;
353 use crate::style::GridAutoFlow;
354 use slotmap::SlotMap;
355
356 use super::super::place_grid_items;
357
358 type ExpectedPlacement = (i16, i16, i16, i16);
359
360 fn placement_test_runner(
361 explicit_col_count: u16,
362 explicit_row_count: u16,
363 children: Vec<(usize, Node, Style, ExpectedPlacement)>,
364 expected_col_counts: TrackCounts,
365 expected_row_counts: TrackCounts,
366 flow: GridAutoFlow,
367 ) {
368 // Setup test
369 let children_iter = || children.iter().map(|(index, node, style, _)| (*index, *node, style));
370 let child_styles_iter = children.iter().map(|(_, _, style, _)| style);
371 let estimated_sizes = compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles_iter);
372 let mut items = Vec::new();
373 let mut cell_occupancy_matrix =
374 CellOccupancyMatrix::with_track_counts(estimated_sizes.0, estimated_sizes.1);
375
376 // Run placement algorithm
377 place_grid_items(
378 &mut cell_occupancy_matrix,
379 &mut items,
380 children_iter,
381 flow,
382 AlignSelf::Start,
383 AlignSelf::Start,
384 );
385
386 // Assert that each item has been placed in the right location
387 let mut sorted_children = children.clone();
388 sorted_children.sort_by_key(|child| child.0);
389 for (idx, ((_sort_order, node, _style, expected_placement), item)) in
390 sorted_children.iter().zip(items.iter()).enumerate()
391 {
392 assert_eq!(item.node, *node);
393 let actual_placement = (item.column.start, item.column.end, item.row.start, item.row.end);
394 assert_eq!(actual_placement, (*expected_placement).into_oz(), "Item {idx} (0-indexed)");
395 }
396
397 // Assert that the correct number of implicit rows have been generated
398 let actual_row_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Vertical);
399 assert_eq!(actual_row_counts, expected_row_counts, "row track counts");
400 let actual_col_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Horizontal);
401 assert_eq!(actual_col_counts, expected_col_counts, "column track counts");
402 }
403
404 #[test]
405 fn test_only_fixed_placement() {
406 let flow = GridAutoFlow::Row;
407 let explicit_col_count = 2;
408 let explicit_row_count = 2;
409 let children = {
410 let mut sm = SlotMap::new();
411 vec![
412 // node, style (grid coords), expected_placement (oz coords)
413 (1, sm.insert(()), (line(1), auto(), line(1), auto()).into_grid_child(), (0, 1, 0, 1)),
414 (2, sm.insert(()), (line(-4), auto(), line(-3), auto()).into_grid_child(), (-1, 0, 0, 1)),
415 (3, sm.insert(()), (line(-3), auto(), line(-4), auto()).into_grid_child(), (0, 1, -1, 0)),
416 (4, sm.insert(()), (line(3), span(2), line(5), auto()).into_grid_child(), (2, 4, 4, 5)),
417 ]
418 };
419 let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
420 let expected_rows = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 3 };
421 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
422 }
423
424 #[test]
425 fn test_placement_spanning_origin() {
426 let flow = GridAutoFlow::Row;
427 let explicit_col_count = 2;
428 let explicit_row_count = 2;
429 let children = {
430 let mut sm = SlotMap::new();
431 vec![
432 // node, style (grid coords), expected_placement (oz coords)
433 (1, sm.insert(()), (line(-1), line(-1), line(-1), line(-1)).into_grid_child(), (2, 3, 2, 3)),
434 (2, sm.insert(()), (line(-1), span(2), line(-1), span(2)).into_grid_child(), (2, 4, 2, 4)),
435 (3, sm.insert(()), (line(-4), line(-4), line(-4), line(-4)).into_grid_child(), (-1, 0, -1, 0)),
436 (4, sm.insert(()), (line(-4), span(2), line(-4), span(2)).into_grid_child(), (-1, 1, -1, 1)),
437 ]
438 };
439 let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
440 let expected_rows = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
441 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
442 }
443
444 #[test]
445 fn test_only_auto_placement_row_flow() {
446 let flow = GridAutoFlow::Row;
447 let explicit_col_count = 2;
448 let explicit_row_count = 2;
449 let children = {
450 let mut sm = SlotMap::new();
451 let auto_child = (auto(), auto(), auto(), auto()).into_grid_child();
452 vec![
453 // output order, node, style (grid coords), expected_placement (oz coords)
454 (1, sm.insert(()), auto_child.clone(), (0, 1, 0, 1)),
455 (2, sm.insert(()), auto_child.clone(), (1, 2, 0, 1)),
456 (3, sm.insert(()), auto_child.clone(), (0, 1, 1, 2)),
457 (4, sm.insert(()), auto_child.clone(), (1, 2, 1, 2)),
458 (5, sm.insert(()), auto_child.clone(), (0, 1, 2, 3)),
459 (6, sm.insert(()), auto_child.clone(), (1, 2, 2, 3)),
460 (7, sm.insert(()), auto_child.clone(), (0, 1, 3, 4)),
461 (8, sm.insert(()), auto_child.clone(), (1, 2, 3, 4)),
462 ]
463 };
464 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
465 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
466 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
467 }
468
469 #[test]
470 fn test_only_auto_placement_column_flow() {
471 let flow = GridAutoFlow::Column;
472 let explicit_col_count = 2;
473 let explicit_row_count = 2;
474 let children = {
475 let mut sm = SlotMap::new();
476 let auto_child = (auto(), auto(), auto(), auto()).into_grid_child();
477 vec![
478 // output order, node, style (grid coords), expected_placement (oz coords)
479 (1, sm.insert(()), auto_child.clone(), (0, 1, 0, 1)),
480 (2, sm.insert(()), auto_child.clone(), (0, 1, 1, 2)),
481 (3, sm.insert(()), auto_child.clone(), (1, 2, 0, 1)),
482 (4, sm.insert(()), auto_child.clone(), (1, 2, 1, 2)),
483 (5, sm.insert(()), auto_child.clone(), (2, 3, 0, 1)),
484 (6, sm.insert(()), auto_child.clone(), (2, 3, 1, 2)),
485 (7, sm.insert(()), auto_child.clone(), (3, 4, 0, 1)),
486 (8, sm.insert(()), auto_child.clone(), (3, 4, 1, 2)),
487 ]
488 };
489 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
490 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
491 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
492 }
493
494 #[test]
495 fn test_oversized_item() {
496 let flow = GridAutoFlow::Row;
497 let explicit_col_count = 2;
498 let explicit_row_count = 2;
499 let children = {
500 let mut sm = SlotMap::new();
501 vec![
502 // output order, node, style (grid coords), expected_placement (oz coords)
503 (1, sm.insert(()), (span(5), auto(), auto(), auto()).into_grid_child(), (0, 5, 0, 1)),
504 ]
505 };
506 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 3 };
507 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
508 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
509 }
510
511 #[test]
512 fn test_fixed_in_secondary_axis() {
513 let flow = GridAutoFlow::Row;
514 let explicit_col_count = 2;
515 let explicit_row_count = 2;
516 let children = {
517 let mut sm = SlotMap::new();
518 vec![
519 // output order, node, style (grid coords), expected_placement (oz coords)
520 (1, sm.insert(()), (span(2), auto(), line(1), auto()).into_grid_child(), (0, 2, 0, 1)),
521 (2, sm.insert(()), (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
522 (3, sm.insert(()), (auto(), auto(), line(1), auto()).into_grid_child(), (2, 3, 0, 1)),
523 (4, sm.insert(()), (auto(), auto(), line(4), auto()).into_grid_child(), (0, 1, 3, 4)),
524 ]
525 };
526 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 1 };
527 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
528 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
529 }
530
531 #[test]
532 fn test_definite_in_secondary_axis_with_fully_definite_negative() {
533 let flow = GridAutoFlow::Row;
534 let explicit_col_count = 2;
535 let explicit_row_count = 2;
536 let children = {
537 let mut sm = SlotMap::new();
538 vec![
539 // output order, node, style (grid coords), expected_placement (oz coords)
540 (2, sm.insert(()), (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
541 (1, sm.insert(()), (line(-4), auto(), line(2), auto()).into_grid_child(), (-1, 0, 1, 2)),
542 (3, sm.insert(()), (auto(), auto(), line(1), auto()).into_grid_child(), (-1, 0, 0, 1)),
543 ]
544 };
545 let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 0 };
546 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
547 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
548 }
549
550 #[test]
551 fn test_dense_packing_algorithm() {
552 let flow = GridAutoFlow::RowDense;
553 let explicit_col_count = 4;
554 let explicit_row_count = 4;
555 let children = {
556 let mut sm = SlotMap::new();
557 vec![
558 // output order, node, style (grid coords), expected_placement (oz coords)
559 (1, sm.insert(()), (line(2), auto(), line(1), auto()).into_grid_child(), (1, 2, 0, 1)), // Definitely positioned in column 2
560 (2, sm.insert(()), (span(2), auto(), auto(), auto()).into_grid_child(), (2, 4, 0, 1)), // Spans 2 columns, so positioned after item 1
561 (3, sm.insert(()), (auto(), auto(), auto(), auto()).into_grid_child(), (0, 1, 0, 1)), // Spans 1 column, so should be positioned before item 1
562 ]
563 };
564 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
565 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
566 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
567 }
568
569 #[test]
570 fn test_sparse_packing_algorithm() {
571 let flow = GridAutoFlow::Row;
572 let explicit_col_count = 4;
573 let explicit_row_count = 4;
574 let children = {
575 let mut sm = SlotMap::new();
576 vec![
577 // output order, node, style (grid coords), expected_placement (oz coords)
578 (1, sm.insert(()), (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 0, 1)), // Width 3
579 (2, sm.insert(()), (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 1, 2)), // Width 3 (wraps to next row)
580 (3, sm.insert(()), (auto(), span(1), auto(), auto()).into_grid_child(), (3, 4, 1, 2)), // Width 1 (uses second row as we're already on it)
581 ]
582 };
583 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
584 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
585 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
586 }
587
588 #[test]
589 fn test_auto_placement_in_negative_tracks() {
590 let flow = GridAutoFlow::RowDense;
591 let explicit_col_count = 2;
592 let explicit_row_count = 2;
593 let children = {
594 let mut sm = SlotMap::new();
595 vec![
596 // output order, node, style (grid coords), expected_placement (oz coords)
597 (1, sm.insert(()), (line(-5), auto(), line(1), auto()).into_grid_child(), (-2, -1, 0, 1)), // Row 1. Definitely positioned in column -2
598 (2, sm.insert(()), (auto(), auto(), line(2), auto()).into_grid_child(), (-2, -1, 1, 2)), // Row 2. Auto positioned in column -2
599 (3, sm.insert(()), (auto(), auto(), auto(), auto()).into_grid_child(), (-1, 0, 0, 1)), // Row 1. Auto positioned in column -1
600 ]
601 };
602 let expected_cols = TrackCounts { negative_implicit: 2, explicit: 2, positive_implicit: 0 };
603 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
604 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
605 }
606 }
607}