gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 10TOML 3CSS 1348 Definiciones

⎇ 09c7a718b2eaee87aa9d0505d5a98bd1d33a32db src/compute/grid/placement.rs Rust · 594 líneas · 27.4 KB Historia Quién tocó qué crudo

Fix clippy lints (2024-03) (#630) dc9862a93 · Nico Burns ·

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