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