• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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