⎇ devin/1785205225-fix-grid-track-limit-overflow
src/compute/grid/explicit_grid.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
CSS Grid auto-repeat and minimum-size handling (#946)
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}