gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81412 Sutichaykuna

Grid: Add support for repeat() with an integer repetition count (#342)

* Add support for repeat() with an integer repetition count

* Add tests for integer repeat

* Fix clippy lints

* Make repeat() helper function accept u16 and 'auto-fit' and 'auto-fill' strings

* Fix typo

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>

ebd376fbebd0ba73c79aa00d1835918680ef82a0 · ruwaq Nico Burns · (3 wata ñawpaq) · tayta 2fa6cc64f · kay commitpi sach'ata qhaway

Semántico sayk'uy · 26

benches/generated/grid_repeat_integer.rs

benches/generated/grid_repeat_mixed.rs

benches/generated/mod.rs

src/compute/grid/explicit_grid.rs

src/style/grid.rs

src/style_helpers.rs

tests/generated/grid_repeat_integer.rs

tests/generated/grid_repeat_mixed.rs

tests/generated/mod.rs

12 qillqakuna · +463 −50

diff --git a/benches/generated/grid_repeat_integer.rs b/benches/generated/grid_repeat_integer.rs
new file mode 100644
index 000000000..30cc71e4f
--- /dev/null
+++ b/benches/generated/grid_repeat_integer.rs
@@ -0,0 +1,30 @@
+pub fn compute() {
+    #[allow(unused_imports)]
+    use taffy::prelude::*;
+    let mut taffy = taffy::Taffy::new();
+    let node0 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node1 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node2 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node3 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node4 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node5 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node6 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node7 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node8 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Grid,
+                grid_template_rows: vec![repeat(GridTrackRepetition::Count(3u16), vec![points(40f32)])],
+                grid_template_columns: vec![repeat(GridTrackRepetition::Count(3u16), vec![points(40f32)])],
+                size: taffy::geometry::Size {
+                    width: taffy::style::Dimension::Points(120f32),
+                    height: taffy::style::Dimension::Points(120f32),
+                },
+                ..Default::default()
+            },
+            &[node0, node1, node2, node3, node4, node5, node6, node7, node8],
+        )
+        .unwrap();
+    taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+}
diff --git a/benches/generated/grid_repeat_mixed.rs b/benches/generated/grid_repeat_mixed.rs
new file mode 100644
index 000000000..dc90474ad
--- /dev/null
+++ b/benches/generated/grid_repeat_mixed.rs
@@ -0,0 +1,38 @@
+pub fn compute() {
+    #[allow(unused_imports)]
+    use taffy::prelude::*;
+    let mut taffy = taffy::Taffy::new();
+    let node0 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node1 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node2 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node3 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node4 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node5 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node6 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node7 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node8 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Grid,
+                grid_template_rows: vec![
+                    points(40f32),
+                    repeat(GridTrackRepetition::Count(1u16), vec![points(40f32)]),
+                    repeat(GridTrackRepetition::AutoFill, vec![points(40f32)]),
+                ],
+                grid_template_columns: vec![
+                    points(40f32),
+                    repeat(GridTrackRepetition::Count(1u16), vec![points(40f32)]),
+                    repeat(GridTrackRepetition::AutoFill, vec![points(40f32)]),
+                ],
+                size: taffy::geometry::Size {
+                    width: taffy::style::Dimension::Points(120f32),
+                    height: taffy::style::Dimension::Points(120f32),
+                },
+                ..Default::default()
+            },
+            &[node0, node1, node2, node3, node4, node5, node6, node7, node8],
+        )
+        .unwrap();
+    taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+}
diff --git a/benches/generated/mod.rs b/benches/generated/mod.rs
index dcd569813..b83f9958a 100644
--- a/benches/generated/mod.rs
+++ b/benches/generated/mod.rs
@@ -577,6 +577,10 @@ mod grid_placement_definite_in_secondary_axis_with_fully_definite_negative;
 #[cfg(feature = "grid")]
 mod grid_relayout_vertical_text;
 #[cfg(feature = "grid")]
+mod grid_repeat_integer;
+#[cfg(feature = "grid")]
+mod grid_repeat_mixed;
+#[cfg(feature = "grid")]
 mod grid_size_child_fixed_tracks;
 mod justify_content_column_center;
 mod justify_content_column_flex_end;
