tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 8TOML 3CSS 1317 Definitions

⎇ 6ce28e1535f2ab4631d0502f2fee13d4827f2a11 src/compute/grid/explicit_grid.rs Rust · 453 lines · 22.2 KB History Who touched what raw

Optimise CSS Grid (#338) 6ce28e153 · Nico Burns ·

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