taffy/compute/grid/
explicit_grid.rs

1//! Helper functions for intialising GridTrack's from styles
2//! This mainly consists of evaluating GridAutoTracks
3use super::types::{GridTrack, TrackCounts};
4use crate::geometry::{AbsoluteAxis, Size};
5use crate::style::{GridTrackRepetition, LengthPercentage, NonRepeatedTrackSizingFunction, Style, TrackSizingFunction};
6use crate::style_helpers::TaffyAuto;
7use crate::util::sys::{GridTrackVec, Vec};
8use crate::util::MaybeMath;
9use crate::util::ResolveOrZero;
10
11#[cfg(not(feature = "std"))]
12use num_traits::float::FloatCore;
13
14/// Compute the number of rows and columns in the explicit grid
15pub(crate) fn compute_explicit_grid_size_in_axis(
16    style: &Style,
17    preferred_size: Size<Option<f32>>,
18    axis: AbsoluteAxis,
19) -> u16 {
20    // Load the grid-template-rows or grid-template-columns definition (depending on the axis)
21    let template = style.grid_template_tracks(axis);
22
23    // If template contains no tracks, then there are trivially zero explcit tracks
24    if template.is_empty() {
25        return 0;
26    }
27
28    // If there are any repetitions that contains no tracks, then the whole definition should be considered invalid
29    // and we default to no explicit tracks
30    let template_has_repetitions_with_zero_tracks = template.iter().any(|track_def| match track_def {
31        TrackSizingFunction::Single(_) => false,
32        TrackSizingFunction::Repeat(_, tracks) => tracks.is_empty(),
33    });
34    if template_has_repetitions_with_zero_tracks {
35        return 0;
36    }
37
38    // Compute that number of track generated by single track definition and repetitions with a fixed repetition count
39    let non_auto_repeating_track_count = template
40        .iter()
41        .map(|track_def| {
42            use GridTrackRepetition::{AutoFill, AutoFit, Count};
43            match track_def {
44                TrackSizingFunction::Single(_) => 1,
45                TrackSizingFunction::Repeat(Count(count), tracks) => count * tracks.len() as u16,
46                TrackSizingFunction::Repeat(AutoFit | AutoFill, _) => 0,
47            }
48        })
49        .sum::<u16>();
50
51    let auto_repetition_count = template.iter().filter(|track_def| track_def.is_auto_repetition()).count() as u16;
52    let all_track_defs_have_fixed_component = template.iter().all(|track_def| match track_def {
53        TrackSizingFunction::Single(sizing_function) => sizing_function.has_fixed_component(),
54        TrackSizingFunction::Repeat(_, tracks) => {
55            tracks.iter().all(|sizing_function| sizing_function.has_fixed_component())
56        }
57    });
58
59    let template_is_valid =
60        auto_repetition_count == 0 || (auto_repetition_count == 1 && all_track_defs_have_fixed_component);
61
62    // If the template is invalid because it contains multiple auto-repetition definitions or it combines an auto-repetition
63    // definition with non-fixed-size track sizing functions, then disregard it entirely and default to zero explicit tracks
64    if !template_is_valid {
65        return 0;
66    }
67
68    // If there are no repetitions, then the number of explicit tracks is simply equal to the lengths of the track definition
69    // vector (as each item in the Vec represents one track).
70    if auto_repetition_count == 0 {
71        return non_auto_repeating_track_count;
72    }
73
74    let repetition_definition = template
75        .iter()
76        .find_map(|def| {
77            use GridTrackRepetition::{AutoFill, AutoFit, Count};
78            match def {
79                TrackSizingFunction::Single(_) => None,
80                TrackSizingFunction::Repeat(Count(_), _) => None,
81                TrackSizingFunction::Repeat(AutoFit | AutoFill, tracks) => Some(tracks),
82            }
83        })
84        .unwrap();
85    let repetition_track_count = repetition_definition.len() as u16;
86
87    // Otherwise, run logic to resolve the auto-repeated track count:
88    //
89    // If the grid container has a definite size or max size in the relevant axis:
90    //   - then the number of repetitions is the largest possible positive integer that does not cause the grid to overflow the content
91    //     box of its grid container.
92    // Otherwise, if the grid container has a definite min size in the relevant axis:
93    //   - then the number of repetitions is the smallest possible positive integer that fulfills that minimum requirement
94    // Otherwise, the specified track list repeats only once.
95    let style_size = preferred_size.get_abs(axis);
96    let style_min_size = style.min_size.get_abs(axis).into_option();
97    let style_max_size = style.max_size.get_abs(axis).into_option();
98
99    let outer_container_size = style_size.maybe_min(style_max_size).or(style_max_size).or(style_min_size);
100    let inner_container_size = outer_container_size.map(|size| {
101        let padding_sum = style.padding.resolve_or_zero(outer_container_size).grid_axis_sum(axis);
102        let border_sum = style.border.resolve_or_zero(outer_container_size).grid_axis_sum(axis);
103        size - padding_sum - border_sum
104    });
105    let size_is_maximum = style_size.is_some() || style_max_size.is_some();
106
107    // Determine the number of repetitions
108    let num_repetitions: u16 = match inner_container_size {
109        None => 1,
110        Some(inner_container_size) => {
111            let parent_size = Some(inner_container_size);
112
113            /// ...treating each track as its max track sizing function if that is definite or as its minimum track sizing function
114            /// otherwise, flooring the max track sizing function by the min track sizing function if both are definite
115            fn track_definite_value(sizing_function: &NonRepeatedTrackSizingFunction, parent_size: Option<f32>) -> f32 {
116                let max_size = sizing_function.max.definite_value(parent_size);
117                let min_size = sizing_function.max.definite_value(parent_size);
118                max_size.map(|max| max.maybe_min(min_size)).or(min_size).unwrap()
119            }
120
121            let non_repeating_track_used_space: f32 = template
122                .iter()
123                .map(|track_def| {
124                    use GridTrackRepetition::{AutoFill, AutoFit, Count};
125                    match track_def {
126                        TrackSizingFunction::Single(sizing_function) => {
127                            track_definite_value(sizing_function, parent_size)
128                        }
129                        TrackSizingFunction::Repeat(Count(count), repeated_tracks) => {
130                            let sum = repeated_tracks
131                                .iter()
132                                .map(|sizing_function| track_definite_value(sizing_function, parent_size))
133                                .sum::<f32>();
134                            sum * (*count as f32)
135                        }
136                        TrackSizingFunction::Repeat(AutoFit | AutoFill, _) => 0.0,
137                    }
138                })
139                .sum();
140            let gap_size = style.gap.get_abs(axis).resolve_or_zero(Some(inner_container_size));
141
142            // Compute the amount of space that a single repetition of the repeated track list takes
143            let per_repetition_track_used_space: f32 = repetition_definition
144                .iter()
145                .map(|sizing_function| track_definite_value(sizing_function, parent_size))
146                .sum::<f32>();
147
148            // We special case the first repetition here because the number of gaps in the first repetition
149            // depends on the number of non-repeating tracks in the template
150            let first_repetition_and_non_repeating_tracks_used_space = non_repeating_track_used_space
151                + per_repetition_track_used_space
152                + ((non_auto_repeating_track_count + repetition_track_count).saturating_sub(1) as f32 * gap_size);
153
154            // If a single repetition already overflows the container then we return 1 as the repetition count
155            // (the number of repetitions is floored at 1)
156            if first_repetition_and_non_repeating_tracks_used_space > inner_container_size {
157                1u16
158            } else {
159                let per_repetition_gap_used_space = (repetition_definition.len() as f32) * gap_size;
160                let per_repetition_used_space = per_repetition_track_used_space + per_repetition_gap_used_space;
161                let num_repetition_that_fit = (inner_container_size
162                    - first_repetition_and_non_repeating_tracks_used_space)
163                    / per_repetition_used_space;
164
165                // If the container size is a preferred or maximum size:
166                //   Then we return the maximum number of repetitions that fit into the container without overflowing.
167                // If the container size is a minimum size:
168                //   - Then we return the minimum number of repititions required to overflow the size.
169                //
170                // In all cases we add the additional repetition that was already accounted for in the special-case computation above
171                if size_is_maximum {
172                    (num_repetition_that_fit.floor() as u16) + 1
173                } else {
174                    (num_repetition_that_fit.ceil() as u16) + 1
175                }
176            }
177        }
178    };
179
180    non_auto_repeating_track_count + (repetition_track_count * num_repetitions)
181}
182
183/// Resolve the track sizing functions of explicit tracks, automatically created tracks, and gutters
184/// given a set of track counts and all of the relevant styles
185pub(super) fn initialize_grid_tracks(
186    tracks: &mut Vec<GridTrack>,
187    counts: TrackCounts,
188    track_template: &GridTrackVec<TrackSizingFunction>,
189    auto_tracks: &Vec<NonRepeatedTrackSizingFunction>,
190    gap: LengthPercentage,
191    track_has_items: impl Fn(usize) -> bool,
192) {
193    // Clear vector (in case this is a re-layout), reserve space for all tracks ahead of time to reduce allocations,
194    // and push the initial gutter
195    tracks.clear();
196    tracks.reserve((counts.len() * 2) + 1);
197    tracks.push(GridTrack::gutter(gap));
198
199    // Create negative implicit tracks
200    if counts.negative_implicit > 0 {
201        if auto_tracks.is_empty() {
202            let iter = core::iter::repeat(NonRepeatedTrackSizingFunction::AUTO);
203            create_implicit_tracks(tracks, counts.negative_implicit, iter, gap)
204        } else {
205            let offset = auto_tracks.len() - (counts.negative_implicit as usize % auto_tracks.len());
206            let iter = auto_tracks.iter().copied().cycle().skip(offset);
207            create_implicit_tracks(tracks, counts.negative_implicit, iter, gap)
208        }
209    }
210
211    let mut current_track_index = (counts.negative_implicit) as usize;
212
213    // Create explicit tracks
214    // An explicit check against the count (rather than just relying on track_template being empty) is required here
215    // because a count of zero can result from the track_template being invalid, in which case it should be ignored.
216    if counts.explicit > 0 {
217        track_template.iter().for_each(|track_sizing_function| {
218            use GridTrackRepetition::{AutoFill, AutoFit, Count};
219            match track_sizing_function {
220                TrackSizingFunction::Single(sizing_function) => {
221                    tracks.push(GridTrack::new(
222                        sizing_function.min_sizing_function(),
223                        sizing_function.max_sizing_function(),
224                    ));
225                    tracks.push(GridTrack::gutter(gap));
226                    current_track_index += 1;
227                }
228                TrackSizingFunction::Repeat(Count(count), repeated_tracks) => {
229                    let track_iter = repeated_tracks.iter().cycle().take(repeated_tracks.len() * *count as usize);
230                    track_iter.for_each(|sizing_function| {
231                        tracks.push(GridTrack::new(
232                            sizing_function.min_sizing_function(),
233                            sizing_function.max_sizing_function(),
234                        ));
235                        tracks.push(GridTrack::gutter(gap));
236                        current_track_index += 1;
237                    });
238                }
239                TrackSizingFunction::Repeat(repetition_kind @ (AutoFit | AutoFill), repeated_tracks) => {
240                    let auto_repeated_track_count = (counts.explicit - (track_template.len() as u16 - 1)) as usize;
241                    let iter = repeated_tracks.iter().copied().cycle();
242                    for track_def in iter.take(auto_repeated_track_count) {
243                        let mut track =
244                            GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function());
245                        let mut gutter = GridTrack::gutter(gap);
246
247                        // Auto-fit tracks that don't contain should be collapsed.
248                        if *repetition_kind == AutoFit && !track_has_items(current_track_index) {
249                            track.collapse();
250                            gutter.collapse();
251                        }
252
253                        tracks.push(track);
254                        tracks.push(gutter);
255
256                        current_track_index += 1;
257                    }
258                }
259            }
260        });
261    }
262
263    // Create positive implicit tracks
264    if auto_tracks.is_empty() {
265        let iter = core::iter::repeat(NonRepeatedTrackSizingFunction::AUTO);
266        create_implicit_tracks(tracks, counts.positive_implicit, iter, gap)
267    } else {
268        let iter = auto_tracks.iter().copied().cycle();
269        create_implicit_tracks(tracks, counts.positive_implicit, iter, gap)
270    }
271
272    // Mark first and last grid lines as collapsed
273    tracks.first_mut().unwrap().collapse();
274    tracks.last_mut().unwrap().collapse();
275}
276
277/// Utility function for repeating logic of creating implicit tracks
278fn create_implicit_tracks(
279    tracks: &mut Vec<GridTrack>,
280    count: u16,
281    mut auto_tracks_iter: impl Iterator<Item = NonRepeatedTrackSizingFunction>,
282    gap: LengthPercentage,
283) {
284    for _ in 0..count {
285        let track_def = auto_tracks_iter.next().unwrap();
286        tracks.push(GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function()));
287        tracks.push(GridTrack::gutter(gap));
288    }
289}
290
291#[cfg(test)]
292mod test {
293    use super::compute_explicit_grid_size_in_axis;
294    use super::initialize_grid_tracks;
295    use crate::compute::grid::types::GridTrackKind;
296    use crate::compute::grid::types::TrackCounts;
297    use crate::compute::grid::util::*;
298    use crate::geometry::AbsoluteAxis;
299    use crate::prelude::*;
300
301    #[test]
302    fn explicit_grid_sizing_no_repeats() {
303        let grid_style = (600.0, 600.0, 2, 4).into_grid();
304        let preferred_size = grid_style.size.map(|s| s.into_option());
305        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
306        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
307        assert_eq!(width, 2);
308        assert_eq!(height, 4);
309    }
310
311    #[test]
312    fn explicit_grid_sizing_auto_fill_exact_fit() {
313        use GridTrackRepetition::AutoFill;
314        let grid_style = Style {
315            display: Display::Grid,
316            size: Size { width: length(120.0), height: length(80.0) },
317            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
318            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
319            ..Default::default()
320        };
321        let preferred_size = grid_style.size.map(|s| s.into_option());
322        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
323        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
324        assert_eq!(width, 3);
325        assert_eq!(height, 4);
326    }
327
328    #[test]
329    fn explicit_grid_sizing_auto_fill_non_exact_fit() {
330        use GridTrackRepetition::AutoFill;
331        let grid_style = Style {
332            display: Display::Grid,
333            size: Size { width: length(140.0), height: length(90.0) },
334            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
335            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
336            ..Default::default()
337        };
338        let preferred_size = grid_style.size.map(|s| s.into_option());
339        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
340        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
341        assert_eq!(width, 3);
342        assert_eq!(height, 4);
343    }
344
345    #[test]
346    fn explicit_grid_sizing_auto_fill_min_size_exact_fit() {
347        use GridTrackRepetition::AutoFill;
348        let grid_style = Style {
349            display: Display::Grid,
350            min_size: Size { width: length(120.0), height: length(80.0) },
351            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
352            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
353            ..Default::default()
354        };
355        let preferred_size = grid_style.size.map(|s| s.into_option());
356        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
357        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
358        assert_eq!(width, 3);
359        assert_eq!(height, 4);
360    }
361
362    #[test]
363    fn explicit_grid_sizing_auto_fill_min_size_non_exact_fit() {
364        use GridTrackRepetition::AutoFill;
365        let grid_style = Style {
366            display: Display::Grid,
367            min_size: Size { width: length(140.0), height: length(90.0) },
368            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
369            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
370            ..Default::default()
371        };
372        let preferred_size = grid_style.size.map(|s| s.into_option());
373        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
374        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
375        assert_eq!(width, 4);
376        assert_eq!(height, 5);
377    }
378
379    #[test]
380    fn explicit_grid_sizing_auto_fill_multiple_repeated_tracks() {
381        use GridTrackRepetition::AutoFill;
382        let grid_style = Style {
383            display: Display::Grid,
384            size: Size { width: length(140.0), height: length(100.0) },
385            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0), length(20.0)])],
386            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0), length(10.0)])],
387            ..Default::default()
388        };
389        let preferred_size = grid_style.size.map(|s| s.into_option());
390        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
391        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
392        assert_eq!(width, 4); // 2 repetitions * 2 repeated tracks = 4 tracks in total
393        assert_eq!(height, 6); // 3 repetitions * 2 repeated tracks = 4 tracks in total
394    }
395
396    #[test]
397    fn explicit_grid_sizing_auto_fill_gap() {
398        use GridTrackRepetition::AutoFill;
399        let grid_style = Style {
400            display: Display::Grid,
401            size: Size { width: length(140.0), height: length(100.0) },
402            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
403            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
404            gap: length(20.0),
405            ..Default::default()
406        };
407        let preferred_size = grid_style.size.map(|s| s.into_option());
408        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
409        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
410        assert_eq!(width, 2); // 2 tracks + 1 gap
411        assert_eq!(height, 3); // 3 tracks + 2 gaps
412    }
413
414    #[test]
415    fn explicit_grid_sizing_no_defined_size() {
416        use GridTrackRepetition::AutoFill;
417        let grid_style = Style {
418            display: Display::Grid,
419            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0), percent(0.5), length(20.0)])],
420            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
421            gap: length(20.0),
422            ..Default::default()
423        };
424        let preferred_size = grid_style.size.map(|s| s.into_option());
425        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
426        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
427        assert_eq!(width, 3);
428        assert_eq!(height, 1);
429    }
430
431    #[test]
432    fn explicit_grid_sizing_mix_repeated_and_non_repeated() {
433        use GridTrackRepetition::AutoFill;
434        let grid_style = Style {
435            display: Display::Grid,
436            size: Size { width: length(140.0), height: length(100.0) },
437            grid_template_columns: vec![length(20.0), repeat(AutoFill, vec![length(40.0)])],
438            grid_template_rows: vec![length(40.0), repeat(AutoFill, vec![length(20.0)])],
439            gap: length(20.0),
440            ..Default::default()
441        };
442        let preferred_size = grid_style.size.map(|s| s.into_option());
443        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
444        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
445        assert_eq!(width, 3); // 3 tracks + 2 gaps
446        assert_eq!(height, 2); // 2 tracks + 1 gap
447    }
448
449    #[test]
450    fn explicit_grid_sizing_mix_with_padding() {
451        use GridTrackRepetition::AutoFill;
452        let grid_style = Style {
453            display: Display::Grid,
454            size: Size { width: length(120.0), height: length(120.0) },
455            padding: Rect { left: length(10.0), right: length(10.0), top: length(20.0), bottom: length(20.0) },
456            grid_template_columns: vec![repeat(AutoFill, vec![length(20.0)])],
457            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
458            ..Default::default()
459        };
460        let preferred_size = grid_style.size.map(|s| s.into_option());
461        let width = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Horizontal);
462        let height = compute_explicit_grid_size_in_axis(&grid_style, preferred_size, AbsoluteAxis::Vertical);
463        assert_eq!(width, 5); // 40px horizontal padding
464        assert_eq!(height, 4); // 20px vertical padding
465    }
466
467    #[test]
468    fn test_initialize_grid_tracks() {
469        let px0 = LengthPercentage::Length(0.0);
470        let px20 = LengthPercentage::Length(20.0);
471        let px100 = LengthPercentage::Length(100.0);
472
473        // Setup test
474        let track_template = vec![length(100.0), minmax(length(100.0), fr(2.0)), fr(1.0)];
475        let track_counts =
476            TrackCounts { negative_implicit: 3, explicit: track_template.len() as u16, positive_implicit: 3 };
477        let auto_tracks = vec![auto(), length(100.0)];
478        let gap = px20;
479
480        // Call function
481        let mut tracks = Vec::new();
482        initialize_grid_tracks(&mut tracks, track_counts, &track_template, &auto_tracks, gap, |_| false);
483
484        // Assertions
485        let expected = vec![
486            // Gutter
487            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px0), MaxTrackSizingFunction::Fixed(px0)),
488            // Negative implict tracks
489            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
490            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
491            (GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Auto),
492            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
493            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
494            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
495            // Explicit tracks
496            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
497            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
498            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fraction(2.0)), // Note: separate min-max functions
499            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
500            (GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Fraction(1.0)), // Note: min sizing function of flex sizing functions is auto
501            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
502            // Positive implict tracks
503            (GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Auto),
504            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
505            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
506            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
507            (GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Auto),
508            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px0), MaxTrackSizingFunction::Fixed(px0)),
509        ];
510
511        assert_eq!(tracks.len(), expected.len(), "Number of tracks doesn't match");
512
513        for (idx, (actual, (kind, min, max))) in tracks.into_iter().zip(expected).enumerate() {
514            assert_eq!(actual.kind, kind, "Track {idx} (0-based index)");
515            assert_eq!(actual.min_track_sizing_function, min, "Track {idx} (0-based index)");
516            assert_eq!(actual.max_track_sizing_function, max, "Track {idx} (0-based index)");
517        }
518    }
519}