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