tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81412 Definiciones

⎇ d2b2eb08a93c6088e89b14d630cc962d551b084e src/compute/grid/explicit_grid.rs Rust · 520 líneas · 25.7 KB Historia Quién tocó qué crudo

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