taffy/style/
grid.rs

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