tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81412 Definitions

⎇ 50ff9cb42edca669a335afffbf6e05808e52068a src/compute/grid/placement.rs Rust · 599 lines · 27.5 KB History Who touched what raw

fix: removed wrong addition, causing items to be misplaced. (#817) 50ff9cb42 · koko ·

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::geometry::Line;
6use crate::geometry::{AbsoluteAxis, InBothAbsAxis};
7use crate::style::{AlignItems, GridAutoFlow, OriginZeroGridPlacement};
8use crate::tree::NodeId;
9use crate::util::sys::Vec;
10use 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)
16pub(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
169fn 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
182fn 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.
219fn 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;
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)]
304fn 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)]
349mod 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
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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}