tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 9TOML 3CSS 1345 Definitions

⎇ devin/1784819829-clamp-overlarge-grids src/style/grid.rs Rust · 1611 lines · 62.8 KB History Who touched what raw

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