⎇ d97b1d3506caee8bc85ac3ff8c109bbb2b8afeae
src/compute/grid/placement.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
Clamp origin-zero grid lines to a symmetric [-10000, 10000] range
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::{NamedLineResolver, OriginZeroLine, MAX_OZ_LINE, MIN_OZ_LINE};
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::{CoreStyle, Direction, GridItemStyle};
11
12#[inline]
13/// Returns whether placement/search should run in reverse for this axis.
14fn axis_is_reversed(direction: Direction, axis: AbsoluteAxis) -> bool {
15 direction.is_rtl() && axis == AbsoluteAxis::Horizontal
16}
17
18#[inline]
19/// Advances the cursor by one track in the active search direction.
20fn advance_position(position: OriginZeroLine, axis_is_reversed: bool) -> OriginZeroLine {
21 if axis_is_reversed {
22 OriginZeroLine(position.0 - 1)
23 } else {
24 OriginZeroLine(position.0 + 1)
25 }
26}
27
28#[inline]
29/// Returns the initial search line for sparse/dense placement in the given axis direction.
30fn search_start_line(
31 grid_start_line: OriginZeroLine,
32 grid_end_line: OriginZeroLine,
33 axis_is_reversed: bool,
34) -> OriginZeroLine {
35 if axis_is_reversed {
36 grid_end_line - 1
37 } else {
38 grid_start_line
39 }
40}
41
42#[inline]
43/// Resolves an indefinite span at `position`, respecting the active axis direction.
44fn resolve_indefinite_grid_span(position: OriginZeroLine, span: u16, axis_is_reversed: bool) -> Line<OriginZeroLine> {
45 if axis_is_reversed {
46 Line { start: (position - span) + 1, end: position + 1 }
47 } else {
48 Line { start: position, end: position + span }
49 }
50}
51
52#[inline]
53/// Mirrors a horizontal span around the explicit grid width.
54fn mirror_horizontal_span(span: Line<OriginZeroLine>, explicit_col_count: u16) -> Line<OriginZeroLine> {
55 let explicit_col_end_line = explicit_col_count as i16;
56 Line {
57 start: OriginZeroLine(explicit_col_end_line - span.end.0),
58 end: OriginZeroLine(explicit_col_end_line - span.start.0),
59 }
60}
61
62#[inline]
63/// Mirrors horizontal spans for RTL while leaving all other spans unchanged.
64fn maybe_mirror_span(
65 span: Line<OriginZeroLine>,
66 axis: AbsoluteAxis,
67 direction: Direction,
68 explicit_col_count: u16,
69) -> Line<OriginZeroLine> {
70 if axis == AbsoluteAxis::Horizontal && direction.is_rtl() {
71 mirror_horizontal_span(span, explicit_col_count)
72 } else {
73 span
74 }
75}
76
77/// 8.5. Grid Item Placement Algorithm
78/// Place items into the grid, generating new rows/column into the implicit grid as required
79///
80/// [Specification](https://www.w3.org/TR/css-grid-2/#auto-placement-algo)
81#[allow(clippy::too_many_arguments)]
82pub(super) fn place_grid_items<'a, S, ChildIter>(
83 cell_occupancy_matrix: &mut CellOccupancyMatrix,
84 items: &mut Vec<GridItem>,
85 children_iter: impl Fn() -> ChildIter,
86 direction: Direction,
87 grid_auto_flow: GridAutoFlow,
88 align_items: AlignItems,
89 justify_items: AlignItems,
90 named_line_resolver: &NamedLineResolver<<S as CoreStyle>::CustomIdent>,
91) where
92 S: GridItemStyle + 'a,
93 ChildIter: Iterator<Item = (usize, NodeId, S)>,
94{
95 let primary_axis = grid_auto_flow.primary_axis();
96 let secondary_axis = primary_axis.other_axis();
97 let explicit_col_count = cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal).explicit;
98
99 let map_child_style_to_origin_zero_placement = {
100 let explicit_row_count = cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical).explicit;
101 move |(index, node, style): (usize, NodeId, S)| -> (_, _, _, S) {
102 let origin_zero_placement = InBothAbsAxis {
103 horizontal: named_line_resolver
104 .resolve_column_names(&style.grid_column())
105 .map(|placement| placement.into_origin_zero_placement(explicit_col_count)),
106 vertical: named_line_resolver
107 .resolve_row_names(&style.grid_row())
108 .map(|placement| placement.into_origin_zero_placement(explicit_row_count)),
109 };
110 (index, node, origin_zero_placement, style)
111 }
112 };
113
114 // 1. Place children with definite positions
115 let mut idx = 0;
116 children_iter()
117 .map(map_child_style_to_origin_zero_placement)
118 .filter(|(_, _, placement, _)| placement.horizontal.is_definite() && placement.vertical.is_definite())
119 .for_each(|(index, child_node, child_placement, style)| {
120 idx += 1;
121 #[cfg(test)]
122 println!("Definite Item {idx}\n==============");
123
124 let (row_span, col_span) =
125 place_definite_grid_item(child_placement, primary_axis, direction, explicit_col_count);
126 record_grid_placement(
127 cell_occupancy_matrix,
128 items,
129 child_node,
130 index,
131 style,
132 align_items,
133 justify_items,
134 primary_axis,
135 row_span,
136 col_span,
137 CellOccupancyState::DefinitelyPlaced,
138 );
139 });
140
141 // 2. Place remaining children with definite secondary axis positions
142 let mut idx = 0;
143 children_iter()
144 .map(map_child_style_to_origin_zero_placement)
145 .filter(|(_, _, placement, _)| {
146 placement.get(secondary_axis).is_definite() && !placement.get(primary_axis).is_definite()
147 })
148 .for_each(|(index, child_node, child_placement, style)| {
149 idx += 1;
150 #[cfg(test)]
151 println!("Definite Secondary Item {idx}\n==============");
152
153 let (primary_span, secondary_span) = place_definite_secondary_axis_item(
154 &*cell_occupancy_matrix,
155 child_placement,
156 grid_auto_flow,
157 direction,
158 explicit_col_count,
159 );
160
161 record_grid_placement(
162 cell_occupancy_matrix,
163 items,
164 child_node,
165 index,
166 style,
167 align_items,
168 justify_items,
169 primary_axis,
170 primary_span,
171 secondary_span,
172 CellOccupancyState::AutoPlaced,
173 );
174 });
175
176 // 3. Determine the number of columns in the implicit grid
177 // By the time we get to this point in the execution, this is actually already accounted for:
178 //
179 // 3.1 Start with the columns from the explicit grid
180 // => Handled by grid size estimate which is used to pre-size the GridOccupancyMatrix
181 //
182 // 3.2 Among all the items with a definite column position (explicitly positioned items, items positioned in the previous step,
183 // and items not yet positioned but with a definite column) add columns to the beginning and end of the implicit grid as necessary
184 // to accommodate those items.
185 // => Handled by expand_to_fit_range which expands the GridOccupancyMatrix as necessary
186 // -> Called by mark_area_as
187 // -> Called by record_grid_placement
188 //
189 // 3.3 If the largest column span among all the items without a definite column position is larger than the width of
190 // the implicit grid, add columns to the end of the implicit grid to accommodate that column span.
191 // => Handled by grid size estimate which is used to pre-size the GridOccupancyMatrix
192
193 // 4. Position the remaining grid items
194 // (which either have definite position only in the secondary axis or indefinite positions in both axis)
195 let primary_axis = grid_auto_flow.primary_axis();
196 let secondary_axis = primary_axis.other_axis();
197 let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
198 let primary_axis_grid_end_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_end_line();
199 let secondary_axis_grid_start_line = cell_occupancy_matrix.track_counts(secondary_axis).implicit_start_line();
200 let secondary_axis_grid_end_line = cell_occupancy_matrix.track_counts(secondary_axis).implicit_end_line();
201 let primary_axis_is_reversed = axis_is_reversed(direction, primary_axis);
202 let grid_start_position = (
203 search_start_line(primary_axis_grid_start_line, primary_axis_grid_end_line, primary_axis_is_reversed),
204 search_start_line(
205 secondary_axis_grid_start_line,
206 secondary_axis_grid_end_line,
207 axis_is_reversed(direction, secondary_axis),
208 ),
209 );
210 let mut grid_position = grid_start_position;
211 let mut idx = 0;
212 children_iter()
213 .map(map_child_style_to_origin_zero_placement)
214 .filter(|(_, _, placement, _)| !placement.get(secondary_axis).is_definite())
215 .for_each(|(index, child_node, child_placement, style)| {
216 idx += 1;
217 #[cfg(test)]
218 println!("\nAuto Item {idx}\n==============");
219
220 // Compute placement
221 let (primary_span, secondary_span) = place_indefinitely_positioned_item(
222 &*cell_occupancy_matrix,
223 child_placement,
224 grid_auto_flow,
225 grid_position,
226 direction,
227 explicit_col_count,
228 );
229
230 // Record item
231 record_grid_placement(
232 cell_occupancy_matrix,
233 items,
234 child_node,
235 index,
236 style,
237 align_items,
238 justify_items,
239 primary_axis,
240 primary_span,
241 secondary_span,
242 CellOccupancyState::AutoPlaced,
243 );
244
245 // If using the "dense" placement algorithm then reset the grid position back to grid_start_position ready for the next item
246 // Otherwise set it to the position of the current item so that the next item it placed after it.
247 grid_position = match (grid_auto_flow.is_dense(), primary_axis_is_reversed) {
248 (true, _) => grid_start_position,
249 (false, false) => (primary_span.end, secondary_span.start),
250 (false, true) => (primary_span.start, secondary_span.start),
251 };
252 });
253}
254
255/// 8.5. Grid Item Placement Algorithm
256/// Place a single definitely placed item into the grid
257fn place_definite_grid_item(
258 placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
259 primary_axis: AbsoluteAxis,
260 direction: Direction,
261 explicit_col_count: u16,
262) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
263 // Resolve spans to tracks
264 let primary_span = maybe_mirror_span(
265 placement.get(primary_axis).resolve_definite_grid_lines(),
266 primary_axis,
267 direction,
268 explicit_col_count,
269 );
270 let secondary_span = maybe_mirror_span(
271 placement.get(primary_axis.other_axis()).resolve_definite_grid_lines(),
272 primary_axis.other_axis(),
273 direction,
274 explicit_col_count,
275 );
276
277 (primary_span, secondary_span)
278}
279
280/// 8.5. Grid Item Placement Algorithm
281/// Step 2. Place remaining children with definite secondary axis positions
282fn place_definite_secondary_axis_item(
283 cell_occupancy_matrix: &CellOccupancyMatrix,
284 placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
285 auto_flow: GridAutoFlow,
286 direction: Direction,
287 explicit_col_count: u16,
288) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
289 let primary_axis = auto_flow.primary_axis();
290 let secondary_axis = primary_axis.other_axis();
291 let primary_axis_is_reversed = axis_is_reversed(direction, primary_axis);
292 let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
293 let primary_axis_grid_end_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_end_line();
294
295 let secondary_axis_placement = maybe_mirror_span(
296 placement.get(secondary_axis).resolve_definite_grid_lines(),
297 secondary_axis,
298 direction,
299 explicit_col_count,
300 );
301 let starting_position = match auto_flow.is_dense() {
302 true => search_start_line(primary_axis_grid_start_line, primary_axis_grid_end_line, primary_axis_is_reversed),
303 false => {
304 let lookup_result = if primary_axis_is_reversed {
305 cell_occupancy_matrix.first_of_type(
306 primary_axis,
307 secondary_axis_placement.start,
308 CellOccupancyState::AutoPlaced,
309 )
310 } else {
311 cell_occupancy_matrix.last_of_type(
312 primary_axis,
313 secondary_axis_placement.start,
314 CellOccupancyState::AutoPlaced,
315 )
316 };
317 lookup_result.unwrap_or(search_start_line(
318 primary_axis_grid_start_line,
319 primary_axis_grid_end_line,
320 primary_axis_is_reversed,
321 ))
322 }
323 };
324 let primary_axis_span = placement.get(primary_axis).indefinite_span();
325
326 let mut position: OriginZeroLine = starting_position;
327 loop {
328 let primary_axis_placement =
329 resolve_indefinite_grid_span(position, primary_axis_span, primary_axis_is_reversed);
330
331 let does_fit = cell_occupancy_matrix.line_area_is_unoccupied(
332 primary_axis,
333 primary_axis_placement,
334 secondary_axis_placement,
335 );
336
337 if does_fit {
338 return (primary_axis_placement, secondary_axis_placement);
339 } else {
340 position = advance_position(position, primary_axis_is_reversed);
341 }
342 }
343}
344
345/// 8.5. Grid Item Placement Algorithm
346/// Step 4. Position the remaining grid items.
347fn place_indefinitely_positioned_item(
348 cell_occupancy_matrix: &CellOccupancyMatrix,
349 placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
350 auto_flow: GridAutoFlow,
351 grid_position: (OriginZeroLine, OriginZeroLine),
352 direction: Direction,
353 explicit_col_count: u16,
354) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
355 let primary_axis = auto_flow.primary_axis();
356 let secondary_axis = primary_axis.other_axis();
357 let primary_axis_is_reversed = axis_is_reversed(direction, primary_axis);
358 let secondary_axis_is_reversed = axis_is_reversed(direction, secondary_axis);
359
360 let primary_placement_style = placement.get(primary_axis);
361 let secondary_placement_style = placement.get(secondary_axis);
362
363 let secondary_span = secondary_placement_style.indefinite_span();
364 let has_definite_primary_axis_position = primary_placement_style.is_definite();
365 let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
366 let primary_axis_grid_end_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_end_line();
367 let secondary_axis_grid_start_line = cell_occupancy_matrix.track_counts(secondary_axis).implicit_start_line();
368 let secondary_axis_grid_end_line = cell_occupancy_matrix.track_counts(secondary_axis).implicit_end_line();
369 let primary_start_position =
370 search_start_line(primary_axis_grid_start_line, primary_axis_grid_end_line, primary_axis_is_reversed);
371 let secondary_start_position =
372 search_start_line(secondary_axis_grid_start_line, secondary_axis_grid_end_line, secondary_axis_is_reversed);
373
374 let line_area_is_occupied = |primary_span, secondary_span| {
375 !cell_occupancy_matrix.line_area_is_unoccupied(primary_axis, primary_span, secondary_span)
376 };
377
378 let (mut primary_idx, mut secondary_idx) = grid_position;
379
380 if has_definite_primary_axis_position {
381 let primary_span = maybe_mirror_span(
382 primary_placement_style.resolve_definite_grid_lines(),
383 primary_axis,
384 direction,
385 explicit_col_count,
386 );
387
388 // Compute secondary axis starting position for search
389 secondary_idx = match auto_flow.is_dense() {
390 // If auto-flow is dense then we always search from the first track
391 true => secondary_start_position,
392 false => {
393 let should_advance_secondary = if primary_axis_is_reversed {
394 primary_span.start > primary_idx
395 } else {
396 primary_span.start < primary_idx
397 };
398 if should_advance_secondary {
399 advance_position(secondary_idx, secondary_axis_is_reversed)
400 } else {
401 secondary_idx
402 }
403 }
404 };
405
406 // Item has fixed primary axis position: so we simply increment the secondary axis position
407 // until we find a space that the item fits in
408 loop {
409 let secondary_span =
410 resolve_indefinite_grid_span(secondary_idx, secondary_span, secondary_axis_is_reversed);
411
412 // If area is occupied, increment the index and try again
413 if line_area_is_occupied(primary_span, secondary_span) {
414 secondary_idx = advance_position(secondary_idx, secondary_axis_is_reversed);
415 continue;
416 }
417
418 // Once we find a free space, return that position
419 return (primary_span, secondary_span);
420 }
421 } else {
422 let primary_span = primary_placement_style.indefinite_span();
423
424 // Item does not have any fixed axis, so we search along the primary axis until we hit the end of the already
425 // existent tracks, and then we reset the primary axis back to zero and increment the secondary axis index.
426 // We continue in this vein until we find a space that the item fits in.
427 loop {
428 let primary_span = resolve_indefinite_grid_span(primary_idx, primary_span, primary_axis_is_reversed);
429 let secondary_span =
430 resolve_indefinite_grid_span(secondary_idx, secondary_span, secondary_axis_is_reversed);
431
432 // If the primary index is out of bounds, then increment the secondary index and reset the primary
433 // index back to the start of the grid
434 let primary_out_of_bounds = if primary_axis_is_reversed {
435 primary_span.start < primary_axis_grid_start_line
436 } else {
437 primary_span.end > primary_axis_grid_end_line
438 };
439 if primary_out_of_bounds {
440 secondary_idx = advance_position(secondary_idx, secondary_axis_is_reversed);
441 primary_idx = primary_start_position;
442 continue;
443 }
444
445 // If area is occupied, increment the primary index and try again
446 if line_area_is_occupied(primary_span, secondary_span) {
447 primary_idx = advance_position(primary_idx, primary_axis_is_reversed);
448 continue;
449 }
450
451 // Once we find a free space that's in bounds, return that position
452 return (primary_span, secondary_span);
453 }
454 }
455}
456
457/// Clamp a placement into the limited grid `[-MAX_GRID_TRACKS, MAX_GRID_TRACKS]`,
458/// preserving a span of at least 1 track. Items placed outside of the limited grid are clamped into it.
459///
460/// See: <https://www.w3.org/TR/css-grid-1/#overlarge-grids>
461fn clamp_span_to_limited_grid(span: Line<OriginZeroLine>) -> Line<OriginZeroLine> {
462 let start = span.start.0.clamp(MIN_OZ_LINE, MAX_OZ_LINE - 1);
463 let end = span.end.0.clamp(start + 1, MAX_OZ_LINE);
464 Line { start: OriginZeroLine(start), end: OriginZeroLine(end) }
465}
466
467/// Record the grid item in both CellOccupancyMatric and the GridItems list
468/// once a definite placement has been determined
469#[allow(clippy::too_many_arguments)]
470fn record_grid_placement<S: GridItemStyle>(
471 cell_occupancy_matrix: &mut CellOccupancyMatrix,
472 items: &mut Vec<GridItem>,
473 node: NodeId,
474 index: usize,
475 style: S,
476 parent_align_items: AlignItems,
477 parent_justify_items: AlignItems,
478 primary_axis: AbsoluteAxis,
479 primary_span: Line<OriginZeroLine>,
480 secondary_span: Line<OriginZeroLine>,
481 placement_type: CellOccupancyState,
482) {
483 #[cfg(test)]
484 println!("BEFORE placement:");
485 #[cfg(test)]
486 println!("{cell_occupancy_matrix:?}");
487
488 // Clamp placements into the limited grid to prevent arithmetic overflow when growing the
489 // implicit grid (https://www.w3.org/TR/css-grid-1/#overlarge-grids)
490 let primary_span = clamp_span_to_limited_grid(primary_span);
491 let secondary_span = clamp_span_to_limited_grid(secondary_span);
492
493 // Mark area of grid as occupied
494 cell_occupancy_matrix.mark_area_as(primary_axis, primary_span, secondary_span, placement_type);
495
496 // Create grid item
497 let (col_span, row_span) = match primary_axis {
498 AbsoluteAxis::Horizontal => (primary_span, secondary_span),
499 AbsoluteAxis::Vertical => (secondary_span, primary_span),
500 };
501 items.push(GridItem::new_with_placement_style_and_order(
502 node,
503 col_span,
504 row_span,
505 style,
506 parent_align_items,
507 parent_justify_items,
508 index as u16,
509 ));
510
511 #[cfg(test)]
512 println!("AFTER placement:");
513 #[cfg(test)]
514 println!("{cell_occupancy_matrix:?}");
515 #[cfg(test)]
516 println!("\n");
517}
518
519#[cfg(test)]
520mod tests {
521
522 mod test_placement_algorithm {
523 use crate::compute::grid::implicit_grid::compute_grid_size_estimate;
524 use crate::compute::grid::types::TrackCounts;
525 use crate::compute::grid::util::*;
526 use crate::compute::grid::CellOccupancyMatrix;
527 use crate::compute::grid::NamedLineResolver;
528 use crate::prelude::*;
529 use crate::style::GridAutoFlow;
530 use crate::Direction;
531
532 use super::super::place_grid_items;
533
534 type ExpectedPlacement = (i16, i16, i16, i16);
535
536 fn placement_test_runner(
537 explicit_col_count: u16,
538 explicit_row_count: u16,
539 children: Vec<(usize, Style, ExpectedPlacement)>,
540 expected_col_counts: TrackCounts,
541 expected_row_counts: TrackCounts,
542 flow: GridAutoFlow,
543 ) {
544 // Setup test
545 let children_iter = || children.iter().map(|(index, style, _)| (*index, NodeId::from(*index), style));
546 let child_styles_iter = children.iter().map(|(_, style, _)| style);
547 let estimated_sizes =
548 compute_grid_size_estimate(explicit_col_count, explicit_row_count, Direction::Ltr, child_styles_iter);
549 let mut items = Vec::new();
550 let mut cell_occupancy_matrix =
551 CellOccupancyMatrix::with_track_counts(estimated_sizes.0, estimated_sizes.1);
552 let mut name_resolver = NamedLineResolver::new(&Style::DEFAULT, 0, 0);
553 name_resolver.set_explicit_column_count(explicit_col_count);
554 name_resolver.set_explicit_row_count(explicit_row_count);
555
556 // Run placement algorithm
557 place_grid_items(
558 &mut cell_occupancy_matrix,
559 &mut items,
560 children_iter,
561 Direction::Ltr,
562 flow,
563 AlignSelf::START,
564 AlignSelf::START,
565 // TODO: actually test named line resolution
566 &name_resolver,
567 );
568
569 // Assert that each item has been placed in the right location
570 let mut sorted_children = children.clone();
571 sorted_children.sort_by_key(|child| child.0);
572 for (idx, ((id, _style, expected_placement), item)) in sorted_children.iter().zip(items.iter()).enumerate()
573 {
574 assert_eq!(item.node, NodeId::from(*id));
575 let actual_placement = (item.column.start, item.column.end, item.row.start, item.row.end);
576 assert_eq!(actual_placement, (*expected_placement).into_oz(), "Item {idx} (0-indexed)");
577 }
578
579 // Assert that the correct number of implicit rows have been generated
580 let actual_row_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Vertical);
581 assert_eq!(actual_row_counts, expected_row_counts, "row track counts");
582 let actual_col_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Horizontal);
583 assert_eq!(actual_col_counts, expected_col_counts, "column track counts");
584 }
585
586 #[test]
587 fn test_only_fixed_placement() {
588 let flow = GridAutoFlow::Row;
589 let explicit_col_count = 2;
590 let explicit_row_count = 2;
591 let children = {
592 vec![
593 // node, style (grid coords), expected_placement (oz coords)
594 (1, (line(1), auto(), line(1), auto()).into_grid_child(), (0, 1, 0, 1)),
595 (2, (line(-4), auto(), line(-3), auto()).into_grid_child(), (-1, 0, 0, 1)),
596 (3, (line(-3), auto(), line(-4), auto()).into_grid_child(), (0, 1, -1, 0)),
597 (4, (line(3), span(2), line(5), auto()).into_grid_child(), (2, 4, 4, 5)),
598 ]
599 };
600 let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
601 let expected_rows = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 3 };
602 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
603 }
604
605 #[test]
606 fn test_placement_spanning_origin() {
607 let flow = GridAutoFlow::Row;
608 let explicit_col_count = 2;
609 let explicit_row_count = 2;
610 let children = {
611 vec![
612 // node, style (grid coords), expected_placement (oz coords)
613 (1, (line(-1), line(-1), line(-1), line(-1)).into_grid_child(), (2, 3, 2, 3)),
614 (2, (line(-1), span(2), line(-1), span(2)).into_grid_child(), (2, 4, 2, 4)),
615 (3, (line(-4), line(-4), line(-4), line(-4)).into_grid_child(), (-1, 0, -1, 0)),
616 (4, (line(-4), span(2), line(-4), span(2)).into_grid_child(), (-1, 1, -1, 1)),
617 ]
618 };
619 let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
620 let expected_rows = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
621 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
622 }
623
624 #[test]
625 fn test_only_auto_placement_row_flow() {
626 let flow = GridAutoFlow::Row;
627 let explicit_col_count = 2;
628 let explicit_row_count = 2;
629 let children = {
630 let auto_child = (auto(), auto(), auto(), auto()).into_grid_child();
631 vec![
632 // output order, node, style (grid coords), expected_placement (oz coords)
633 (1, auto_child.clone(), (0, 1, 0, 1)),
634 (2, auto_child.clone(), (1, 2, 0, 1)),
635 (3, auto_child.clone(), (0, 1, 1, 2)),
636 (4, auto_child.clone(), (1, 2, 1, 2)),
637 (5, auto_child.clone(), (0, 1, 2, 3)),
638 (6, auto_child.clone(), (1, 2, 2, 3)),
639 (7, auto_child.clone(), (0, 1, 3, 4)),
640 (8, auto_child.clone(), (1, 2, 3, 4)),
641 ]
642 };
643 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
644 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
645 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
646 }
647
648 #[test]
649 fn test_only_auto_placement_column_flow() {
650 let flow = GridAutoFlow::Column;
651 let explicit_col_count = 2;
652 let explicit_row_count = 2;
653 let children = {
654 let auto_child = (auto(), auto(), auto(), auto()).into_grid_child();
655 vec![
656 // output order, node, style (grid coords), expected_placement (oz coords)
657 (1, auto_child.clone(), (0, 1, 0, 1)),
658 (2, auto_child.clone(), (0, 1, 1, 2)),
659 (3, auto_child.clone(), (1, 2, 0, 1)),
660 (4, auto_child.clone(), (1, 2, 1, 2)),
661 (5, auto_child.clone(), (2, 3, 0, 1)),
662 (6, auto_child.clone(), (2, 3, 1, 2)),
663 (7, auto_child.clone(), (3, 4, 0, 1)),
664 (8, auto_child.clone(), (3, 4, 1, 2)),
665 ]
666 };
667 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
668 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
669 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
670 }
671
672 #[test]
673 fn test_oversized_item() {
674 let flow = GridAutoFlow::Row;
675 let explicit_col_count = 2;
676 let explicit_row_count = 2;
677 let children = {
678 vec![
679 // output order, node, style (grid coords), expected_placement (oz coords)
680 (1, (span(5), auto(), auto(), auto()).into_grid_child(), (0, 5, 0, 1)),
681 ]
682 };
683 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 3 };
684 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
685 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
686 }
687
688 #[test]
689 fn test_fixed_in_secondary_axis() {
690 let flow = GridAutoFlow::Row;
691 let explicit_col_count = 2;
692 let explicit_row_count = 2;
693 let children = {
694 vec![
695 // output order, node, style (grid coords), expected_placement (oz coords)
696 (1, (span(2), auto(), line(1), auto()).into_grid_child(), (0, 2, 0, 1)),
697 (2, (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
698 (3, (auto(), auto(), line(1), auto()).into_grid_child(), (2, 3, 0, 1)),
699 (4, (auto(), auto(), line(4), auto()).into_grid_child(), (0, 1, 3, 4)),
700 ]
701 };
702 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 1 };
703 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
704 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
705 }
706
707 #[test]
708 fn test_definite_in_secondary_axis_with_fully_definite_negative() {
709 let flow = GridAutoFlow::Row;
710 let explicit_col_count = 2;
711 let explicit_row_count = 2;
712 let children = {
713 vec![
714 // output order, node, style (grid coords), expected_placement (oz coords)
715 (2, (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
716 (1, (line(-4), auto(), line(2), auto()).into_grid_child(), (-1, 0, 1, 2)),
717 (3, (auto(), auto(), line(1), auto()).into_grid_child(), (-1, 0, 0, 1)),
718 ]
719 };
720 let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 0 };
721 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
722 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
723 }
724
725 #[test]
726 fn test_dense_packing_algorithm() {
727 let flow = GridAutoFlow::RowDense;
728 let explicit_col_count = 4;
729 let explicit_row_count = 4;
730 let children = {
731 vec![
732 // output order, node, style (grid coords), expected_placement (oz coords)
733 (1, (line(2), auto(), line(1), auto()).into_grid_child(), (1, 2, 0, 1)), // Definitely positioned in column 2
734 (2, (span(2), auto(), auto(), auto()).into_grid_child(), (2, 4, 0, 1)), // Spans 2 columns, so positioned after item 1
735 (3, (auto(), auto(), auto(), auto()).into_grid_child(), (0, 1, 0, 1)), // Spans 1 column, so should be positioned before item 1
736 ]
737 };
738 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
739 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
740 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
741 }
742
743 #[test]
744 fn test_sparse_packing_algorithm() {
745 let flow = GridAutoFlow::Row;
746 let explicit_col_count = 4;
747 let explicit_row_count = 4;
748 let children = {
749 vec![
750 // output order, node, style (grid coords), expected_placement (oz coords)
751 (1, (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 0, 1)), // Width 3
752 (2, (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 1, 2)), // Width 3 (wraps to next row)
753 (3, (auto(), span(1), auto(), auto()).into_grid_child(), (3, 4, 1, 2)), // Width 1 (uses second row as we're already on it)
754 ]
755 };
756 let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
757 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
758 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
759 }
760
761 #[test]
762 fn test_auto_placement_in_negative_tracks() {
763 let flow = GridAutoFlow::RowDense;
764 let explicit_col_count = 2;
765 let explicit_row_count = 2;
766 let children = {
767 vec![
768 // output order, node, style (grid coords), expected_placement (oz coords)
769 (1, (line(-5), auto(), line(1), auto()).into_grid_child(), (-2, -1, 0, 1)), // Row 1. Definitely positioned in column -2
770 (2, (auto(), auto(), line(2), auto()).into_grid_child(), (-2, -1, 1, 2)), // Row 2. Auto positioned in column -2
771 (3, (auto(), auto(), auto(), auto()).into_grid_child(), (-1, 0, 0, 1)), // Row 1. Auto positioned in column -1
772 ]
773 };
774 let expected_cols = TrackCounts { negative_implicit: 2, explicit: 2, positive_implicit: 0 };
775 let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
776 placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
777 }
778 }
779}