tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1114HTML 1040Markdown 12TOML 94698 Sutichaykuna

⎇ ae87fd0283feff32a5ceffe4c77394ff6ca88d39 src/compute/grid/placement.rs Rust · 607 siq'ikuna · 28.5 KB Ñawpa willay Pi imata llamkarqan ch'uya

Fix `cargo doc` warnings + add `cargo doc` to CI (#360) e2b3b2401 · 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::{AlignItems, 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    align_items: AlignItems,
21    justify_items: AlignItems,
22) where
23    ChildIter: Iterator<Item = (usize, Node, &'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, Node, &'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 x_neg_tracks = cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal).negative_implicit as i16;
117    let y_neg_tracks = cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical).negative_implicit as i16;
118    let grid_start_position = (OriginZeroLine(-x_neg_tracks), OriginZeroLine(-y_neg_tracks));
119    let mut grid_position = grid_start_position;
120    let mut idx = 0;
121    children_iter()
122        .filter(|(_, _, child_style)| !child_style.grid_placement(secondary_axis).is_definite())
123        .map(map_child_style_to_origin_zero_placement)
124        .for_each(|(index, child_node, child_placement, style)| {
125            idx += 1;
126            #[cfg(test)]
127            println!("\nAuto Item {idx}\n==============");
128
129            // Compute placement
130            let (primary_span, secondary_span) = place_indefinitely_positioned_item(
131                &*cell_occupancy_matrix,
132                child_placement,
133                grid_auto_flow,
134                grid_position,
135            );
136
137            // Record item
138            record_grid_placement(
139                cell_occupancy_matrix,
140                items,
141                child_node,
142                index,
143                style,
144                align_items,
145                justify_items,
146                primary_axis,
147                primary_span,
148                secondary_span,
149                CellOccupancyState::AutoPlaced,
150            );
151
152            // If using the "dense" placement algorithm then reset the grid position back to grid_start_position ready for the next item
153            // Otherwise set it to the position of the current item so that the next item it placed after it.
154            grid_position = match grid_auto_flow.is_dense() {
155                true => grid_start_position,
156                false => (primary_span.end, secondary_span.start),
157            }
158        });
159}
160
161/// 8.5. Grid Item Placement Algorithm
162/// Place a single definitely placed item into the grid
163fn place_definite_grid_item(
164    placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
165    primary_axis: AbsoluteAxis,
166) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
167    // Resolve spans to tracks
168    let primary_span = placement.get(primary_axis).resolve_definite_grid_lines();
169    let secondary_span = placement.get(primary_axis.other_axis()).resolve_definite_grid_lines();
170
171    (primary_span, secondary_span)
172}
173
174/// 8.5. Grid Item Placement Algorithm
175/// Step 2. Place remaining children with definite secondary axis positions
176fn place_definite_secondary_axis_item(
177    cell_occupancy_matrix: &CellOccupancyMatrix,
178    placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
179    auto_flow: GridAutoFlow,
180) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
181    let primary_axis = auto_flow.primary_axis();
182    let secondary_axis = primary_axis.other_axis();
183
184    let secondary_axis_placement = placement.get(secondary_axis).resolve_definite_grid_lines();
185    let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
186    let starting_position = match auto_flow.is_dense() {
187        true => primary_axis_grid_start_line,
188        false => cell_occupancy_matrix
189            .last_of_type(primary_axis, secondary_axis_placement.start, CellOccupancyState::AutoPlaced)
190            .unwrap_or(primary_axis_grid_start_line),
191    };
192
193    let mut position: OriginZeroLine = starting_position;
194    loop {
195        let primary_axis_placement = placement.get(primary_axis).resolve_indefinite_grid_tracks(position);
196
197        let does_fit = cell_occupancy_matrix.line_area_is_unoccupied(
198            primary_axis,
199            primary_axis_placement,
200            secondary_axis_placement,
201        );
202
203        if does_fit {
204            return (primary_axis_placement, secondary_axis_placement);
205        } else {
206            position += 1;
207        }
208    }
209}
210
211/// 8.5. Grid Item Placement Algorithm
212/// Step 4. Position the remaining grid items.
213fn place_indefinitely_positioned_item(
214    cell_occupancy_matrix: &CellOccupancyMatrix,
215    placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
216    auto_flow: GridAutoFlow,
217    grid_position: (OriginZeroLine, OriginZeroLine),
218) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
219    let primary_axis = auto_flow.primary_axis();
220
221    let primary_placement_style = placement.get(primary_axis);
222    let secondary_placement_style = placement.get(primary_axis.other_axis());
223
224    let primary_span = primary_placement_style.indefinite_span();
225    let secondary_span = secondary_placement_style.indefinite_span();
226    let has_definite_primary_axis_position = primary_placement_style.is_definite();
227    let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
228    let primary_axis_grid_end_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_end_line();
229    let secondary_axis_grid_start_line =
230        cell_occupancy_matrix.track_counts(primary_axis.other_axis()).implicit_start_line();
231
232    let line_area_is_occupied = |primary_span, secondary_span| {
233        !cell_occupancy_matrix.line_area_is_unoccupied(primary_axis, primary_span, secondary_span)
234    };
235
236    let (mut primary_idx, mut secondary_idx) = grid_position;
237
238    if has_definite_primary_axis_position {
239        let definite_primary_placement = primary_placement_style.resolve_definite_grid_lines();
240        let defined_primary_idx = definite_primary_placement.start;
241
242        // Compute starting position for search
243        if defined_primary_idx < primary_idx && secondary_idx != secondary_axis_grid_start_line {
244            secondary_idx = secondary_axis_grid_start_line;
245            primary_idx = defined_primary_idx + 1;
246        } else {
247            primary_idx = defined_primary_idx;
248        }
249
250        // Item has fixed primary axis position: so we simply increment the secondary axis position
251        // until we find a space that the item fits in
252        loop {
253            let primary_span = Line { start: primary_idx, end: primary_idx + primary_span };
254            let secondary_span = Line { start: secondary_idx, end: secondary_idx + secondary_span };
255
256            // If area is occupied, increment the index and try again
257            if line_area_is_occupied(primary_span, secondary_span) {
258                secondary_idx += 1;
259                continue;
260            }
261
262            // Once we find a free space, return that position
263            return (primary_span, secondary_span);
264        }
265    } else {
266        // Item does not have any fixed axis, so we search along the primary axis until we hit the end of the already
267        // existent tracks, and then we reset the primary axis back to zero and increment the secondary axis index.
268        // We continue in this vein until we find a space that the item fits in.
269        loop {
270            let primary_span = Line { start: primary_idx, end: primary_idx + primary_span };
271            let secondary_span = Line { start: secondary_idx, end: secondary_idx + secondary_span };
272
273            // If the primary index is out of bounds, then increment the secondary index and reset the primary
274            // index back to the start of the grid
275            let primary_out_of_bounds = primary_span.end > primary_axis_grid_end_line;
276            if primary_out_of_bounds {
277                secondary_idx += 1;
278                primary_idx = primary_axis_grid_start_line;
279                continue;
280            }
281
282            // If area is occupied, increment the primary index and try again
283            if line_area_is_occupied(primary_span, secondary_span) {
284                primary_idx += 1;
285                continue;
286            }
287
288            // Once we find a free space that's in bounds, return that position
289            return (primary_span, secondary_span);
290        }
291    }
292}
293
294/// Record the grid item in both CellOccupancyMatric and the GridItems list
295/// once a definite placement has been determined
296#[allow(clippy::too_many_arguments)]
297fn record_grid_placement(
298    cell_occupancy_matrix: &mut CellOccupancyMatrix,
299    items: &mut Vec<GridItem>,
300    node: Node,
301    index: usize,
302    style: &Style,
303    parent_align_items: AlignItems,
304    parent_justify_items: AlignItems,
305    primary_axis: AbsoluteAxis,
306    primary_span: Line<OriginZeroLine>,
307    secondary_span: Line<OriginZeroLine>,
308    placement_type: CellOccupancyState,
309) {
310    #[cfg(test)]
311    println!("BEFORE placement:");
312    #[cfg(test)]
313    println!("{cell_occupancy_matrix:?}");
314
315    // Mark area of grid as occupied
316    cell_occupancy_matrix.mark_area_as(primary_axis, primary_span, secondary_span, placement_type);
317
318    // Create grid item
319    let (col_span, row_span) = match primary_axis {
320        AbsoluteAxis::Horizontal => (primary_span, secondary_span),
321        AbsoluteAxis::Vertical => (secondary_span, primary_span),
322    };
323    items.push(GridItem::new_with_placement_style_and_order(
324        node,
325        col_span,
326        row_span,
327        style,
328        parent_align_items,
329        parent_justify_items,
330        index as u16,
331    ));
332
333    #[cfg(test)]
334    println!("AFTER placement:");
335    #[cfg(test)]
336    println!("{cell_occupancy_matrix:?}");
337    #[cfg(test)]
338    println!("\n");
339}
340
341#[allow(clippy::bool_assert_comparison)]
342#[cfg(test)]
343mod tests {
344    // It's more readable if the test code is uniform, so we tolerate unnecessary clones in tests
345    #![allow(clippy::redundant_clone)]
346
347    mod test_placement_algorithm {
348        use crate::compute::grid::implicit_grid::compute_grid_size_estimate;
349        use crate::compute::grid::types::TrackCounts;
350        use crate::compute::grid::util::*;
351        use crate::compute::grid::CellOccupancyMatrix;
352        use crate::prelude::*;
353        use crate::style::GridAutoFlow;
354        use slotmap::SlotMap;
355
356        use super::super::place_grid_items;
357
358        type ExpectedPlacement = (i16, i16, i16, i16);
359
360        fn placement_test_runner(
361            explicit_col_count: u16,
362            explicit_row_count: u16,
363            children: Vec<(usize, Node, Style, ExpectedPlacement)>,
364            expected_col_counts: TrackCounts,
365            expected_row_counts: TrackCounts,
366            flow: GridAutoFlow,
367        ) {
368            // Setup test
369            let children_iter = || children.iter().map(|(index, node, style, _)| (*index, *node, style));
370            let child_styles_iter = children.iter().map(|(_, _, style, _)| style);
371            let estimated_sizes = compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles_iter);
372            let mut items = Vec::new();
373            let mut cell_occupancy_matrix =
374                CellOccupancyMatrix::with_track_counts(estimated_sizes.0, estimated_sizes.1);
375
376            // Run placement algorithm
377            place_grid_items(
378                &mut cell_occupancy_matrix,
379                &mut items,
380                children_iter,
381                flow,
382                AlignSelf::Start,
383                AlignSelf::Start,
384            );
385
386            // Assert that each item has been placed in the right location
387            let mut sorted_children = children.clone();
388            sorted_children.sort_by_key(|child| child.0);
389            for (idx, ((_sort_order, node, _style, expected_placement), item)) in
390                sorted_children.iter().zip(items.iter()).enumerate()
391            {
392                assert_eq!(item.node, *node);
393                let actual_placement = (item.column.start, item.column.end, item.row.start, item.row.end);
394                assert_eq!(actual_placement, (*expected_placement).into_oz(), "Item {idx} (0-indexed)");
395            }
396
397            // Assert that the correct number of implicit rows have been generated
398            let actual_row_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Vertical);
399            assert_eq!(actual_row_counts, expected_row_counts, "row track counts");
400            let actual_col_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Horizontal);
401            assert_eq!(actual_col_counts, expected_col_counts, "column track counts");
402        }
403
404        #[test]
405        fn test_only_fixed_placement() {
406            let flow = GridAutoFlow::Row;
407            let explicit_col_count = 2;
408            let explicit_row_count = 2;
409            let children = {
410                let mut sm = SlotMap::new();
411                vec![
412                    // node, style (grid coords), expected_placement (oz coords)
413                    (1, sm.insert(()), (line(1), auto(), line(1), auto()).into_grid_child(), (0, 1, 0, 1)),
414                    (2, sm.insert(()), (line(-4), auto(), line(-3), auto()).into_grid_child(), (-1, 0, 0, 1)),
415                    (3, sm.insert(()), (line(-3), auto(), line(-4), auto()).into_grid_child(), (0, 1, -1, 0)),
416                    (4, sm.insert(()), (line(3), span(2), line(5), auto()).into_grid_child(), (2, 4, 4, 5)),
417                ]
418            };
419            let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
420            let expected_rows = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 3 };
421            placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
422        }
423
424        #[test]
425        fn test_placement_spanning_origin() {
426            let flow = GridAutoFlow::Row;
427            let explicit_col_count = 2;
428            let explicit_row_count = 2;
429            let children = {
430                let mut sm = SlotMap::new();
431                vec![
432                    // node, style (grid coords), expected_placement (oz coords)
433                    (1, sm.insert(()), (line(-1), line(-1), line(-1), line(-1)).into_grid_child(), (2, 3, 2, 3)),
434                    (2, sm.insert(()), (line(-1), span(2), line(-1), span(2)).into_grid_child(), (2, 4, 2, 4)),
435                    (3, sm.insert(()), (line(-4), line(-4), line(-4), line(-4)).into_grid_child(), (-1, 0, -1, 0)),
436                    (4, sm.insert(()), (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 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(), (1, 2, 0, 1)),
456                    (3, sm.insert(()), auto_child.clone(), (0, 1, 1, 2)),
457                    (4, sm.insert(()), auto_child.clone(), (1, 2, 1, 2)),
458                    (5, sm.insert(()), auto_child.clone(), (0, 1, 2, 3)),
459                    (6, sm.insert(()), auto_child.clone(), (1, 2, 2, 3)),
460                    (7, sm.insert(()), auto_child.clone(), (0, 1, 3, 4)),
461                    (8, sm.insert(()), auto_child.clone(), (1, 2, 3, 4)),
462                ]
463            };
464            let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
465            let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
466            placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
467        }
468
469        #[test]
470        fn test_only_auto_placement_column_flow() {
471            let flow = GridAutoFlow::Column;
472            let explicit_col_count = 2;
473            let explicit_row_count = 2;
474            let children = {
475                let mut sm = SlotMap::new();
476                let auto_child = (auto(), auto(), auto(), auto()).into_grid_child();
477                vec![
478                    // output order, node, style (grid coords), expected_placement (oz coords)
479                    (1, sm.insert(()), auto_child.clone(), (0, 1, 0, 1)),
480                    (2, sm.insert(()), auto_child.clone(), (0, 1, 1, 2)),
481                    (3, sm.insert(()), auto_child.clone(), (1, 2, 0, 1)),
482                    (4, sm.insert(()), auto_child.clone(), (1, 2, 1, 2)),
483                    (5, sm.insert(()), auto_child.clone(), (2, 3, 0, 1)),
484                    (6, sm.insert(()), auto_child.clone(), (2, 3, 1, 2)),
485                    (7, sm.insert(()), auto_child.clone(), (3, 4, 0, 1)),
486                    (8, sm.insert(()), auto_child.clone(), (3, 4, 1, 2)),
487                ]
488            };
489            let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
490            let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
491            placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
492        }
493
494        #[test]
495        fn test_oversized_item() {
496            let flow = GridAutoFlow::Row;
497            let explicit_col_count = 2;
498            let explicit_row_count = 2;
499            let children = {
500                let mut sm = SlotMap::new();
501                vec![
502                    // output order, node, style (grid coords), expected_placement (oz coords)
503                    (1, sm.insert(()), (span(5), auto(), auto(), auto()).into_grid_child(), (0, 5, 0, 1)),
504                ]
505            };
506            let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 3 };
507            let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
508            placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
509        }
510
511        #[test]
512        fn test_fixed_in_secondary_axis() {
513            let flow = GridAutoFlow::Row;
514            let explicit_col_count = 2;
515            let explicit_row_count = 2;
516            let children = {
517                let mut sm = SlotMap::new();
518                vec![
519                    // output order, node, style (grid coords), expected_placement (oz coords)
520                    (1, sm.insert(()), (span(2), auto(), line(1), auto()).into_grid_child(), (0, 2, 0, 1)),
521                    (2, sm.insert(()), (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
522                    (3, sm.insert(()), (auto(), auto(), line(1), auto()).into_grid_child(), (2, 3, 0, 1)),
523                    (4, sm.insert(()), (auto(), auto(), line(4), auto()).into_grid_child(), (0, 1, 3, 4)),
524                ]
525            };
526            let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 1 };
527            let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
528            placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
529        }
530
531        #[test]
532        fn test_definite_in_secondary_axis_with_fully_definite_negative() {
533            let flow = GridAutoFlow::Row;
534            let explicit_col_count = 2;
535            let explicit_row_count = 2;
536            let children = {
537                let mut sm = SlotMap::new();
538                vec![
539                    // output order, node, style (grid coords), expected_placement (oz coords)
540                    (2, sm.insert(()), (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
541                    (1, sm.insert(()), (line(-4), auto(), line(2), auto()).into_grid_child(), (-1, 0, 1, 2)),
542                    (3, sm.insert(()), (auto(), auto(), line(1), auto()).into_grid_child(), (-1, 0, 0, 1)),
543                ]
544            };
545            let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 0 };
546            let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
547            placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
548        }
549
550        #[test]
551        fn test_dense_packing_algorithm() {
552            let flow = GridAutoFlow::RowDense;
553            let explicit_col_count = 4;
554            let explicit_row_count = 4;
555            let children = {
556                let mut sm = SlotMap::new();
557                vec![
558                    // output order, node, style (grid coords), expected_placement (oz coords)
559                    (1, sm.insert(()), (line(2), auto(), line(1), auto()).into_grid_child(), (1, 2, 0, 1)), // Definitely positioned in column 2
560                    (2, sm.insert(()), (span(2), auto(), auto(), auto()).into_grid_child(), (2, 4, 0, 1)), // Spans 2 columns, so positioned after item 1
561                    (3, sm.insert(()), (auto(), auto(), auto(), auto()).into_grid_child(), (0, 1, 0, 1)), // Spans 1 column, so should be positioned before item 1
562                ]
563            };
564            let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
565            let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
566            placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
567        }
568
569        #[test]
570        fn test_sparse_packing_algorithm() {
571            let flow = GridAutoFlow::Row;
572            let explicit_col_count = 4;
573            let explicit_row_count = 4;
574            let children = {
575                let mut sm = SlotMap::new();
576                vec![
577                    // output order, node, style (grid coords), expected_placement (oz coords)
578                    (1, sm.insert(()), (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 0, 1)), // Width 3
579                    (2, sm.insert(()), (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 1, 2)), // Width 3 (wraps to next row)
580                    (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)
581                ]
582            };
583            let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
584            let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
585            placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
586        }
587
588        #[test]
589        fn test_auto_placement_in_negative_tracks() {
590            let flow = GridAutoFlow::RowDense;
591            let explicit_col_count = 2;
592            let explicit_row_count = 2;
593            let children = {
594                let mut sm = SlotMap::new();
595                vec![
596                    // output order, node, style (grid coords), expected_placement (oz coords)
597                    (1, sm.insert(()), (line(-5), auto(), line(1), auto()).into_grid_child(), (-2, -1, 0, 1)), // Row 1. Definitely positioned in column -2
598                    (2, sm.insert(()), (auto(), auto(), line(2), auto()).into_grid_child(), (-2, -1, 1, 2)), // Row 2. Auto positioned in column -2
599                    (3, sm.insert(()), (auto(), auto(), auto(), auto()).into_grid_child(), (-1, 0, 0, 1)), // Row 1. Auto positioned in column -1
600                ]
601            };
602            let expected_cols = TrackCounts { negative_implicit: 2, explicit: 2, positive_implicit: 0 };
603            let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
604            placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
605        }
606    }
607}