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