Skip to main content

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