tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 583HTML 275Markdown 8TOML 41586 Sutichaykuna

⎇ 2ca609ecf19c2e34bca2ce1fe1f4e73b09d62a97 src/style/grid.rs Rust · 1287 siq'ikuna · 50.8 KB Ñawpa willay Pi imata llamkarqan ch'uya

Implement Floats (#866) 25759dad4 · Nico Burns ·

1//! Style types for CSS Grid layout
2use super::{
3    AlignContent, AlignItems, AlignSelf, CheapCloneStr, CompactLength, CoreStyle, Dimension, JustifyContent,
4    LengthPercentage, LengthPercentageAuto, Style,
5};
6use crate::compute::grid::{GridCoordinate, GridLine, OriginZeroLine};
7use crate::geometry::{AbsoluteAxis, AbstractAxis, Line, MinMax, Size};
8use crate::style_helpers::*;
9use crate::sys::{DefaultCheapStr, Vec};
10use core::cmp::{max, min};
11use core::fmt::Debug;
12
13/// Defines a grid area
14#[derive(Debug, Clone, PartialEq)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16pub struct GridTemplateArea<CustomIdent: CheapCloneStr> {
17    /// The name of the grid area which
18    #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::util::deserialize_from_str"))]
19    pub name: CustomIdent,
20    /// The index of the row at which the grid area starts in grid coordinates.
21    pub row_start: u16,
22    /// The index of the row at which the grid area ends in grid coordinates.
23    pub row_end: u16,
24    /// The index of the column at which the grid area starts in grid coordinates.
25    pub column_start: u16,
26    /// The index of the column at which the grid area end in grid coordinates.
27    pub column_end: u16,
28}
29
30/// Defines a named grid line
31#[derive(Debug, Clone, PartialEq)]
32#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33pub struct NamedGridLine<CustomIdent: CheapCloneStr> {
34    /// The name of the grid area which
35    #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::util::deserialize_from_str"))]
36    pub name: CustomIdent,
37    /// The index of the row at which the grid area starts in grid coordinates.
38    pub index: u16,
39}
40
41/// Axis as `Row` or `Column`
42#[derive(Debug, Clone, Copy, PartialEq)]
43pub(crate) enum GridAreaAxis {
44    /// The `Row` axis
45    Row,
46    /// The `Column` axis
47    Column,
48}
49
50/// Logical end (`Start` or `End`)
51#[derive(Debug, Clone, Copy, PartialEq)]
52pub(crate) enum GridAreaEnd {
53    /// The `Start` end
54    Start,
55    /// The `End` end
56    End,
57}
58
59/// A trait to represent a `repeat()` clause in a `grid-template-*` definition
60pub trait GenericRepetition {
61    /// The type that represents `<custom-ident>`s (for named lines)
62    type CustomIdent: CheapCloneStr;
63    /// The type which represents an iterator over the list of repeated tracks
64    type RepetitionTrackList<'a>: Iterator<Item = TrackSizingFunction> + ExactSizeIterator + Clone
65    where
66        Self: 'a;
67
68    /// A nested iterator of line names (nested because each line may have multiple associated names)
69    type TemplateLineNames<'a>: TemplateLineNames<'a, Self::CustomIdent>
70    where
71        Self: 'a;
72    /// The repetition count (integer, auto-fill, or auto-fit)
73    fn count(&self) -> RepetitionCount;
74    /// Get an iterator over the repeated tracks
75    fn tracks(&self) -> Self::RepetitionTrackList<'_>;
76    /// Returns the number of repeated tracks
77    fn track_count(&self) -> u16 {
78        self.tracks().len() as u16
79    }
80    /// Returns an iterator over the lines names
81    fn lines_names(&self) -> Self::TemplateLineNames<'_>;
82}
83
84/// A nested list of line names. This is effectively a generic representation of `Vec<Vec<String>>` that allows
85/// both the collection and string type to be customised.
86#[rustfmt::skip]
87pub trait TemplateLineNames<'a, S: CheapCloneStr> : Iterator<Item = Self::LineNameSet<'a>> + ExactSizeIterator + Clone where Self: 'a {
88    /// A simple list line names. This is effectively a generic representation of `VecString>` that allows
89    /// both the collection and string type to be customised.
90    type LineNameSet<'b>: Iterator<Item = &'b S> + ExactSizeIterator + Clone where Self: 'b;
91}
92
93impl<'a, S: CheapCloneStr> TemplateLineNames<'a, S>
94    for core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>>
95{
96    type LineNameSet<'b>
97        = core::slice::Iter<'b, S>
98    where
99        Self: 'b;
100}
101
102#[derive(Copy, Clone)]
103/// A type representing a component in a `grid-template-*` defintion where the type
104/// representing `repeat()`s is generic
105pub enum GenericGridTemplateComponent<S, Repetition>
106where
107    S: CheapCloneStr,
108    Repetition: GenericRepetition<CustomIdent = S>,
109{
110    /// A single track sizing function
111    Single(TrackSizingFunction),
112    /// A `repeat()`
113    Repeat(Repetition),
114}
115
116impl<S, Repetition> GenericGridTemplateComponent<S, Repetition>
117where
118    S: CheapCloneStr,
119    Repetition: GenericRepetition<CustomIdent = S>,
120{
121    /// Whether the track definition is a auto-repeated fragment
122    pub fn is_auto_repetition(&self) -> bool {
123        match self {
124            Self::Single(_) => false,
125            Self::Repeat(repeat) => matches!(repeat.count(), RepetitionCount::AutoFit | RepetitionCount::AutoFill),
126        }
127    }
128}
129
130/// The set of styles required for a CSS Grid container
131pub trait GridContainerStyle: CoreStyle {
132    /// The type for a `repeat()` within a grid_template_rows or grid_template_columns
133    type Repetition<'a>: GenericRepetition<CustomIdent = Self::CustomIdent>
134    where
135        Self: 'a;
136
137    /// The type returned by grid_template_rows and grid_template_columns
138    type TemplateTrackList<'a>: Iterator<Item = GenericGridTemplateComponent<Self::CustomIdent, Self::Repetition<'a>>>
139        + ExactSizeIterator
140        + Clone
141    where
142        Self: 'a;
143
144    /// The type returned by grid_auto_rows and grid_auto_columns
145    type AutoTrackList<'a>: Iterator<Item = TrackSizingFunction> + ExactSizeIterator + Clone
146    where
147        Self: 'a;
148
149    /// The type returned by grid_template_row_names and grid_template_column_names
150    //IntoIterator<Item = &'a Self::LineNameSet<'a>>
151    type TemplateLineNames<'a>: TemplateLineNames<'a, Self::CustomIdent>
152    where
153        Self: 'a;
154
155    /// The type of custom identifiers used to identify named grid lines and areas
156    type GridTemplateAreas<'a>: IntoIterator<Item = GridTemplateArea<Self::CustomIdent>>
157    where
158        Self: 'a;
159
160    // FIXME: re-add default implemenations for grid_{template,auto}_{rows,columns} once the
161    // associated_type_defaults feature (https://github.com/rust-lang/rust/issues/29661) is stabilised.
162
163    /// Defines the track sizing functions (heights) of the grid rows
164    fn grid_template_rows(&self) -> Option<Self::TemplateTrackList<'_>>;
165    /// Defines the track sizing functions (widths) of the grid columns
166    fn grid_template_columns(&self) -> Option<Self::TemplateTrackList<'_>>;
167    /// Defines the size of implicitly created rows
168    fn grid_auto_rows(&self) -> Self::AutoTrackList<'_>;
169    /// Defined the size of implicitly created columns
170    fn grid_auto_columns(&self) -> Self::AutoTrackList<'_>;
171
172    /// Named grid areas
173    fn grid_template_areas(&self) -> Option<Self::GridTemplateAreas<'_>>;
174    /// Defines the line names for row lines
175    fn grid_template_column_names(&self) -> Option<Self::TemplateLineNames<'_>>;
176    /// Defines the size of implicitly created rows
177    fn grid_template_row_names(&self) -> Option<Self::TemplateLineNames<'_>>;
178
179    /// Controls how items get placed into the grid for auto-placed items
180    #[inline(always)]
181    fn grid_auto_flow(&self) -> GridAutoFlow {
182        Style::<Self::CustomIdent>::DEFAULT.grid_auto_flow
183    }
184
185    /// How large should the gaps between items in a grid or flex container be?
186    #[inline(always)]
187    fn gap(&self) -> Size<LengthPercentage> {
188        Style::<Self::CustomIdent>::DEFAULT.gap
189    }
190
191    // Alignment properties
192
193    /// How should content contained within this item be aligned in the cross/block axis
194    #[inline(always)]
195    fn align_content(&self) -> Option<AlignContent> {
196        Style::<Self::CustomIdent>::DEFAULT.align_content
197    }
198    /// How should contained within this item be aligned in the main/inline axis
199    #[inline(always)]
200    fn justify_content(&self) -> Option<JustifyContent> {
201        Style::<Self::CustomIdent>::DEFAULT.justify_content
202    }
203    /// How this node's children aligned in the cross/block axis?
204    #[inline(always)]
205    fn align_items(&self) -> Option<AlignItems> {
206        Style::<Self::CustomIdent>::DEFAULT.align_items
207    }
208    /// How this node's children should be aligned in the inline axis
209    #[inline(always)]
210    fn justify_items(&self) -> Option<AlignItems> {
211        Style::<Self::CustomIdent>::DEFAULT.justify_items
212    }
213
214    /// Get a grid item's row or column placement depending on the axis passed
215    #[inline(always)]
216    fn grid_template_tracks(&self, axis: AbsoluteAxis) -> Option<Self::TemplateTrackList<'_>> {
217        match axis {
218            AbsoluteAxis::Horizontal => self.grid_template_columns(),
219            AbsoluteAxis::Vertical => self.grid_template_rows(),
220        }
221    }
222
223    /// Get a grid container's align-content or justify-content alignment depending on the axis passed
224    #[inline(always)]
225    fn grid_align_content(&self, axis: AbstractAxis) -> AlignContent {
226        match axis {
227            AbstractAxis::Inline => self.justify_content().unwrap_or(AlignContent::Stretch),
228            AbstractAxis::Block => self.align_content().unwrap_or(AlignContent::Stretch),
229        }
230    }
231}
232
233/// The set of styles required for a CSS Grid item (child of a CSS Grid container)
234pub trait GridItemStyle: CoreStyle {
235    /// Defines which row in the grid the item should start and end at
236    #[inline(always)]
237    fn grid_row(&self) -> Line<GridPlacement<Self::CustomIdent>> {
238        Default::default()
239    }
240    /// Defines which column in the grid the item should start and end at
241    #[inline(always)]
242    fn grid_column(&self) -> Line<GridPlacement<Self::CustomIdent>> {
243        Default::default()
244    }
245
246    /// How this node should be aligned in the cross/block axis
247    /// Falls back to the parents [`AlignItems`] if not set
248    #[inline(always)]
249    fn align_self(&self) -> Option<AlignSelf> {
250        Style::<Self::CustomIdent>::DEFAULT.align_self
251    }
252    /// How this node should be aligned in the inline axis
253    /// Falls back to the parents [`super::JustifyItems`] if not set
254    #[inline(always)]
255    fn justify_self(&self) -> Option<AlignSelf> {
256        Style::<Self::CustomIdent>::DEFAULT.justify_self
257    }
258
259    /// Get a grid item's row or column placement depending on the axis passed
260    #[inline(always)]
261    fn grid_placement(&self, axis: AbsoluteAxis) -> Line<GridPlacement<Self::CustomIdent>> {
262        match axis {
263            AbsoluteAxis::Horizontal => self.grid_column(),
264            AbsoluteAxis::Vertical => self.grid_row(),
265        }
266    }
267}
268
269/// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
270///
271/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. This may cause items to appear out-of-order, when doing so would fill in holes left by larger items.
272///
273/// Defaults to [`GridAutoFlow::Row`]
274///
275/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow)
276#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
277#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
278pub enum GridAutoFlow {
279    /// Items are placed by filling each row in turn, adding new rows as necessary
280    #[default]
281    Row,
282    /// Items are placed by filling each column in turn, adding new columns as necessary.
283    Column,
284    /// Combines `Row` with the dense packing algorithm.
285    RowDense,
286    /// Combines `Column` with the dense packing algorithm.
287    ColumnDense,
288}
289
290impl GridAutoFlow {
291    /// Whether grid auto placement uses the sparse placement algorithm or the dense placement algorithm
292    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
293    pub const fn is_dense(&self) -> bool {
294        match self {
295            Self::Row | Self::Column => false,
296            Self::RowDense | Self::ColumnDense => true,
297        }
298    }
299
300    /// Whether grid auto placement fills areas row-wise or column-wise
301    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
302    pub const fn primary_axis(&self) -> AbsoluteAxis {
303        match self {
304            Self::Row | Self::RowDense => AbsoluteAxis::Horizontal,
305            Self::Column | Self::ColumnDense => AbsoluteAxis::Vertical,
306        }
307    }
308}
309
310/// A grid line placement specification which is generic over the coordinate system that it uses to define
311/// grid line positions.
312///
313/// `GenericGridPlacement<GridLine>` is aliased as GridPlacement and is exposed to users of Taffy to define styles.
314/// `GenericGridPlacement<OriginZeroLine>` is aliased as OriginZeroGridPlacement and is used internally for placement computations.
315#[derive(Copy, Clone, PartialEq, Eq, Debug)]
316#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
317pub enum GenericGridPlacement<LineType: GridCoordinate> {
318    /// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property
319    Auto,
320    /// Place item at specified line (column or row) index
321    Line(LineType),
322    /// Item should span specified number of tracks (columns or rows)
323    Span(u16),
324}
325
326/// A grid line placement using the normalized OriginZero coordinates to specify line positions.
327pub(crate) type OriginZeroGridPlacement = GenericGridPlacement<OriginZeroLine>;
328
329/// A grid line placement using CSS grid line coordinates to specify line positions. This uses the same coordinate
330/// system as the public `GridPlacement` type but doesn't support named lines (these are expected to have already
331/// been resolved by the time values of this type are constructed).
332pub(crate) type NonNamedGridPlacement = GenericGridPlacement<GridLine>;
333
334/// A grid line placement specification. Used for grid-[row/column]-[start/end]. Named tracks are not implemented.
335///
336/// Defaults to `GridPlacement::Auto`
337///
338/// [Specification](https://www.w3.org/TR/css3-grid-layout/#typedef-grid-row-start-grid-line)
339#[derive(Clone, PartialEq, Debug, Default)]
340#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
341pub enum GridPlacement<S: CheapCloneStr = DefaultCheapStr> {
342    /// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property
343    #[default]
344    Auto,
345    /// Place item at specified line (column or row) index
346    Line(GridLine),
347    /// Place item at specified named line (column or row)
348    NamedLine(S, i16),
349    /// Item should span specified number of tracks (columns or rows)
350    Span(u16),
351    /// Item should span until the nth line named `<name>`.
352    ///
353    /// If there are less than n lines named `<name>` in the specified direction then
354    /// all implicit lines will be counted.
355    NamedSpan(S, u16),
356}
357impl<S: CheapCloneStr> TaffyAuto for GridPlacement<S> {
358    const AUTO: Self = Self::Auto;
359}
360impl<S: CheapCloneStr> TaffyGridLine for GridPlacement<S> {
361    fn from_line_index(index: i16) -> Self {
362        GridPlacement::<S>::Line(GridLine::from(index))
363    }
364}
365impl<S: CheapCloneStr> TaffyGridLine for Line<GridPlacement<S>> {
366    fn from_line_index(index: i16) -> Self {
367        Line { start: GridPlacement::<S>::from_line_index(index), end: GridPlacement::<S>::Auto }
368    }
369}
370impl<S: CheapCloneStr> TaffyGridSpan for GridPlacement<S> {
371    fn from_span(span: u16) -> Self {
372        GridPlacement::<S>::Span(span)
373    }
374}
375impl<S: CheapCloneStr> TaffyGridSpan for Line<GridPlacement<S>> {
376    fn from_span(span: u16) -> Self {
377        Line { start: GridPlacement::<S>::from_span(span), end: GridPlacement::<S>::Auto }
378    }
379}
380
381impl<S: CheapCloneStr> GridPlacement<S> {
382    /// Apply a mapping function if the [`GridPlacement`] is a `Line`. Otherwise return `self` unmodified.
383    pub fn into_origin_zero_placement_ignoring_named(&self, explicit_track_count: u16) -> OriginZeroGridPlacement {
384        match self {
385            Self::Auto => OriginZeroGridPlacement::Auto,
386            Self::Span(span) => OriginZeroGridPlacement::Span(*span),
387            // Grid line zero is an invalid index, so it gets treated as Auto
388            // See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start#values
389            Self::Line(line) => match line.as_i16() {
390                0 => OriginZeroGridPlacement::Auto,
391                _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
392            },
393            Self::NamedLine(_, _) => OriginZeroGridPlacement::Auto,
394            Self::NamedSpan(_, _) => OriginZeroGridPlacement::Auto,
395        }
396    }
397}
398
399impl<S: CheapCloneStr> Line<GridPlacement<S>> {
400    /// Apply a mapping function if the [`GridPlacement`] is a `Line`. Otherwise return `self` unmodified.
401    pub fn into_origin_zero_ignoring_named(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
402        Line {
403            start: self.start.into_origin_zero_placement_ignoring_named(explicit_track_count),
404            end: self.end.into_origin_zero_placement_ignoring_named(explicit_track_count),
405        }
406    }
407}
408
409impl NonNamedGridPlacement {
410    /// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
411    pub fn into_origin_zero_placement(
412        &self,
413        explicit_track_count: u16,
414        // resolve_named: impl Fn(&str) -> Option<GridLine>
415    ) -> OriginZeroGridPlacement {
416        match self {
417            Self::Auto => OriginZeroGridPlacement::Auto,
418            Self::Span(span) => OriginZeroGridPlacement::Span(*span),
419            // Grid line zero is an invalid index, so it gets treated as Auto
420            // See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start#values
421            Self::Line(line) => match line.as_i16() {
422                0 => OriginZeroGridPlacement::Auto,
423                _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
424            },
425        }
426    }
427}
428
429impl<T: GridCoordinate> Line<GenericGridPlacement<T>> {
430    /// Resolves the span for an indefinite placement (a placement that does not consist of two `Track`s).
431    /// Panics if called on a definite placement
432    pub const fn indefinite_span(&self) -> u16 {
433        use GenericGridPlacement as GP;
434        match (self.start, self.end) {
435            (GP::Line(_), GP::Auto) => 1,
436            (GP::Auto, GP::Line(_)) => 1,
437            (GP::Auto, GP::Auto) => 1,
438            (GP::Line(_), GP::Span(span)) => span,
439            (GP::Span(span), GP::Line(_)) => span,
440            (GP::Span(span), GP::Auto) => span,
441            (GP::Auto, GP::Span(span)) => span,
442            (GP::Span(span), GP::Span(_)) => span,
443            (GP::Line(_), GP::Line(_)) => panic!("indefinite_span should only be called on indefinite grid tracks"),
444        }
445    }
446}
447
448impl<S: CheapCloneStr> Line<GridPlacement<S>> {
449    #[inline]
450    /// Whether the track position is definite in this axis (or the item will need auto placement)
451    /// The track position is definite if least one of the start and end positions is a NON-ZERO track index
452    /// (0 is an invalid line in GridLine coordinates, and falls back to "auto" which is indefinite)
453    pub fn is_definite(&self) -> bool {
454        match (&self.start, &self.end) {
455            (GridPlacement::Line(line), _) if line.as_i16() != 0 => true,
456            (_, GridPlacement::Line(line)) if line.as_i16() != 0 => true,
457            (GridPlacement::NamedLine(_, _), _) => true,
458            (_, GridPlacement::NamedLine(_, _)) => true,
459            _ => false,
460        }
461    }
462}
463
464impl Line<NonNamedGridPlacement> {
465    #[inline]
466    /// Whether the track position is definite in this axis (or the item will need auto placement)
467    /// The track position is definite if least one of the start and end positions is a NON-ZERO track index
468    /// (0 is an invalid line in GridLine coordinates, and falls back to "auto" which is indefinite)
469    pub fn is_definite(&self) -> bool {
470        match (&self.start, &self.end) {
471            (GenericGridPlacement::Line(line), _) if line.as_i16() != 0 => true,
472            (_, GenericGridPlacement::Line(line)) if line.as_i16() != 0 => true,
473            _ => false,
474        }
475    }
476
477    /// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
478    pub fn into_origin_zero(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
479        Line {
480            start: self.start.into_origin_zero_placement(explicit_track_count),
481            end: self.end.into_origin_zero_placement(explicit_track_count),
482        }
483    }
484}
485
486impl Line<OriginZeroGridPlacement> {
487    #[inline]
488    /// Whether the track position is definite in this axis (or the item will need auto placement)
489    /// The track position is definite if least one of the start and end positions is a track index
490    pub const fn is_definite(&self) -> bool {
491        matches!((self.start, self.end), (GenericGridPlacement::Line(_), _) | (_, GenericGridPlacement::Line(_)))
492    }
493
494    /// If at least one of the of the start and end positions is a track index then the other end can be resolved
495    /// into a track index purely based on the information contained with the placement specification
496    pub fn resolve_definite_grid_lines(&self) -> Line<OriginZeroLine> {
497        use OriginZeroGridPlacement as GP;
498        match (self.start, self.end) {
499            (GP::Line(line1), GP::Line(line2)) => {
500                if line1 == line2 {
501                    Line { start: line1, end: line1 + 1 }
502                } else {
503                    Line { start: min(line1, line2), end: max(line1, line2) }
504                }
505            }
506            (GP::Line(line), GP::Span(span)) => Line { start: line, end: line + span },
507            (GP::Line(line), GP::Auto) => Line { start: line, end: line + 1 },
508            (GP::Span(span), GP::Line(line)) => Line { start: line - span, end: line },
509            (GP::Auto, GP::Line(line)) => Line { start: line - 1, end: line },
510            _ => panic!("resolve_definite_grid_tracks should only be called on definite grid tracks"),
511        }
512    }
513
514    /// For absolutely positioned items:
515    ///   - Tracks resolve to definite tracks
516    ///   - For Spans:
517    ///      - If the other position is a Track, they resolve to a definite track relative to the other track
518    ///      - Else resolve to None
519    ///   - Auto resolves to None
520    ///
521    /// When finally positioning the item, a value of None means that the item's grid area is bounded by the grid
522    /// container's border box on that side.
523    pub fn resolve_absolutely_positioned_grid_tracks(&self) -> Line<Option<OriginZeroLine>> {
524        use OriginZeroGridPlacement as GP;
525        match (self.start, self.end) {
526            (GP::Line(track1), GP::Line(track2)) => {
527                if track1 == track2 {
528                    Line { start: Some(track1), end: Some(track1 + 1) }
529                } else {
530                    Line { start: Some(min(track1, track2)), end: Some(max(track1, track2)) }
531                }
532            }
533            (GP::Line(track), GP::Span(span)) => Line { start: Some(track), end: Some(track + span) },
534            (GP::Line(track), GP::Auto) => Line { start: Some(track), end: None },
535            (GP::Span(span), GP::Line(track)) => Line { start: Some(track - span), end: Some(track) },
536            (GP::Auto, GP::Line(track)) => Line { start: None, end: Some(track) },
537            _ => Line { start: None, end: None },
538        }
539    }
540
541    /// If neither of the start and end positions is a track index then the other end can be resolved
542    /// into a track index if a definite start position is supplied externally
543    pub fn resolve_indefinite_grid_tracks(&self, start: OriginZeroLine) -> Line<OriginZeroLine> {
544        use OriginZeroGridPlacement as GP;
545        match (self.start, self.end) {
546            (GP::Auto, GP::Auto) => Line { start, end: start + 1 },
547            (GP::Span(span), GP::Auto) => Line { start, end: start + span },
548            (GP::Auto, GP::Span(span)) => Line { start, end: start + span },
549            (GP::Span(span), GP::Span(_)) => Line { start, end: start + span },
550            _ => panic!("resolve_indefinite_grid_tracks should only be called on indefinite grid tracks"),
551        }
552    }
553}
554
555/// Represents the start and end points of a GridItem within a given axis
556impl<S: CheapCloneStr> Default for Line<GridPlacement<S>> {
557    fn default() -> Self {
558        Line { start: GridPlacement::<S>::Auto, end: GridPlacement::<S>::Auto }
559    }
560}
561
562/// Maximum track sizing function
563///
564/// Specifies the maximum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
565/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
566/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
567#[derive(Copy, Clone, PartialEq, Debug)]
568#[cfg_attr(feature = "serde", derive(Serialize))]
569pub struct MaxTrackSizingFunction(pub(crate) CompactLength);
570impl TaffyZero for MaxTrackSizingFunction {
571    const ZERO: Self = Self(CompactLength::ZERO);
572}
573impl TaffyAuto for MaxTrackSizingFunction {
574    const AUTO: Self = Self(CompactLength::AUTO);
575}
576impl TaffyMinContent for MaxTrackSizingFunction {
577    const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
578}
579impl TaffyMaxContent for MaxTrackSizingFunction {
580    const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
581}
582impl FromLength for MaxTrackSizingFunction {
583    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
584        Self::length(value.into())
585    }
586}
587impl FromPercent for MaxTrackSizingFunction {
588    fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
589        Self::percent(value.into())
590    }
591}
592impl TaffyFitContent for MaxTrackSizingFunction {
593    fn fit_content(argument: LengthPercentage) -> Self {
594        Self(CompactLength::fit_content(argument))
595    }
596}
597impl FromFr for MaxTrackSizingFunction {
598    fn from_fr<Input: Into<f32> + Copy>(value: Input) -> Self {
599        Self::fr(value.into())
600    }
601}
602impl From<LengthPercentage> for MaxTrackSizingFunction {
603    fn from(input: LengthPercentage) -> Self {
604        Self(input.0)
605    }
606}
607impl From<LengthPercentageAuto> for MaxTrackSizingFunction {
608    fn from(input: LengthPercentageAuto) -> Self {
609        Self(input.0)
610    }
611}
612impl From<Dimension> for MaxTrackSizingFunction {
613    fn from(input: Dimension) -> Self {
614        Self(input.0)
615    }
616}
617impl From<MinTrackSizingFunction> for MaxTrackSizingFunction {
618    fn from(input: MinTrackSizingFunction) -> Self {
619        Self(input.0)
620    }
621}
622#[cfg(feature = "serde")]
623impl<'de> serde::Deserialize<'de> for MaxTrackSizingFunction {
624    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
625    where
626        D: serde::Deserializer<'de>,
627    {
628        let inner = CompactLength::deserialize(deserializer)?;
629        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
630        if matches!(
631            inner.tag(),
632            CompactLength::LENGTH_TAG
633                | CompactLength::PERCENT_TAG
634                | CompactLength::AUTO_TAG
635                | CompactLength::MIN_CONTENT_TAG
636                | CompactLength::MAX_CONTENT_TAG
637                | CompactLength::FIT_CONTENT_PX_TAG
638                | CompactLength::FIT_CONTENT_PERCENT_TAG
639                | CompactLength::FR_TAG
640        ) {
641            Ok(Self(inner))
642        } else {
643            Err(serde::de::Error::custom("Invalid tag"))
644        }
645    }
646}
647
648impl MaxTrackSizingFunction {
649    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
650    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
651    #[inline(always)]
652    pub const fn length(val: f32) -> Self {
653        Self(CompactLength::length(val))
654    }
655
656    /// A percentage length relative to the size of the containing block.
657    ///
658    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
659    #[inline(always)]
660    pub const fn percent(val: f32) -> Self {
661        Self(CompactLength::percent(val))
662    }
663
664    /// The dimension should be automatically computed according to algorithm-specific rules
665    /// regarding the default size of boxes.
666    #[inline(always)]
667    pub const fn auto() -> Self {
668        Self(CompactLength::auto())
669    }
670
671    /// The size should be the "min-content" size.
672    /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
673    #[inline(always)]
674    pub const fn min_content() -> Self {
675        Self(CompactLength::min_content())
676    }
677
678    /// The size should be the "max-content" size.
679    /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
680    #[inline(always)]
681    pub const fn max_content() -> Self {
682        Self(CompactLength::max_content())
683    }
684
685    /// The size should be computed according to the "fit content" formula:
686    ///    `max(min_content, min(max_content, limit))`
687    /// where:
688    ///    - `min_content` is the [min-content](Self::min_content) size
689    ///    - `max_content` is the [max-content](Self::max_content) size
690    ///    - `limit` is a LENGTH value passed to this function
691    ///
692    /// The effect of this is that the item takes the size of `limit` clamped
693    /// by the min-content and max-content sizes.
694    #[inline(always)]
695    pub const fn fit_content_px(limit: f32) -> Self {
696        Self(CompactLength::fit_content_px(limit))
697    }
698
699    /// The size should be computed according to the "fit content" formula:
700    ///    `max(min_content, min(max_content, limit))`
701    /// where:
702    ///    - `min_content` is the [min-content](Self::min_content) size
703    ///    - `max_content` is the [max-content](Self::max_content) size
704    ///    - `limit` is a PERCENTAGE value passed to this function
705    ///
706    /// The effect of this is that the item takes the size of `limit` clamped
707    /// by the min-content and max-content sizes.
708    #[inline(always)]
709    pub const fn fit_content_percent(limit: f32) -> Self {
710        Self(CompactLength::fit_content_percent(limit))
711    }
712
713    /// The dimension as a fraction of the total available grid space (`fr` units in CSS)
714    /// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
715    /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
716    #[inline(always)]
717    pub const fn fr(val: f32) -> Self {
718        Self(CompactLength::fr(val))
719    }
720
721    /// A `calc()` value. The value passed here is treated as an opaque handle to
722    /// the actual calc representation and may be a pointer, index, etc.
723    ///
724    /// The low 3 bits are used as a tag value and will be returned as 0.
725    #[inline]
726    #[cfg(feature = "calc")]
727    pub fn calc(ptr: *const ()) -> Self {
728        Self(CompactLength::calc(ptr))
729    }
730
731    /// Create a LengthPercentageAuto from a raw `CompactLength`.
732    /// # Safety
733    /// CompactLength must represent a valid variant for LengthPercentageAuto
734    #[allow(unsafe_code)]
735    pub unsafe fn from_raw(val: CompactLength) -> Self {
736        Self(val)
737    }
738
739    /// Get the underlying `CompactLength` representation of the value
740    pub fn into_raw(self) -> CompactLength {
741        self.0
742    }
743
744    /// Returns true if the max track sizing function is `MinContent`, `MaxContent`, `FitContent` or `Auto`, else false.
745    #[inline(always)]
746    pub fn is_intrinsic(&self) -> bool {
747        self.0.is_intrinsic()
748    }
749
750    /// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
751    /// "In all cases, treat auto and fit-content() as max-content, except where specified otherwise for fit-content()."
752    /// See: <https://www.w3.org/TR/css-grid-1/#algo-terms>
753    #[inline(always)]
754    pub fn is_max_content_alike(&self) -> bool {
755        self.0.is_max_content_alike()
756    }
757
758    /// Returns true if the an Fr value, else false.
759    #[inline(always)]
760    pub fn is_fr(&self) -> bool {
761        self.0.is_fr()
762    }
763
764    /// Returns true if the is `Auto`, else false.
765    #[inline(always)]
766    pub fn is_auto(&self) -> bool {
767        self.0.is_auto()
768    }
769
770    /// Returns true if value is MinContent
771    #[inline(always)]
772    pub fn is_min_content(&self) -> bool {
773        self.0.is_min_content()
774    }
775
776    /// Returns true if value is MaxContent
777    #[inline(always)]
778    pub fn is_max_content(&self) -> bool {
779        self.0.is_max_content()
780    }
781
782    /// Returns true if value is FitContent(...)
783    #[inline(always)]
784    pub fn is_fit_content(&self) -> bool {
785        self.0.is_fit_content()
786    }
787
788    /// Returns true if value is MaxContent or FitContent(...)
789    #[inline(always)]
790    pub fn is_max_or_fit_content(&self) -> bool {
791        self.0.is_max_or_fit_content()
792    }
793
794    /// Returns whether the value can be resolved using `Self::definite_value`
795    #[inline(always)]
796    pub fn has_definite_value(self, parent_size: Option<f32>) -> bool {
797        match self.0.tag() {
798            CompactLength::LENGTH_TAG => true,
799            CompactLength::PERCENT_TAG => parent_size.is_some(),
800            #[cfg(feature = "calc")]
801            _ if self.0.is_calc() => parent_size.is_some(),
802            _ => false,
803        }
804    }
805
806    /// Returns fixed point values directly. Attempts to resolve percentage values against
807    /// the passed available_space and returns if this results in a concrete value (which it
808    /// will if the available_space is `Some`). Otherwise returns None.
809    #[inline(always)]
810    pub fn definite_value(
811        self,
812        parent_size: Option<f32>,
813        calc_resolver: impl Fn(*const (), f32) -> f32,
814    ) -> Option<f32> {
815        match self.0.tag() {
816            CompactLength::LENGTH_TAG => Some(self.0.value()),
817            CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
818            #[cfg(feature = "calc")]
819            _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
820            _ => None,
821        }
822    }
823
824    /// Resolve the maximum size of the track as defined by either:
825    ///     - A fixed track sizing function
826    ///     - A percentage track sizing function (with definite available space)
827    ///     - A fit-content sizing function with fixed argument
828    ///     - A fit-content sizing function with percentage argument (with definite available space)
829    /// All other kinds of track sizing function return None.
830    #[inline(always)]
831    pub fn definite_limit(
832        self,
833        parent_size: Option<f32>,
834        calc_resolver: impl Fn(*const (), f32) -> f32,
835    ) -> Option<f32> {
836        match self.0.tag() {
837            CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
838            CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
839            _ => self.definite_value(parent_size, calc_resolver),
840        }
841    }
842
843    /// Resolve percentage values against the passed parent_size, returning Some(value)
844    /// Non-percentage values always return None.
845    #[inline(always)]
846    pub fn resolved_percentage_size(
847        self,
848        parent_size: f32,
849        calc_resolver: impl Fn(*const (), f32) -> f32,
850    ) -> Option<f32> {
851        self.0.resolved_percentage_size(parent_size, calc_resolver)
852    }
853
854    /// Whether the track sizing functions depends on the size of the parent node
855    #[inline(always)]
856    pub fn uses_percentage(self) -> bool {
857        self.0.uses_percentage()
858    }
859}
860
861/// Minimum track sizing function
862///
863/// Specifies the minimum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
864/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
865/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
866#[derive(Copy, Clone, PartialEq, Debug)]
867#[cfg_attr(feature = "serde", derive(Serialize))]
868pub struct MinTrackSizingFunction(pub(crate) CompactLength);
869impl TaffyZero for MinTrackSizingFunction {
870    const ZERO: Self = Self(CompactLength::ZERO);
871}
872impl TaffyAuto for MinTrackSizingFunction {
873    const AUTO: Self = Self(CompactLength::AUTO);
874}
875impl TaffyMinContent for MinTrackSizingFunction {
876    const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
877}
878impl TaffyMaxContent for MinTrackSizingFunction {
879    const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
880}
881impl FromLength for MinTrackSizingFunction {
882    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
883        Self::length(value.into())
884    }
885}
886impl FromPercent for MinTrackSizingFunction {
887    fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
888        Self::percent(value.into())
889    }
890}
891impl From<LengthPercentage> for MinTrackSizingFunction {
892    fn from(input: LengthPercentage) -> Self {
893        Self(input.0)
894    }
895}
896impl From<LengthPercentageAuto> for MinTrackSizingFunction {
897    fn from(input: LengthPercentageAuto) -> Self {
898        Self(input.0)
899    }
900}
901impl From<Dimension> for MinTrackSizingFunction {
902    fn from(input: Dimension) -> Self {
903        Self(input.0)
904    }
905}
906#[cfg(feature = "serde")]
907impl<'de> serde::Deserialize<'de> for MinTrackSizingFunction {
908    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
909    where
910        D: serde::Deserializer<'de>,
911    {
912        let inner = CompactLength::deserialize(deserializer)?;
913        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
914        if matches!(
915            inner.tag(),
916            CompactLength::LENGTH_TAG
917                | CompactLength::PERCENT_TAG
918                | CompactLength::AUTO_TAG
919                | CompactLength::MIN_CONTENT_TAG
920                | CompactLength::MAX_CONTENT_TAG
921                | CompactLength::FIT_CONTENT_PX_TAG
922                | CompactLength::FIT_CONTENT_PERCENT_TAG
923        ) {
924            Ok(Self(inner))
925        } else {
926            Err(serde::de::Error::custom("Invalid tag"))
927        }
928    }
929}
930
931impl MinTrackSizingFunction {
932    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
933    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
934    #[inline(always)]
935    pub const fn length(val: f32) -> Self {
936        Self(CompactLength::length(val))
937    }
938
939    /// A percentage length relative to the size of the containing block.
940    ///
941    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
942    #[inline(always)]
943    pub const fn percent(val: f32) -> Self {
944        Self(CompactLength::percent(val))
945    }
946
947    /// The dimension should be automatically computed according to algorithm-specific rules
948    /// regarding the default size of boxes.
949    #[inline(always)]
950    pub const fn auto() -> Self {
951        Self(CompactLength::auto())
952    }
953
954    /// The size should be the "min-content" size.
955    /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
956    #[inline(always)]
957    pub const fn min_content() -> Self {
958        Self(CompactLength::min_content())
959    }
960
961    /// The size should be the "max-content" size.
962    /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
963    #[inline(always)]
964    pub const fn max_content() -> Self {
965        Self(CompactLength::max_content())
966    }
967
968    /// A `calc()` value. The value passed here is treated as an opaque handle to
969    /// the actual calc representation and may be a pointer, index, etc.
970    ///
971    /// The low 3 bits are used as a tag value and will be returned as 0.
972    #[inline]
973    #[cfg(feature = "calc")]
974    pub fn calc(ptr: *const ()) -> Self {
975        Self(CompactLength::calc(ptr))
976    }
977
978    /// Create a LengthPercentageAuto from a raw `CompactLength`.
979    /// # Safety
980    /// CompactLength must represent a valid variant for LengthPercentageAuto
981    #[allow(unsafe_code)]
982    pub unsafe fn from_raw(val: CompactLength) -> Self {
983        Self(val)
984    }
985
986    /// Get the underlying `CompactLength` representation of the value
987    pub fn into_raw(self) -> CompactLength {
988        self.0
989    }
990
991    /// Returns true if the min track sizing function is `MinContent`, `MaxContent` or `Auto`, else false.
992    #[inline(always)]
993    pub fn is_intrinsic(&self) -> bool {
994        self.0.is_intrinsic()
995    }
996
997    /// Returns true if the min track sizing function is `MinContent` or `MaxContent`, else false.
998    #[inline(always)]
999    pub fn is_min_or_max_content(&self) -> bool {
1000        self.0.is_min_or_max_content()
1001    }
1002
1003    /// Returns true if the value is an fr value
1004    #[inline(always)]
1005    pub fn is_fr(&self) -> bool {
1006        self.0.is_fr()
1007    }
1008
1009    /// Returns true if the is `Auto`, else false.
1010    #[inline(always)]
1011    pub fn is_auto(&self) -> bool {
1012        self.0.is_auto()
1013    }
1014
1015    /// Returns true if value is MinContent
1016    #[inline(always)]
1017    pub fn is_min_content(&self) -> bool {
1018        self.0.is_min_content()
1019    }
1020
1021    /// Returns true if value is MaxContent
1022    #[inline(always)]
1023    pub fn is_max_content(&self) -> bool {
1024        self.0.is_max_content()
1025    }
1026
1027    /// Returns fixed point values directly. Attempts to resolve percentage values against
1028    /// the passed available_space and returns if this results in a concrete value (which it
1029    /// will if the available_space is `Some`). Otherwise returns `None`.
1030    #[inline(always)]
1031    pub fn definite_value(
1032        self,
1033        parent_size: Option<f32>,
1034        calc_resolver: impl Fn(*const (), f32) -> f32,
1035    ) -> Option<f32> {
1036        match self.0.tag() {
1037            CompactLength::LENGTH_TAG => Some(self.0.value()),
1038            CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1039            #[cfg(feature = "calc")]
1040            _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1041            _ => None,
1042        }
1043    }
1044
1045    /// Resolve percentage values against the passed parent_size, returning Some(value)
1046    /// Non-percentage values always return None.
1047    #[inline(always)]
1048    pub fn resolved_percentage_size(
1049        self,
1050        parent_size: f32,
1051        calc_resolver: impl Fn(*const (), f32) -> f32,
1052    ) -> Option<f32> {
1053        self.0.resolved_percentage_size(parent_size, calc_resolver)
1054    }
1055
1056    /// Whether the track sizing functions depends on the size of the parent node
1057    #[inline(always)]
1058    pub fn uses_percentage(self) -> bool {
1059        #[cfg(feature = "calc")]
1060        {
1061            matches!(self.0.tag(), CompactLength::PERCENT_TAG) || self.0.is_calc()
1062        }
1063        #[cfg(not(feature = "calc"))]
1064        {
1065            matches!(self.0.tag(), CompactLength::PERCENT_TAG)
1066        }
1067    }
1068}
1069
1070/// The sizing function for a grid track (row/column)
1071///
1072/// May either be a MinMax variant which specifies separate values for the min-/max- track sizing functions
1073/// or a scalar value which applies to both track sizing functions.
1074pub type TrackSizingFunction = MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>;
1075impl TrackSizingFunction {
1076    /// Extract the min track sizing function
1077    pub fn min_sizing_function(&self) -> MinTrackSizingFunction {
1078        self.min
1079    }
1080    /// Extract the max track sizing function
1081    pub fn max_sizing_function(&self) -> MaxTrackSizingFunction {
1082        self.max
1083    }
1084    /// Determine whether at least one of the components ("min" and "max") are fixed sizing function
1085    pub fn has_fixed_component(&self) -> bool {
1086        self.min.0.is_length_or_percentage() || self.max.0.is_length_or_percentage()
1087    }
1088}
1089impl TaffyAuto for TrackSizingFunction {
1090    const AUTO: Self = Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::AUTO };
1091}
1092impl TaffyMinContent for TrackSizingFunction {
1093    const MIN_CONTENT: Self =
1094        Self { min: MinTrackSizingFunction::MIN_CONTENT, max: MaxTrackSizingFunction::MIN_CONTENT };
1095}
1096impl TaffyMaxContent for TrackSizingFunction {
1097    const MAX_CONTENT: Self =
1098        Self { min: MinTrackSizingFunction::MAX_CONTENT, max: MaxTrackSizingFunction::MAX_CONTENT };
1099}
1100impl TaffyFitContent for TrackSizingFunction {
1101    fn fit_content(argument: LengthPercentage) -> Self {
1102        Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::fit_content(argument) }
1103    }
1104}
1105impl TaffyZero for TrackSizingFunction {
1106    const ZERO: Self = Self { min: MinTrackSizingFunction::ZERO, max: MaxTrackSizingFunction::ZERO };
1107}
1108impl FromLength for TrackSizingFunction {
1109    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
1110        Self { min: MinTrackSizingFunction::from_length(value), max: MaxTrackSizingFunction::from_length(value) }
1111    }
1112}
1113impl FromPercent for TrackSizingFunction {
1114    fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
1115        Self { min: MinTrackSizingFunction::from_percent(percent), max: MaxTrackSizingFunction::from_percent(percent) }
1116    }
1117}
1118impl FromFr for TrackSizingFunction {
1119    fn from_fr<Input: Into<f32> + Copy>(flex: Input) -> Self {
1120        Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::from_fr(flex) }
1121    }
1122}
1123impl From<LengthPercentage> for TrackSizingFunction {
1124    fn from(input: LengthPercentage) -> Self {
1125        Self { min: input.into(), max: input.into() }
1126    }
1127}
1128impl From<LengthPercentageAuto> for TrackSizingFunction {
1129    fn from(input: LengthPercentageAuto) -> Self {
1130        Self { min: input.into(), max: input.into() }
1131    }
1132}
1133impl From<Dimension> for TrackSizingFunction {
1134    fn from(input: Dimension) -> Self {
1135        Self { min: input.into(), max: input.into() }
1136    }
1137}
1138
1139/// The first argument to a repeated track definition. This type represents the type of automatic repetition to perform.
1140///
1141/// See <https://www.w3.org/TR/css-grid-1/#auto-repeat> for an explanation of how auto-repeated track definitions work
1142/// and the difference between AutoFit and AutoFill.
1143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1144#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1145pub enum RepetitionCount {
1146    /// Auto-repeating tracks should be generated to fit the container
1147    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill>
1148    AutoFill,
1149    /// Auto-repeating tracks should be generated to fit the container
1150    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit>
1151    AutoFit,
1152    /// The specified tracks should be repeated exacts N times
1153    Count(u16),
1154}
1155impl From<u16> for RepetitionCount {
1156    fn from(value: u16) -> Self {
1157        Self::Count(value)
1158    }
1159}
1160
1161/// Error returned when trying to convert a string to a GridTrackRepetition and that string is not
1162/// either "auto-fit" or "auto-fill"
1163#[derive(Debug)]
1164pub struct InvalidStringRepetitionValue;
1165#[cfg(feature = "std")]
1166impl std::error::Error for InvalidStringRepetitionValue {}
1167impl core::fmt::Display for InvalidStringRepetitionValue {
1168    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1169        f.write_str("&str can only be converted to GridTrackRepetition if it's value is 'auto-fit' or 'auto-fill'")
1170    }
1171}
1172impl TryFrom<&str> for RepetitionCount {
1173    type Error = InvalidStringRepetitionValue;
1174    fn try_from(value: &str) -> Result<Self, InvalidStringRepetitionValue> {
1175        match value {
1176            "auto-fit" => Ok(Self::AutoFit),
1177            "auto-fill" => Ok(Self::AutoFill),
1178            _ => Err(InvalidStringRepetitionValue),
1179        }
1180    }
1181}
1182
1183/// A typed representation of a `repeat(..)` in `grid-template-*` value
1184#[derive(Clone, PartialEq, Debug)]
1185#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1186pub struct GridTemplateRepetition<S: CheapCloneStr> {
1187    /// The number of the times the repeat is repeated
1188    pub count: RepetitionCount,
1189    /// The tracks to repeat
1190    pub tracks: Vec<TrackSizingFunction>,
1191    /// The line names for the repeated tracks
1192    pub line_names: Vec<Vec<S>>,
1193}
1194
1195#[rustfmt::skip]
1196impl<S: CheapCloneStr> GenericRepetition for &'_ GridTemplateRepetition<S> {
1197    type CustomIdent = S;
1198    type RepetitionTrackList<'a> = core::iter::Copied<core::slice::Iter<'a, TrackSizingFunction>> where Self: 'a;
1199    type TemplateLineNames<'a> = core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>> where Self: 'a;
1200    #[inline(always)]
1201    fn count(&self) -> RepetitionCount {
1202        self.count
1203    }
1204    #[inline(always)]
1205    fn track_count(&self) -> u16 {
1206        self.tracks.len() as u16
1207    }
1208    #[inline(always)]
1209    fn tracks(&self) -> Self::RepetitionTrackList<'_> {
1210        self.tracks.iter().copied()
1211    }
1212    #[inline(always)]
1213    fn lines_names(&self) -> Self::TemplateLineNames<'_> {
1214        self.line_names.iter().map(|names| names.iter())
1215    }
1216}
1217
1218/// An element in a `grid-template-columns` or `grid-template-rows` definition.
1219/// Either a track sizing function or a repeat().
1220///
1221/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
1222#[derive(Clone, PartialEq, Debug)]
1223#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1224pub enum GridTemplateComponent<S: CheapCloneStr> {
1225    /// A single non-repeated track
1226    Single(TrackSizingFunction),
1227    /// Automatically generate grid tracks to fit the available space using the specified definite track lengths
1228    /// Only valid if every track in template (not just the repetition) has a fixed size.
1229    Repeat(GridTemplateRepetition<S>),
1230}
1231
1232impl<S: CheapCloneStr> GridTemplateComponent<S> {
1233    /// Convert a `GridTemplateComponent` into a `GridTemplateComponentRef`
1234    pub fn as_component_ref(&self) -> GenericGridTemplateComponent<S, &GridTemplateRepetition<S>> {
1235        match self {
1236            GridTemplateComponent::Single(size) => GenericGridTemplateComponent::Single(*size),
1237            GridTemplateComponent::Repeat(repetition) => GenericGridTemplateComponent::Repeat(repetition),
1238        }
1239    }
1240}
1241
1242impl<S: CheapCloneStr> GridTemplateComponent<S> {
1243    /// Whether the track definition is a auto-repeated fragment
1244    pub fn is_auto_repetition(&self) -> bool {
1245        matches!(
1246            self,
1247            Self::Repeat(GridTemplateRepetition { count: RepetitionCount::AutoFit | RepetitionCount::AutoFill, .. })
1248        )
1249    }
1250}
1251impl<S: CheapCloneStr> TaffyAuto for GridTemplateComponent<S> {
1252    const AUTO: Self = Self::Single(TrackSizingFunction::AUTO);
1253}
1254impl<S: CheapCloneStr> TaffyMinContent for GridTemplateComponent<S> {
1255    const MIN_CONTENT: Self = Self::Single(TrackSizingFunction::MIN_CONTENT);
1256}
1257impl<S: CheapCloneStr> TaffyMaxContent for GridTemplateComponent<S> {
1258    const MAX_CONTENT: Self = Self::Single(TrackSizingFunction::MAX_CONTENT);
1259}
1260impl<S: CheapCloneStr> TaffyFitContent for GridTemplateComponent<S> {
1261    fn fit_content(argument: LengthPercentage) -> Self {
1262        Self::Single(TrackSizingFunction::fit_content(argument))
1263    }
1264}
1265impl<S: CheapCloneStr> TaffyZero for GridTemplateComponent<S> {
1266    const ZERO: Self = Self::Single(TrackSizingFunction::ZERO);
1267}
1268impl<S: CheapCloneStr> FromLength for GridTemplateComponent<S> {
1269    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
1270        Self::Single(TrackSizingFunction::from_length(value))
1271    }
1272}
1273impl<S: CheapCloneStr> FromPercent for GridTemplateComponent<S> {
1274    fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
1275        Self::Single(TrackSizingFunction::from_percent(percent))
1276    }
1277}
1278impl<S: CheapCloneStr> FromFr for GridTemplateComponent<S> {
1279    fn from_fr<Input: Into<f32> + Copy>(flex: Input) -> Self {
1280        Self::Single(TrackSizingFunction::from_fr(flex))
1281    }
1282}
1283impl<S: CheapCloneStr> From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>> for GridTemplateComponent<S> {
1284    fn from(input: MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>) -> Self {
1285        Self::Single(input)
1286    }
1287}