@@ -1268,6 +1272,10 @@ fn benchmark(c: &mut Criterion) {
             #[cfg(feature = "grid")]
             grid_relayout_vertical_text::compute();
             #[cfg(feature = "grid")]
+            grid_repeat_integer::compute();
+            #[cfg(feature = "grid")]
+            grid_repeat_mixed::compute();
+            #[cfg(feature = "grid")]
             grid_size_child_fixed_tracks::compute();
             justify_content_column_center::compute();
             justify_content_column_flex_end::compute();
diff --git a/scripts/gentest/src/main.rs b/scripts/gentest/src/main.rs
index a8512071d..890fad8de 100644
--- a/scripts/gentest/src/main.rs
+++ b/scripts/gentest/src/main.rs
@@ -797,9 +797,7 @@ fn generate_track_definition(track_definition: &serde_json::Map<String, Value>)
 
                         match unit {
                             "auto-fill" => quote!(GridTrackRepetition::AutoFill),
-                            // Not yet implemented in taffy
                             "auto-fit" => quote!(GridTrackRepetition::AutoFit),
-                            // Not yet implemented in taffy
                             "integer" => {
                                 let repetition_count = value();
                                 quote!(GridTrackRepetition::Count(#repetition_count))
diff --git a/src/compute/grid/explicit_grid.rs b/src/compute/grid/explicit_grid.rs
index ef85bb389..1bcdb2f24 100644
--- a/src/compute/grid/explicit_grid.rs
+++ b/src/compute/grid/explicit_grid.rs
@@ -19,11 +19,33 @@ pub(crate) fn compute_explicit_grid_size_in_axis(style: &Style, axis: AbsoluteAx
         return 0;
     }
 
+    // If there are any repetitions that contains no tracks, then the whole definition should be considered invalid
+    // and we default to no explicit tracks
+    let template_has_repetitions_with_zero_tracks = template.iter().any(|track_def| match track_def {
+        TrackSizingFunction::Single(_) => false,
+        TrackSizingFunction::Repeat(_, tracks) => tracks.is_empty(),
+    });
+    if template_has_repetitions_with_zero_tracks {
+        return 0;
+    }
+
+    // Compute that number of track generated by single track definition and repetitions with a fixed repetition count
+    let non_auto_repeating_track_count = template
+        .iter()
+        .map(|track_def| {
+            use GridTrackRepetition::{AutoFill, AutoFit, Count};
+            match track_def {
+                TrackSizingFunction::Single(_) => 1,
+                TrackSizingFunction::Repeat(Count(count), tracks) => count * tracks.len() as u16,
+                TrackSizingFunction::Repeat(AutoFit | AutoFill, _) => 0,
+            }
+        })
+        .sum::<u16>();
+
     let auto_repetition_count = template.iter().filter(|track_def| track_def.is_auto_repetition()).count() as u16;
-    let non_repeating_track_count = (template.len() as u16) - auto_repetition_count;
     let all_track_defs_have_fixed_component = template.iter().all(|track_def| match track_def {
         TrackSizingFunction::Single(sizing_function) => sizing_function.has_fixed_component(),
-        TrackSizingFunction::AutoRepeat(_, tracks) => {
+        TrackSizingFunction::Repeat(_, tracks) => {
             tracks.iter().all(|sizing_function| sizing_function.has_fixed_component())
         }
     });
@@ -40,23 +62,22 @@ pub(crate) fn compute_explicit_grid_size_in_axis(style: &Style, axis: AbsoluteAx
     // If there are no repetitions, then the number of explicit tracks is simply equal to the lengths of the track definition
     // vector (as each item in the Vec represents one track).
     if auto_repetition_count == 0 {
-        return template.len() as u16;
+        return non_auto_repeating_track_count;
     }
 
     let repetition_definition = template
         .iter()
-        .find_map(|def| match def {
-            TrackSizingFunction::Single(_) => None,
-            TrackSizingFunction::AutoRepeat(_, tracks) => Some(tracks),
+        .find_map(|def| {
+            use GridTrackRepetition::{AutoFill, AutoFit, Count};
+            match def {
+                TrackSizingFunction::Single(_) => None,
+                TrackSizingFunction::Repeat(Count(_), _) => None,
+                TrackSizingFunction::Repeat(AutoFit | AutoFill, tracks) => Some(tracks),
+            }
         })
         .unwrap();
     let repetition_track_count = repetition_definition.len() as u16;
 
-    // If the repetition contains no tracks, then the whole definition should be considered invalid and we default to no explcit tracks
-    if repetition_track_count == 0 {
-        return 0u16;
-    }
-
     // Otherwise, run logic to resolve the auto-repeated track count:
     //
     // If the grid container has a definite size or max size in the relevant axis:
@@ -93,9 +114,21 @@ pub(crate) fn compute_explicit_grid_size_in_axis(style: &Style, axis: AbsoluteAx
 
             let non_repeating_track_used_space: f32 = template
                 .iter()
-                .map(|track_def| match track_def {
-                    TrackSizingFunction::Single(sizing_function) => track_definite_value(sizing_function, parent_size),
-                    TrackSizingFunction::AutoRepeat(_, _) => 0.0,
+                .map(|track_def| {
+                    use GridTrackRepetition::{AutoFill, AutoFit, Count};
+                    match track_def {
+                        TrackSizingFunction::Single(sizing_function) => {
+                            track_definite_value(sizing_function, parent_size)
+                        }
+                        TrackSizingFunction::Repeat(Count(count), repeated_tracks) => {
+                            let sum = repeated_tracks
+                                .iter()
+                                .map(|sizing_function| track_definite_value(sizing_function, parent_size))
+                                .sum::<f32>();
+                            sum * (*count as f32)
+                        }
+                        TrackSizingFunction::Repeat(AutoFit | AutoFill, _) => 0.0,
+                    }
                 })
                 .sum();
             let gap_size = style.gap.get_abs(axis).resolve_or_zero(Some(inner_container_size));
@@ -110,7 +143,7 @@ pub(crate) fn compute_explicit_grid_size_in_axis(style: &Style, axis: AbsoluteAx
             // depends on the number of non-repeating tracks in the template
             let first_repetition_and_non_repeating_tracks_used_space = non_repeating_track_used_space
                 + per_repetition_track_used_space
-                + ((non_repeating_track_count + repetition_track_count).saturating_sub(1) as f32 * gap_size);
+                + ((non_auto_repeating_track_count + repetition_track_count).saturating_sub(1) as f32 * gap_size);
 
             // If a single repetition already overflows the container then we return 1 as the repetition count
             // (the number of repetitions is floored at 1)
@@ -138,7 +171,7 @@ pub(crate) fn compute_explicit_grid_size_in_axis(style: &Style, axis: AbsoluteAx
         }
     };
 
-    non_repeating_track_count + (repetition_track_count * num_repetitions)
+    non_auto_repeating_track_count + (repetition_track_count * num_repetitions)
 }
 
 /// Resolve the track sizing functions of explicit tracks, automatically created tracks, and gutters
@@ -175,31 +208,48 @@ pub(super) fn initialize_grid_tracks(
     // An explicit check against the count (rather than just relying on track_template being empty) is required here
     // because a count of zero can result from the track_template being invalid, in which case it should be ignored.
     if counts.explicit > 0 {
-        track_template.iter().for_each(|track_sizing_function| match track_sizing_function {
-            TrackSizingFunction::Single(sizing_function) => {
-                tracks
-                    .push(GridTrack::new(sizing_function.min_sizing_function(), sizing_function.max_sizing_function()));
-                tracks.push(GridTrack::gutter(gap));
-                current_track_index += 1;
-            }
-            TrackSizingFunction::AutoRepeat(repetition_kind, repeated_tracks) => {
-                let auto_repeated_track_count = (counts.explicit - (track_template.len() as u16 - 1)) as usize;
-                let iter = repeated_tracks.iter().copied().cycle();
-                for track_def in iter.take(auto_repeated_track_count) {
-                    let mut track = GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function());
-                    let mut gutter = GridTrack::gutter(gap);
-
-                    // Auto-fit tracks that don't contain should be collapsed.
-                    if *repetition_kind == GridTrackRepetition::AutoFit && !track_has_items(current_track_index) {
-                        track.collapse();
-                        gutter.collapse();
-                    }
-
-                    tracks.push(track);
-                    tracks.push(gutter);
-
+        track_template.iter().for_each(|track_sizing_function| {
+            use GridTrackRepetition::{AutoFill, AutoFit, Count};
+            match track_sizing_function {
+                TrackSizingFunction::Single(sizing_function) => {
+                    tracks.push(GridTrack::new(
+                        sizing_function.min_sizing_function(),
+                        sizing_function.max_sizing_function(),
+                    ));
+                    tracks.push(GridTrack::gutter(gap));
                     current_track_index += 1;
                 }
+                TrackSizingFunction::Repeat(Count(count), repeated_tracks) => {
+                    let track_iter = repeated_tracks.iter().cycle().take(repeated_tracks.len() * *count as usize);
+                    track_iter.for_each(|sizing_function| {
+                        tracks.push(GridTrack::new(
+                            sizing_function.min_sizing_function(),
+                            sizing_function.max_sizing_function(),
+                        ));
+                        tracks.push(GridTrack::gutter(gap));
+                        current_track_index += 1;
+                    });
+                }
+                TrackSizingFunction::Repeat(repetition_kind @ (AutoFit | AutoFill), repeated_tracks) => {
+                    let auto_repeated_track_count = (counts.explicit - (track_template.len() as u16 - 1)) as usize;
+                    let iter = repeated_tracks.iter().copied().cycle();
+                    for track_def in iter.take(auto_repeated_track_count) {
+                        let mut track =
+                            GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function());
+                        let mut gutter = GridTrack::gutter(gap);
+
+                        // Auto-fit tracks that don't contain should be collapsed.
+                        if *repetition_kind == AutoFit && !track_has_items(current_track_index) {
+                            track.collapse();
+                            gutter.collapse();
+                        }
+
+                        tracks.push(track);
+                        tracks.push(gutter);
+
+                        current_track_index += 1;
+                    }
+                }
             }
         });
     }
diff --git a/src/style/grid.rs b/src/style/grid.rs
index 388a9290d..3d9d4b4b2 100644
--- a/src/style/grid.rs
+++ b/src/style/grid.rs
@@ -6,6 +6,7 @@ use crate::geometry::{Line, MinMax};
 use crate::style_helpers::*;
 use crate::sys::GridTrackVec;
 use core::cmp::{max, min};
+use core::convert::Infallible;
 
 /// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
 ///
@@ -492,12 +493,42 @@ impl FromFlex for NonRepeatedTrackSizingFunction {
 /// and the difference between AutoFit and AutoFill.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum GridTrackRepetition {
-    /// Auto-repeating track should be generated to fit the container
+    /// Auto-repeating tracks should be generated to fit the container
     /// See: https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill
     AutoFill,
-    /// Auto-repeating track should be generated to fit the container
+    /// Auto-repeating tracks should be generated to fit the container
     /// See: https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit
     AutoFit,
+    /// The specified tracks should be repeated exacts N times
+    Count(u16),
+}
+impl TryFrom<u16> for GridTrackRepetition {
+    type Error = Infallible;
+    fn try_from(value: u16) -> Result<Self, Infallible> {
+        Ok(Self::Count(value))
+    }
+}
+
+/// Error returned when trying to convert a string to a GridTrackRepetition and that string is not
+/// either "auto-fit" or "auto-fill"
+#[derive(Debug)]
+pub struct InvalidStringRepetitionValue;
+#[cfg(feature = "std")]
+impl std::error::Error for InvalidStringRepetitionValue {}
+impl core::fmt::Display for InvalidStringRepetitionValue {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.write_str("&str can only be converted to GridTrackRepetition if it's value is 'auto-fit' or 'auto-fill'")
+    }
+}
+impl<'a> TryFrom<&'a str> for GridTrackRepetition {
+    type Error = InvalidStringRepetitionValue;
+    fn try_from(value: &str) -> Result<Self, InvalidStringRepetitionValue> {
+        match value {
+            "auto-fit" => Ok(Self::AutoFit),
+            "auto-fill" => Ok(Self::AutoFill),
+            _ => Err(InvalidStringRepetitionValue),
+        }
+    }
 }
 
 /// The sizing function for a grid track (row/column)
@@ -508,12 +539,12 @@ pub enum TrackSizingFunction {
     Single(NonRepeatedTrackSizingFunction),
     /// Automatically generate grid tracks to fit the available space using the specified definite track lengths
     /// Only valid if every track in template (not just the repitition) has a fixed size.
-    AutoRepeat(GridTrackRepetition, GridTrackVec<NonRepeatedTrackSizingFunction>),
+    Repeat(GridTrackRepetition, GridTrackVec<NonRepeatedTrackSizingFunction>),
 }
 impl TrackSizingFunction {
     /// Whether the track definition is a auto-repeated fragment
     pub fn is_auto_repetition(&self) -> bool {
-        matches!(self, Self::AutoRepeat(_, _))
+        matches!(self, Self::Repeat(GridTrackRepetition::AutoFit | GridTrackRepetition::AutoFill, _))
     }
 }
 impl TaffyAuto for TrackSizingFunction {
diff --git a/src/style_helpers.rs b/src/style_helpers.rs
index eaf3f7949..69d790421 100644
--- a/src/style_helpers.rs
+++ b/src/style_helpers.rs
@@ -3,6 +3,7 @@ use crate::{
     geometry::{MinMax, Point, Rect, Size},
     style::LengthPercentage,
 };
+use core::fmt::Debug;
 
 #[cfg(feature = "grid")]
 use crate::style::{
@@ -12,11 +13,36 @@ use crate::style::{
 
 /// Returns an auto-repeated track definition
 #[cfg(feature = "grid")]
-pub fn repeat(
-    repetition_kind: GridTrackRepetition,
-    track_list: Vec<NonRepeatedTrackSizingFunction>,
-) -> TrackSizingFunction {
-    TrackSizingFunction::AutoRepeat(repetition_kind, track_list)
+pub fn repeat<Input>(repetition_kind: Input, track_list: Vec<NonRepeatedTrackSizingFunction>) -> TrackSizingFunction
+where
+    Input: TryInto<GridTrackRepetition>,
+    <Input as TryInto<GridTrackRepetition>>::Error: Debug,
+{
+    TrackSizingFunction::Repeat(repetition_kind.try_into().unwrap(), track_list)
+}
+
+#[cfg(feature = "grid")]
+#[cfg(test)]
+mod repeat_fn_tests {
+    use super::repeat;
+    use crate::style::{GridTrackRepetition, NonRepeatedTrackSizingFunction, TrackSizingFunction};
+
+    const TEST_VEC: Vec<NonRepeatedTrackSizingFunction> = Vec::new();
+
+    #[test]
+    fn test_repeat_u16() {
+        assert_eq!(repeat(123, TEST_VEC), TrackSizingFunction::Repeat(GridTrackRepetition::Count(123), TEST_VEC));
+    }
+
+    #[test]
+    fn test_repeat_auto_fit_str() {
+        assert_eq!(repeat("auto-fit", TEST_VEC), TrackSizingFunction::Repeat(GridTrackRepetition::AutoFit, TEST_VEC));
+    }
+
+    #[test]
+    fn test_repeat_auto_fill_str() {
+        assert_eq!(repeat("auto-fill", TEST_VEC), TrackSizingFunction::Repeat(GridTrackRepetition::AutoFill, TEST_VEC));
+    }
 }
 
 /// Specifies a grid line to place a grid item between in CSS Grid Line coordinates:
diff --git a/test_fixtures/grid_repeat_integer.html b/test_fixtures/grid_repeat_integer.html
new file mode 100644
index 000000000..67e5341c5
--- /dev/null
+++ b/test_fixtures/grid_repeat_integer.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <script src="../scripts/gentest/test_helper.js"></script>
+  <link rel="stylesheet" type="text/css" href="../scripts/gentest/test_base_style.css">
+  <title>
+    Test description
+  </title>
+<head/>
+<body>
+
+<div id="test-root" style="height: 120px; width: 120px; display: grid; grid-template-columns: repeat(3, 40px);grid-template-rows: repeat(3, 40px)">
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/test_fixtures/grid_repeat_mixed.html b/test_fixtures/grid_repeat_mixed.html
new file mode 100644
index 000000000..5859bb34a
--- /dev/null
+++ b/test_fixtures/grid_repeat_mixed.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <script src="../scripts/gentest/test_helper.js"></script>
+  <link rel="stylesheet" type="text/css" href="../scripts/gentest/test_base_style.css">
+  <title>
+    Test description
+  </title>
+<head/>
+<body>
+
+<div id="test-root" style="height: 120px; width: 120px; display: grid; grid-template-columns: 40px repeat(1, 40px) repeat(auto-fill, 40px);grid-template-rows: 40px repeat(1, 40px) repeat(auto-fill, 40px)">
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/generated/grid_repeat_integer.rs b/tests/generated/grid_repeat_integer.rs
new file mode 100644
index 000000000..ec241849c
--- /dev/null
+++ b/tests/generated/grid_repeat_integer.rs
@@ -0,0 +1,85 @@
+#[test]
+fn grid_repeat_integer() {
+    use slotmap::Key;
+    #[allow(unused_imports)]
+    use taffy::{layout::Layout, prelude::*};
+    let mut taffy = taffy::Taffy::new();
+    let node0 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node1 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node2 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node3 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node4 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node5 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node6 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node7 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node8 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Grid,
+                grid_template_rows: vec![repeat(GridTrackRepetition::Count(3u16), vec![points(40f32)])],
+                grid_template_columns: vec![repeat(GridTrackRepetition::Count(3u16), vec![points(40f32)])],
+                size: taffy::geometry::Size {
+                    width: taffy::style::Dimension::Points(120f32),
+                    height: taffy::style::Dimension::Points(120f32),
+                },
+                ..Default::default()
+            },
+            &[node0, node1, node2, node3, node4, node5, node6, node7, node8],
+        )
+        .unwrap();
+    taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+    println!("\nComputed tree:");
+    taffy::debug::print_tree(&taffy, node);
+    println!();
+    let Layout { size, location, .. } = taffy.layout(node).unwrap();
+    assert_eq!(size.width, 120f32, "width of node {:?}. Expected {}. Actual {}", node.data(), 120f32, size.width);
+    assert_eq!(size.height, 120f32, "height of node {:?}. Expected {}. Actual {}", node.data(), 120f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node0).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node0.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node0.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node1).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node1.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node1.data(), 40f32, size.height);
+    assert_eq!(location.x, 40f32, "x of node {:?}. Expected {}. Actual {}", node1.data(), 40f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node1.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node2).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node2.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node2.data(), 40f32, size.height);
+    assert_eq!(location.x, 80f32, "x of node {:?}. Expected {}. Actual {}", node2.data(), 80f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node2.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node3).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node3.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node3.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node3.data(), 0f32, location.x);
+    assert_eq!(location.y, 40f32, "y of node {:?}. Expected {}. Actual {}", node3.data(), 40f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node4).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node4.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node4.data(), 40f32, size.height);
+    assert_eq!(location.x, 40f32, "x of node {:?}. Expected {}. Actual {}", node4.data(), 40f32, location.x);
+    assert_eq!(location.y, 40f32, "y of node {:?}. Expected {}. Actual {}", node4.data(), 40f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node5).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node5.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node5.data(), 40f32, size.height);
+    assert_eq!(location.x, 80f32, "x of node {:?}. Expected {}. Actual {}", node5.data(), 80f32, location.x);
+    assert_eq!(location.y, 40f32, "y of node {:?}. Expected {}. Actual {}", node5.data(), 40f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node6).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node6.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node6.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node6.data(), 0f32, location.x);
+    assert_eq!(location.y, 80f32, "y of node {:?}. Expected {}. Actual {}", node6.data(), 80f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node7).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node7.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node7.data(), 40f32, size.height);
+    assert_eq!(location.x, 40f32, "x of node {:?}. Expected {}. Actual {}", node7.data(), 40f32, location.x);
+    assert_eq!(location.y, 80f32, "y of node {:?}. Expected {}. Actual {}", node7.data(), 80f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node8).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node8.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node8.data(), 40f32, size.height);
+    assert_eq!(location.x, 80f32, "x of node {:?}. Expected {}. Actual {}", node8.data(), 80f32, location.x);
+    assert_eq!(location.y, 80f32, "y of node {:?}. Expected {}. Actual {}", node8.data(), 80f32, location.y);
+}
diff --git a/tests/generated/grid_repeat_mixed.rs b/tests/generated/grid_repeat_mixed.rs
new file mode 100644
index 000000000..155eb259b
--- /dev/null
+++ b/tests/generated/grid_repeat_mixed.rs
@@ -0,0 +1,93 @@
+#[test]
+fn grid_repeat_mixed() {
+    use slotmap::Key;
+    #[allow(unused_imports)]
+    use taffy::{layout::Layout, prelude::*};
+    let mut taffy = taffy::Taffy::new();
+    let node0 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node1 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node2 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node3 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node4 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node5 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node6 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node7 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node8 = taffy.new_leaf(taffy::style::Style { ..Default::default() }).unwrap();
+    let node = taffy
+        .new_with_children(
+            taffy::style::Style {
+                display: taffy::style::Display::Grid,
+                grid_template_rows: vec![
+                    points(40f32),
+                    repeat(GridTrackRepetition::Count(1u16), vec![points(40f32)]),
+                    repeat(GridTrackRepetition::AutoFill, vec![points(40f32)]),
+                ],
+                grid_template_columns: vec![
+                    points(40f32),
+                    repeat(GridTrackRepetition::Count(1u16), vec![points(40f32)]),
+                    repeat(GridTrackRepetition::AutoFill, vec![points(40f32)]),
+                ],
+                size: taffy::geometry::Size {
+                    width: taffy::style::Dimension::Points(120f32),
+                    height: taffy::style::Dimension::Points(120f32),
+                },
+                ..Default::default()
+            },
+            &[node0, node1, node2, node3, node4, node5, node6, node7, node8],
+        )
+        .unwrap();
+    taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
+    println!("\nComputed tree:");
+    taffy::debug::print_tree(&taffy, node);
+    println!();
+    let Layout { size, location, .. } = taffy.layout(node).unwrap();
+    assert_eq!(size.width, 120f32, "width of node {:?}. Expected {}. Actual {}", node.data(), 120f32, size.width);
+    assert_eq!(size.height, 120f32, "height of node {:?}. Expected {}. Actual {}", node.data(), 120f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node0).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node0.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node0.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node0.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node1).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node1.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node1.data(), 40f32, size.height);
+    assert_eq!(location.x, 40f32, "x of node {:?}. Expected {}. Actual {}", node1.data(), 40f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node1.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node2).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node2.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node2.data(), 40f32, size.height);
+    assert_eq!(location.x, 80f32, "x of node {:?}. Expected {}. Actual {}", node2.data(), 80f32, location.x);
+    assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node2.data(), 0f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node3).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node3.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node3.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node3.data(), 0f32, location.x);
+    assert_eq!(location.y, 40f32, "y of node {:?}. Expected {}. Actual {}", node3.data(), 40f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node4).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node4.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node4.data(), 40f32, size.height);
+    assert_eq!(location.x, 40f32, "x of node {:?}. Expected {}. Actual {}", node4.data(), 40f32, location.x);
+    assert_eq!(location.y, 40f32, "y of node {:?}. Expected {}. Actual {}", node4.data(), 40f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node5).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node5.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node5.data(), 40f32, size.height);
+    assert_eq!(location.x, 80f32, "x of node {:?}. Expected {}. Actual {}", node5.data(), 80f32, location.x);
+    assert_eq!(location.y, 40f32, "y of node {:?}. Expected {}. Actual {}", node5.data(), 40f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node6).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node6.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node6.data(), 40f32, size.height);
+    assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node6.data(), 0f32, location.x);
+    assert_eq!(location.y, 80f32, "y of node {:?}. Expected {}. Actual {}", node6.data(), 80f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node7).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node7.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node7.data(), 40f32, size.height);
+    assert_eq!(location.x, 40f32, "x of node {:?}. Expected {}. Actual {}", node7.data(), 40f32, location.x);
+    assert_eq!(location.y, 80f32, "y of node {:?}. Expected {}. Actual {}", node7.data(), 80f32, location.y);
+    let Layout { size, location, .. } = taffy.layout(node8).unwrap();
+    assert_eq!(size.width, 40f32, "width of node {:?}. Expected {}. Actual {}", node8.data(), 40f32, size.width);
+    assert_eq!(size.height, 40f32, "height of node {:?}. Expected {}. Actual {}", node8.data(), 40f32, size.height);
+    assert_eq!(location.x, 80f32, "x of node {:?}. Expected {}. Actual {}", node8.data(), 80f32, location.x);
+    assert_eq!(location.y, 80f32, "y of node {:?}. Expected {}. Actual {}", node8.data(), 80f32, location.y);
+}
diff --git a/tests/generated/mod.rs b/tests/generated/mod.rs
index 1c1f11387..73b8c371b 100644
--- a/tests/generated/mod.rs
+++ b/tests/generated/mod.rs
@@ -576,6 +576,10 @@ mod grid_placement_definite_in_secondary_axis_with_fully_definite_negative;
 #[cfg(feature = "grid")]
 mod grid_relayout_vertical_text;
 #[cfg(feature = "grid")]
+mod grid_repeat_integer;
+#[cfg(feature = "grid")]
+mod grid_repeat_mixed;
+#[cfg(feature = "grid")]
 mod grid_size_child_fixed_tracks;
 mod justify_content_column_center;
 mod justify_content_column_flex_end;