Clamp CSS grids at 10000 tracks in each dimension
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);