⎇ a3f94c2748ff655b3ab42444640d8e69e917aaab
src/compute/grid/explicit_grid.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
Clamp CSS grids at 10000 tracks in each dimension
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}