gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Definitions

⎇ 10b1e6f859c7dc232bb8239b42d55d35afd6be18 src/compute/grid/explicit_grid.rs Rust · 611 lines · 27.2 KB History Who touched what raw

Fix calculation of `auto-fill`/`auto-fit` repetition count when container has a definite percentage size (#722) 10b1e6f85 · Nico Burns ·

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