gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Definitions

⎇ 70341c9f697b610470b0806a53266aa75343563b src/style/grid.rs Rust · 1594 lines · 61.9 KB History Who touched what raw

Add support for parsing Taffy style types from string (#929) 70341c9f6 · Nico Burns ·

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