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