taffy/style/
grid.rs

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