tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 582HTML 275Markdown 8TOML 41545 Sutichaykuna

⎇ 9915b967e9809dc57f210de8fe5f7510e66806d6 src/compute/grid/explicit_grid.rs Rust · 609 siq'ikuna · 26.9 KB Ñawpa willay Pi imata llamkarqan ch'uya

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