tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1077HTML 1008Markdown 12TOML 83209 Definitions

⎇ 859cc921b3adb8e3b184e6fc0cf0c40b6c5a625b src/compute/grid/placement.rs Rust · 582 lines · 28.0 KB History Who touched what raw

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