gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Definitions

⎇ a3f94c2748ff655b3ab42444640d8e69e917aaab src/compute/grid/explicit_grid.rs Rust · 764 lines · 35.0 KB History Who touched what raw

Clamp CSS grids at 10000 tracks in each dimension a3f94c274 · Nico Burns ·

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