taffy/compute/grid/
explicit_grid.rs

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