gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Definitions

⎇ 2f8ee0713dd50570116065547fca8efc1786bafb src/compute/grid/explicit_grid.rs Rust · 506 lines · 24.7 KB History Who touched what raw

Upgrade `grid` dependency to `0.10` (#473) 2f8ee0713 · Nico Burns ·

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