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