1use super::{
3 AlignContent, AlignItems, AlignSelf, CheapCloneStr, CompactLength, CoreStyle, Dimension, JustifyContent,
4 LengthPercentage, LengthPercentageAuto, Style,
5};
6use crate::compute::grid::{GridCoordinate, GridLine, OriginZeroLine, MAX_GRID_TRACKS};
7use crate::geometry::{AbsoluteAxis, AbstractAxis, Line, MinMax, Size};
8use crate::style_helpers::*;
9use crate::sys::{DefaultCheapStr, Vec};
10use core::cmp::{max, min};
11use core::fmt::Debug;
12
13#[cfg(feature = "parse")]
14use crate::util::parse::{
15 from_str_from_css, parse_css_str_entirely, CssParseResult, FromCss, ParseError, Parser, Token,
16};
17
18#[derive(Debug, Clone, PartialEq)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25pub struct GridTemplateAreas<CustomIdent: CheapCloneStr> {
26 pub areas: crate::util::sys::GridTrackVec<GridTemplateArea<CustomIdent>>,
28 pub row_count: u16,
30 pub column_count: u16,
32}
33
34#[derive(Debug, Clone, PartialEq)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37pub struct GridTemplateArea<CustomIdent: CheapCloneStr> {
38 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::util::deserialize_from_str"))]
40 pub name: CustomIdent,
41 pub row_start: u16,
43 pub row_end: u16,
45 pub column_start: u16,
47 pub column_end: u16,
49}
50
51#[derive(Debug, Clone, PartialEq)]
53#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54pub struct NamedGridLine<CustomIdent: CheapCloneStr> {
55 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::util::deserialize_from_str"))]
57 pub name: CustomIdent,
58 pub index: u16,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq)]
64pub(crate) enum GridAreaAxis {
65 Row,
67 Column,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq)]
73pub(crate) enum GridAreaEnd {
74 Start,
76 End,
78}
79
80pub trait GenericRepetition {
82 type CustomIdent: CheapCloneStr;
84 type RepetitionTrackList<'a>: Iterator<Item = TrackSizingFunction> + ExactSizeIterator + Clone
86 where
87 Self: 'a;
88
89 type TemplateLineNames<'a>: TemplateLineNames<'a, Self::CustomIdent>
91 where
92 Self: 'a;
93 fn count(&self) -> RepetitionCount;
95 fn tracks(&self) -> Self::RepetitionTrackList<'_>;
97 fn track_count(&self) -> u16 {
99 self.tracks().len().min(u16::MAX as usize) as u16
100 }
101 fn lines_names(&self) -> Self::TemplateLineNames<'_>;
103}
104
105#[rustfmt::skip]
108pub trait TemplateLineNames<'a, S: CheapCloneStr> : Iterator<Item = Self::LineNameSet<'a>> + ExactSizeIterator + Clone where Self: 'a {
109 type LineNameSet<'b>: Iterator<Item = &'b S> + ExactSizeIterator + Clone where Self: 'b;
112}
113
114impl<'a, S: CheapCloneStr> TemplateLineNames<'a, S>
115 for core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>>
116{
117 type LineNameSet<'b>
118 = core::slice::Iter<'b, S>
119 where
120 Self: 'b;
121}
122
123#[derive(Copy, Clone)]
124pub enum GenericGridTemplateComponent<S, Repetition>
127where
128 S: CheapCloneStr,
129 Repetition: GenericRepetition<CustomIdent = S>,
130{
131 Single(TrackSizingFunction),
133 Repeat(Repetition),
135}
136
137impl<S, Repetition> GenericGridTemplateComponent<S, Repetition>
138where
139 S: CheapCloneStr,
140 Repetition: GenericRepetition<CustomIdent = S>,
141{
142 pub fn is_auto_repetition(&self) -> bool {
144 match self {
145 Self::Single(_) => false,
146 Self::Repeat(repeat) => matches!(repeat.count(), RepetitionCount::AutoFit | RepetitionCount::AutoFill),
147 }
148 }
149}
150
151pub trait GridContainerStyle: CoreStyle {
153 type Repetition<'a>: GenericRepetition<CustomIdent = Self::CustomIdent>
155 where
156 Self: 'a;
157
158 type TemplateTrackList<'a>: Iterator<Item = GenericGridTemplateComponent<Self::CustomIdent, Self::Repetition<'a>>>
160 + ExactSizeIterator
161 + Clone
162 where
163 Self: 'a;
164
165 type AutoTrackList<'a>: Iterator<Item = TrackSizingFunction> + ExactSizeIterator + Clone
167 where
168 Self: 'a;
169
170 type TemplateLineNames<'a>: TemplateLineNames<'a, Self::CustomIdent>
173 where
174 Self: 'a;
175
176 type GridTemplateAreas<'a>: IntoIterator<Item = GridTemplateArea<Self::CustomIdent>>
178 where
179 Self: 'a;
180
181 fn grid_template_rows(&self) -> Option<Self::TemplateTrackList<'_>>;
186 fn grid_template_columns(&self) -> Option<Self::TemplateTrackList<'_>>;
188 fn grid_auto_rows(&self) -> Self::AutoTrackList<'_>;
190 fn grid_auto_columns(&self) -> Self::AutoTrackList<'_>;
192
193 fn grid_template_areas(&self) -> Option<Self::GridTemplateAreas<'_>>;
195 fn grid_template_area_row_count(&self) -> u16 {
198 self.grid_template_areas()
199 .map(|areas| areas.into_iter().map(|area| area.row_end.max(1) - 1).max().unwrap_or(0))
200 .unwrap_or(0)
201 }
202 fn grid_template_area_column_count(&self) -> u16 {
205 self.grid_template_areas()
206 .map(|areas| areas.into_iter().map(|area| area.column_end.max(1) - 1).max().unwrap_or(0))
207 .unwrap_or(0)
208 }
209 fn grid_template_column_names(&self) -> Option<Self::TemplateLineNames<'_>>;
211 fn grid_template_row_names(&self) -> Option<Self::TemplateLineNames<'_>>;
213
214 #[inline(always)]
216 fn grid_auto_flow(&self) -> GridAutoFlow {
217 Style::<Self::CustomIdent>::DEFAULT.grid_auto_flow
218 }
219
220 #[inline(always)]
222 fn gap(&self) -> Size<LengthPercentage> {
223 Style::<Self::CustomIdent>::DEFAULT.gap
224 }
225
226 #[inline(always)]
230 fn align_content(&self) -> Option<AlignContent> {
231 Style::<Self::CustomIdent>::DEFAULT.align_content
232 }
233 #[inline(always)]
235 fn justify_content(&self) -> Option<JustifyContent> {
236 Style::<Self::CustomIdent>::DEFAULT.justify_content
237 }
238 #[inline(always)]
240 fn align_items(&self) -> Option<AlignItems> {
241 Style::<Self::CustomIdent>::DEFAULT.align_items
242 }
243 #[inline(always)]
245 fn justify_items(&self) -> Option<AlignItems> {
246 Style::<Self::CustomIdent>::DEFAULT.justify_items
247 }
248
249 #[inline(always)]
251 fn grid_template_tracks(&self, axis: AbsoluteAxis) -> Option<Self::TemplateTrackList<'_>> {
252 match axis {
253 AbsoluteAxis::Horizontal => self.grid_template_columns(),
254 AbsoluteAxis::Vertical => self.grid_template_rows(),
255 }
256 }
257
258 #[inline(always)]
260 fn grid_align_content(&self, axis: AbstractAxis) -> AlignContent {
261 match axis {
262 AbstractAxis::Inline => self.justify_content().unwrap_or(AlignContent::STRETCH),
263 AbstractAxis::Block => self.align_content().unwrap_or(AlignContent::STRETCH),
264 }
265 }
266}
267
268pub trait GridItemStyle: CoreStyle {
270 #[inline(always)]
272 fn grid_row(&self) -> Line<GridPlacement<Self::CustomIdent>> {
273 Default::default()
274 }
275 #[inline(always)]
277 fn grid_column(&self) -> Line<GridPlacement<Self::CustomIdent>> {
278 Default::default()
279 }
280
281 #[inline(always)]
284 fn align_self(&self) -> Option<AlignSelf> {
285 Style::<Self::CustomIdent>::DEFAULT.align_self
286 }
287 #[inline(always)]
290 fn justify_self(&self) -> Option<AlignSelf> {
291 Style::<Self::CustomIdent>::DEFAULT.justify_self
292 }
293
294 #[inline(always)]
296 fn grid_placement(&self, axis: AbsoluteAxis) -> Line<GridPlacement<Self::CustomIdent>> {
297 match axis {
298 AbsoluteAxis::Horizontal => self.grid_column(),
299 AbsoluteAxis::Vertical => self.grid_row(),
300 }
301 }
302}
303
304#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
312#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
313pub enum GridAutoFlow {
314 #[default]
316 Row,
317 Column,
319 RowDense,
321 ColumnDense,
323}
324
325#[cfg(feature = "parse")]
326impl FromCss for GridAutoFlow {
327 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
328 let mut axis: Option<&'static str> = None;
329 let mut dense = false;
330
331 for _ in 0..2 {
332 if let Ok(ident) = parser.try_parse(|parser| parser.expect_ident_cloned()) {
333 match &*ident {
334 "row" => {
335 axis = Some("row");
336 }
337 "column" => {
338 axis = Some("column");
339 }
340 "dense" => dense = true,
341 _ => {
342 return Err(parser.new_unexpected_token_error(Token::Ident(ident)));
343 }
344 }
345 } else {
346 break;
347 }
348 }
349
350 match (axis, dense) {
351 (Some("row"), false) => Ok(Self::Row),
352 (Some("row") | None, true) => Ok(Self::RowDense),
353 (Some("column"), false) => Ok(Self::Column),
354 (Some("column"), true) => Ok(Self::ColumnDense),
355 (None, false) => {
356 let token = parser.next().cloned()?;
357 Err(parser.new_unexpected_token_error(token))
358 }
359 _ => unreachable!(),
360 }
361 }
362}
363#[cfg(feature = "parse")]
364from_str_from_css!(GridAutoFlow);
365
366impl GridAutoFlow {
367 pub const fn is_dense(&self) -> bool {
370 match self {
371 Self::Row | Self::Column => false,
372 Self::RowDense | Self::ColumnDense => true,
373 }
374 }
375
376 pub const fn primary_axis(&self) -> AbsoluteAxis {
379 match self {
380 Self::Row | Self::RowDense => AbsoluteAxis::Horizontal,
381 Self::Column | Self::ColumnDense => AbsoluteAxis::Vertical,
382 }
383 }
384}
385
386#[derive(Copy, Clone, PartialEq, Eq, Debug)]
392#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
393pub enum GenericGridPlacement<LineType: GridCoordinate> {
394 Auto,
396 Line(LineType),
398 Span(u16),
400}
401
402pub(crate) type OriginZeroGridPlacement = GenericGridPlacement<OriginZeroLine>;
404
405pub(crate) type NonNamedGridPlacement = GenericGridPlacement<GridLine>;
409
410#[derive(Clone, PartialEq, Debug, Default)]
416#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
417pub enum GridPlacement<S: CheapCloneStr = DefaultCheapStr> {
418 #[default]
420 Auto,
421 Line(GridLine),
423 NamedLine(S, i16),
425 Span(u16),
427 NamedSpan(S, u16),
432}
433impl<S: CheapCloneStr> TaffyAuto for GridPlacement<S> {
434 const AUTO: Self = Self::Auto;
435}
436impl<S: CheapCloneStr> TaffyGridLine for GridPlacement<S> {
437 fn from_line_index(index: i16) -> Self {
438 GridPlacement::<S>::Line(GridLine::from(index))
439 }
440}
441impl<S: CheapCloneStr> TaffyGridLine for Line<GridPlacement<S>> {
442 fn from_line_index(index: i16) -> Self {
443 Line { start: GridPlacement::<S>::from_line_index(index), end: GridPlacement::<S>::Auto }
444 }
445}
446impl<S: CheapCloneStr> TaffyGridSpan for GridPlacement<S> {
447 fn from_span(span: u16) -> Self {
448 GridPlacement::<S>::Span(span)
449 }
450}
451impl<S: CheapCloneStr> TaffyGridSpan for Line<GridPlacement<S>> {
452 fn from_span(span: u16) -> Self {
453 Line { start: GridPlacement::<S>::from_span(span), end: GridPlacement::<S>::Auto }
454 }
455}
456
457#[cfg(feature = "parse")]
458fn saturating_i16(value: i32) -> i16 {
460 value.clamp(i16::MIN as i32, i16::MAX as i32) as i16
461}
462
463#[cfg(feature = "parse")]
464fn saturating_u16(value: i32) -> u16 {
466 value.clamp(u16::MIN as i32, u16::MAX as i32) as u16
467}
468
469#[cfg(feature = "parse")]
470impl<S: CheapCloneStr> FromCss for GridPlacement<S> {
471 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
472 let mut span = false;
473 let mut number = None;
474 let mut ident = None;
475
476 while !parser.is_exhausted() {
477 let token = parser.next()?.clone();
478 match &token {
479 Token::Ident(s) => match s.as_ref() {
480 "auto" => {
481 if span || number.is_some() || ident.is_some() {
482 return Err(parser.new_unexpected_token_error(token));
483 }
484 parser.expect_exhausted()?;
485 return Ok(Self::Auto);
486 }
487 "span" => {
488 if span {
489 return Err(parser.new_unexpected_token_error(token));
490 }
491 span = true;
492 }
493 other => {
494 if ident.is_some() {
495 return Err(parser.new_unexpected_token_error(token));
496 }
497 ident = Some(S::from(other));
498 }
499 },
500 Token::Number { int_value: Some(value), .. } if *value != 0 => {
501 if number.is_some() {
502 return Err(parser.new_unexpected_token_error(token));
503 }
504 number = Some(*value);
505 }
506 _ => return Err(parser.new_unexpected_token_error(token)),
507 };
508 }
509
510 match (span, number, ident) {
511 (true, None, None) => Ok(Self::Span(0)),
512 (true, Some(number), None) => Ok(Self::Span(saturating_u16(number))),
513 (true, None, Some(ident)) => Ok(Self::NamedSpan(ident, 0)),
514 (true, Some(number), Some(ident)) => Ok(Self::NamedSpan(ident, saturating_u16(number))),
515 (false, Some(number), None) => Ok(Self::Line(GridLine::from(saturating_i16(number)))),
516 (false, Some(number), Some(ident)) => Ok(Self::NamedLine(ident, saturating_i16(number))),
517 (false, None, Some(ident)) => Ok(Self::NamedLine(ident, 0)),
518 (false, None, None) => Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput)),
519 }
520 }
521}
522
523#[cfg(feature = "parse")]
524impl<S: CheapCloneStr> core::str::FromStr for GridPlacement<S> {
525 type Err = ParseError;
526 fn from_str(input: &str) -> Result<Self, Self::Err> {
527 parse_css_str_entirely(input)
528 }
529}
530
531impl<S: CheapCloneStr> GridPlacement<S> {
532 pub fn into_origin_zero_placement_ignoring_named(&self, explicit_track_count: u16) -> OriginZeroGridPlacement {
534 match self {
535 Self::Auto => OriginZeroGridPlacement::Auto,
536 Self::Span(span) => OriginZeroGridPlacement::Span(min(*span, MAX_GRID_TRACKS)),
539 Self::Line(line) => match line.as_i16() {
542 0 => OriginZeroGridPlacement::Auto,
543 _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
544 },
545 Self::NamedLine(_, _) => OriginZeroGridPlacement::Auto,
546 Self::NamedSpan(_, _) => OriginZeroGridPlacement::Auto,
547 }
548 }
549}
550
551impl<S: CheapCloneStr> Line<GridPlacement<S>> {
552 pub fn into_origin_zero_ignoring_named(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
554 Line {
555 start: self.start.into_origin_zero_placement_ignoring_named(explicit_track_count),
556 end: self.end.into_origin_zero_placement_ignoring_named(explicit_track_count),
557 }
558 }
559}
560
561impl NonNamedGridPlacement {
562 pub fn into_origin_zero_placement(
564 &self,
565 explicit_track_count: u16,
566 ) -> OriginZeroGridPlacement {
568 match self {
569 Self::Auto => OriginZeroGridPlacement::Auto,
570 Self::Span(span) => OriginZeroGridPlacement::Span(min(*span, MAX_GRID_TRACKS)),
573 Self::Line(line) => match line.as_i16() {
576 0 => OriginZeroGridPlacement::Auto,
577 _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
578 },
579 }
580 }
581}
582
583impl<T: GridCoordinate> Line<GenericGridPlacement<T>> {
584 pub const fn indefinite_span(&self) -> u16 {
587 use GenericGridPlacement as GP;
588 match (self.start, self.end) {
589 (GP::Line(_), GP::Auto) => 1,
590 (GP::Auto, GP::Line(_)) => 1,
591 (GP::Auto, GP::Auto) => 1,
592 (GP::Line(_), GP::Span(span)) => span,
593 (GP::Span(span), GP::Line(_)) => span,
594 (GP::Span(span), GP::Auto) => span,
595 (GP::Auto, GP::Span(span)) => span,
596 (GP::Span(span), GP::Span(_)) => span,
597 (GP::Line(_), GP::Line(_)) => panic!("indefinite_span should only be called on indefinite grid tracks"),
598 }
599 }
600}
601
602impl<S: CheapCloneStr> Line<GridPlacement<S>> {
603 #[inline]
604 pub fn is_definite(&self) -> bool {
608 match (&self.start, &self.end) {
609 (GridPlacement::Line(line), _) if line.as_i16() != 0 => true,
610 (_, GridPlacement::Line(line)) if line.as_i16() != 0 => true,
611 (GridPlacement::NamedLine(_, _), _) => true,
612 (_, GridPlacement::NamedLine(_, _)) => true,
613 _ => false,
614 }
615 }
616}
617
618impl Line<NonNamedGridPlacement> {
619 #[inline]
620 pub fn is_definite(&self) -> bool {
624 match (&self.start, &self.end) {
625 (GenericGridPlacement::Line(line), _) if line.as_i16() != 0 => true,
626 (_, GenericGridPlacement::Line(line)) if line.as_i16() != 0 => true,
627 _ => false,
628 }
629 }
630
631 pub fn into_origin_zero(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
633 Line {
634 start: self.start.into_origin_zero_placement(explicit_track_count),
635 end: self.end.into_origin_zero_placement(explicit_track_count),
636 }
637 }
638}
639
640impl Line<OriginZeroGridPlacement> {
641 #[inline]
642 pub const fn is_definite(&self) -> bool {
645 matches!((self.start, self.end), (GenericGridPlacement::Line(_), _) | (_, GenericGridPlacement::Line(_)))
646 }
647
648 pub fn resolve_definite_grid_lines(&self) -> Line<OriginZeroLine> {
651 use OriginZeroGridPlacement as GP;
652 match (self.start, self.end) {
653 (GP::Line(line1), GP::Line(line2)) => {
654 if line1 == line2 {
655 Line { start: line1, end: line1 + 1 }
656 } else {
657 Line { start: min(line1, line2), end: max(line1, line2) }
658 }
659 }
660 (GP::Line(line), GP::Span(span)) => Line { start: line, end: line + span },
661 (GP::Line(line), GP::Auto) => Line { start: line, end: line + 1 },
662 (GP::Span(span), GP::Line(line)) => Line { start: line - span, end: line },
663 (GP::Auto, GP::Line(line)) => Line { start: line - 1, end: line },
664 _ => panic!("resolve_definite_grid_tracks should only be called on definite grid tracks"),
665 }
666 }
667
668 pub fn resolve_absolutely_positioned_grid_tracks(&self) -> Line<Option<OriginZeroLine>> {
678 use OriginZeroGridPlacement as GP;
679 match (self.start, self.end) {
680 (GP::Line(track1), GP::Line(track2)) => {
681 if track1 == track2 {
682 Line { start: Some(track1), end: Some(track1 + 1) }
683 } else {
684 Line { start: Some(min(track1, track2)), end: Some(max(track1, track2)) }
685 }
686 }
687 (GP::Line(track), GP::Span(span)) => Line { start: Some(track), end: Some(track + span) },
688 (GP::Line(track), GP::Auto) => Line { start: Some(track), end: None },
689 (GP::Span(span), GP::Line(track)) => Line { start: Some(track - span), end: Some(track) },
690 (GP::Auto, GP::Line(track)) => Line { start: None, end: Some(track) },
691 _ => Line { start: None, end: None },
692 }
693 }
694
695 pub fn resolve_indefinite_grid_tracks(&self, start: OriginZeroLine) -> Line<OriginZeroLine> {
698 use OriginZeroGridPlacement as GP;
699 match (self.start, self.end) {
700 (GP::Auto, GP::Auto) => Line { start, end: start + 1 },
701 (GP::Span(span), GP::Auto) => Line { start, end: start + span },
702 (GP::Auto, GP::Span(span)) => Line { start, end: start + span },
703 (GP::Span(span), GP::Span(_)) => Line { start, end: start + span },
704 _ => panic!("resolve_indefinite_grid_tracks should only be called on indefinite grid tracks"),
705 }
706 }
707}
708
709impl<S: CheapCloneStr> Default for Line<GridPlacement<S>> {
711 fn default() -> Self {
712 Line { start: GridPlacement::<S>::Auto, end: GridPlacement::<S>::Auto }
713 }
714}
715
716#[derive(Copy, Clone, PartialEq, Debug)]
722#[cfg_attr(feature = "serde", derive(Serialize))]
723pub struct MaxTrackSizingFunction(pub(crate) CompactLength);
724impl TaffyZero for MaxTrackSizingFunction {
725 const ZERO: Self = Self(CompactLength::ZERO);
726}
727impl TaffyAuto for MaxTrackSizingFunction {
728 const AUTO: Self = Self(CompactLength::AUTO);
729}
730impl TaffyMinContent for MaxTrackSizingFunction {
731 const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
732}
733impl TaffyMaxContent for MaxTrackSizingFunction {
734 const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
735}
736impl FromLength for MaxTrackSizingFunction {
737 fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
738 Self::length(value.into() as f32)
739 }
740}
741impl FromPercent for MaxTrackSizingFunction {
742 fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
743 Self::percent(value.into() as f32)
744 }
745}
746impl TaffyFitContent for MaxTrackSizingFunction {
747 fn fit_content(argument: LengthPercentage) -> Self {
748 Self(CompactLength::fit_content(argument))
749 }
750}
751impl FromFr for MaxTrackSizingFunction {
752 fn from_fr<Input: Into<f64> + Copy>(value: Input) -> Self {
753 Self::fr(value.into() as f32)
754 }
755}
756impl From<LengthPercentage> for MaxTrackSizingFunction {
757 fn from(input: LengthPercentage) -> Self {
758 Self(input.0)
759 }
760}
761impl From<LengthPercentageAuto> for MaxTrackSizingFunction {
762 fn from(input: LengthPercentageAuto) -> Self {
763 Self(input.0)
764 }
765}
766impl From<Dimension> for MaxTrackSizingFunction {
767 fn from(input: Dimension) -> Self {
768 Self(input.0)
769 }
770}
771impl From<MinTrackSizingFunction> for MaxTrackSizingFunction {
772 fn from(input: MinTrackSizingFunction) -> Self {
773 Self(input.0)
774 }
775}
776
777#[cfg(feature = "parse")]
778impl FromCss for MaxTrackSizingFunction {
779 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
780 let token = parser.next()?.clone();
781 match token {
782 Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
783 Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
784 Token::Dimension { unit, value, .. } if unit == "fr" && value.is_sign_positive() => Ok(Self::fr(value)),
785 Token::Ident(ref ident) => match ident.as_ref() {
786 "auto" => Ok(Self::auto()),
787 "min-content" => Ok(Self::min_content()),
788 "max-content" => Ok(Self::max_content()),
789 _ => Err(parser.new_unexpected_token_error(token))?,
790 },
791 Token::Function(ref name) if name.as_ref() == "fit-content" => parser.parse_nested_block(|parser| {
792 let token = parser.next()?.clone();
793 match token {
794 Token::Percentage { unit_value, .. } => Ok(Self::fit_content_percent(unit_value)),
795 Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::fit_content_px(value)),
796 token => Err(parser.new_unexpected_token_error(token))?,
797 }
798 }),
799 token => Err(parser.new_unexpected_token_error(token))?,
800 }
801 }
802}
803
804#[cfg(feature = "parse")]
805from_str_from_css!(MaxTrackSizingFunction);
806
807#[cfg(feature = "serde")]
808impl<'de> serde::Deserialize<'de> for MaxTrackSizingFunction {
809 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
810 where
811 D: serde::Deserializer<'de>,
812 {
813 let inner = CompactLength::deserialize(deserializer)?;
814 if matches!(
816 inner.tag(),
817 CompactLength::LENGTH_TAG
818 | CompactLength::PERCENT_TAG
819 | CompactLength::AUTO_TAG
820 | CompactLength::MIN_CONTENT_TAG
821 | CompactLength::MAX_CONTENT_TAG
822 | CompactLength::FIT_CONTENT_PX_TAG
823 | CompactLength::FIT_CONTENT_PERCENT_TAG
824 | CompactLength::FR_TAG
825 ) {
826 Ok(Self(inner))
827 } else {
828 Err(serde::de::Error::custom("Invalid tag"))
829 }
830 }
831}
832
833impl MaxTrackSizingFunction {
834 #[inline(always)]
837 pub const fn length(val: f32) -> Self {
838 Self(CompactLength::length(val))
839 }
840
841 #[inline(always)]
845 pub const fn percent(val: f32) -> Self {
846 Self(CompactLength::percent(val))
847 }
848
849 #[inline(always)]
852 pub const fn auto() -> Self {
853 Self(CompactLength::auto())
854 }
855
856 #[inline(always)]
859 pub const fn min_content() -> Self {
860 Self(CompactLength::min_content())
861 }
862
863 #[inline(always)]
866 pub const fn max_content() -> Self {
867 Self(CompactLength::max_content())
868 }
869
870 #[inline(always)]
880 pub const fn fit_content_px(limit: f32) -> Self {
881 Self(CompactLength::fit_content_px(limit))
882 }
883
884 #[inline(always)]
894 pub const fn fit_content_percent(limit: f32) -> Self {
895 Self(CompactLength::fit_content_percent(limit))
896 }
897
898 #[inline(always)]
902 pub const fn fr(val: f32) -> Self {
903 Self(CompactLength::fr(val))
904 }
905
906 #[inline]
911 #[cfg(feature = "calc")]
912 pub fn calc(ptr: *const ()) -> Self {
913 Self(CompactLength::calc(ptr))
914 }
915
916 #[allow(unsafe_code)]
920 pub unsafe fn from_raw(val: CompactLength) -> Self {
921 Self(val)
922 }
923
924 pub fn into_raw(self) -> CompactLength {
926 self.0
927 }
928
929 #[inline(always)]
931 pub fn is_intrinsic(&self) -> bool {
932 self.0.is_intrinsic()
933 }
934
935 #[inline(always)]
939 pub fn is_max_content_alike(&self) -> bool {
940 self.0.is_max_content_alike()
941 }
942
943 #[inline(always)]
945 pub fn is_fr(&self) -> bool {
946 self.0.is_fr()
947 }
948
949 #[inline(always)]
951 pub fn is_auto(&self) -> bool {
952 self.0.is_auto()
953 }
954
955 #[inline(always)]
957 pub fn is_min_content(&self) -> bool {
958 self.0.is_min_content()
959 }
960
961 #[inline(always)]
963 pub fn is_max_content(&self) -> bool {
964 self.0.is_max_content()
965 }
966
967 #[inline(always)]
969 pub fn is_fit_content(&self) -> bool {
970 self.0.is_fit_content()
971 }
972
973 #[inline(always)]
975 pub fn is_max_or_fit_content(&self) -> bool {
976 self.0.is_max_or_fit_content()
977 }
978
979 #[inline(always)]
981 pub fn has_definite_value(self, parent_size: Option<f32>) -> bool {
982 match self.0.tag() {
983 CompactLength::LENGTH_TAG => true,
984 CompactLength::PERCENT_TAG => parent_size.is_some(),
985 #[cfg(feature = "calc")]
986 _ if self.0.is_calc() => parent_size.is_some(),
987 _ => false,
988 }
989 }
990
991 #[inline(always)]
995 pub fn definite_value(
996 self,
997 parent_size: Option<f32>,
998 calc_resolver: impl Fn(*const (), f32) -> f32,
999 ) -> Option<f32> {
1000 match self.0.tag() {
1001 CompactLength::LENGTH_TAG => Some(self.0.value()),
1002 CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1003 #[cfg(feature = "calc")]
1004 _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1005 _ => None,
1006 }
1007 }
1008
1009 #[inline(always)]
1016 pub fn definite_limit(
1017 self,
1018 parent_size: Option<f32>,
1019 calc_resolver: impl Fn(*const (), f32) -> f32,
1020 ) -> Option<f32> {
1021 match self.0.tag() {
1022 CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
1023 CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1024 _ => self.definite_value(parent_size, calc_resolver),
1025 }
1026 }
1027
1028 #[inline(always)]
1031 pub fn resolved_percentage_size(
1032 self,
1033 parent_size: f32,
1034 calc_resolver: impl Fn(*const (), f32) -> f32,
1035 ) -> Option<f32> {
1036 self.0.resolved_percentage_size(parent_size, calc_resolver)
1037 }
1038
1039 #[inline(always)]
1041 pub fn uses_percentage(self) -> bool {
1042 self.0.uses_percentage()
1043 }
1044}
1045
1046#[derive(Copy, Clone, PartialEq, Debug)]
1052#[cfg_attr(feature = "serde", derive(Serialize))]
1053pub struct MinTrackSizingFunction(pub(crate) CompactLength);
1054impl TaffyZero for MinTrackSizingFunction {
1055 const ZERO: Self = Self(CompactLength::ZERO);
1056}
1057impl TaffyAuto for MinTrackSizingFunction {
1058 const AUTO: Self = Self(CompactLength::AUTO);
1059}
1060impl TaffyMinContent for MinTrackSizingFunction {
1061 const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
1062}
1063impl TaffyMaxContent for MinTrackSizingFunction {
1064 const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
1065}
1066impl FromLength for MinTrackSizingFunction {
1067 fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1068 Self::length(value.into() as f32)
1069 }
1070}
1071impl FromPercent for MinTrackSizingFunction {
1072 fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
1073 Self::percent(value.into() as f32)
1074 }
1075}
1076impl From<LengthPercentage> for MinTrackSizingFunction {
1077 fn from(input: LengthPercentage) -> Self {
1078 Self(input.0)
1079 }
1080}
1081impl From<LengthPercentageAuto> for MinTrackSizingFunction {
1082 fn from(input: LengthPercentageAuto) -> Self {
1083 Self(input.0)
1084 }
1085}
1086impl From<Dimension> for MinTrackSizingFunction {
1087 fn from(input: Dimension) -> Self {
1088 Self(input.0)
1089 }
1090}
1091
1092impl From<MaxTrackSizingFunction> for MinTrackSizingFunction {
1093 fn from(input: MaxTrackSizingFunction) -> Self {
1094 if input.is_fr() || input.is_fit_content() {
1095 return Self::auto();
1096 }
1097 Self(input.0)
1098 }
1099}
1100
1101#[cfg(feature = "parse")]
1102impl FromCss for MinTrackSizingFunction {
1103 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1104 let token = parser.next()?.clone();
1105 match token {
1106 Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
1107 Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
1108 Token::Ident(ref ident) => match ident.as_ref() {
1109 "auto" => Ok(Self::auto()),
1110 "min-content" => Ok(Self::min_content()),
1111 "max-content" => Ok(Self::max_content()),
1112 _ => Err(parser.new_unexpected_token_error(token))?,
1113 },
1114 token => Err(parser.new_unexpected_token_error(token))?,
1115 }
1116 }
1117}
1118
1119#[cfg(feature = "parse")]
1120from_str_from_css!(MinTrackSizingFunction);
1121
1122#[cfg(feature = "serde")]
1123impl<'de> serde::Deserialize<'de> for MinTrackSizingFunction {
1124 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1125 where
1126 D: serde::Deserializer<'de>,
1127 {
1128 let inner = CompactLength::deserialize(deserializer)?;
1129 if matches!(
1131 inner.tag(),
1132 CompactLength::LENGTH_TAG
1133 | CompactLength::PERCENT_TAG
1134 | CompactLength::AUTO_TAG
1135 | CompactLength::MIN_CONTENT_TAG
1136 | CompactLength::MAX_CONTENT_TAG
1137 | CompactLength::FIT_CONTENT_PX_TAG
1138 | CompactLength::FIT_CONTENT_PERCENT_TAG
1139 ) {
1140 Ok(Self(inner))
1141 } else {
1142 Err(serde::de::Error::custom("Invalid tag"))
1143 }
1144 }
1145}
1146
1147impl MinTrackSizingFunction {
1148 #[inline(always)]
1151 pub const fn length(val: f32) -> Self {
1152 Self(CompactLength::length(val))
1153 }
1154
1155 #[inline(always)]
1159 pub const fn percent(val: f32) -> Self {
1160 Self(CompactLength::percent(val))
1161 }
1162
1163 #[inline(always)]
1166 pub const fn auto() -> Self {
1167 Self(CompactLength::auto())
1168 }
1169
1170 #[inline(always)]
1173 pub const fn min_content() -> Self {
1174 Self(CompactLength::min_content())
1175 }
1176
1177 #[inline(always)]
1180 pub const fn max_content() -> Self {
1181 Self(CompactLength::max_content())
1182 }
1183
1184 #[inline]
1189 #[cfg(feature = "calc")]
1190 pub fn calc(ptr: *const ()) -> Self {
1191 Self(CompactLength::calc(ptr))
1192 }
1193
1194 #[allow(unsafe_code)]
1198 pub unsafe fn from_raw(val: CompactLength) -> Self {
1199 Self(val)
1200 }
1201
1202 pub fn into_raw(self) -> CompactLength {
1204 self.0
1205 }
1206
1207 #[inline(always)]
1209 pub fn is_intrinsic(&self) -> bool {
1210 self.0.is_intrinsic()
1211 }
1212
1213 #[inline(always)]
1215 pub fn is_min_or_max_content(&self) -> bool {
1216 self.0.is_min_or_max_content()
1217 }
1218
1219 #[inline(always)]
1221 pub fn is_fr(&self) -> bool {
1222 self.0.is_fr()
1223 }
1224
1225 #[inline(always)]
1227 pub fn is_auto(&self) -> bool {
1228 self.0.is_auto()
1229 }
1230
1231 #[inline(always)]
1233 pub fn is_min_content(&self) -> bool {
1234 self.0.is_min_content()
1235 }
1236
1237 #[inline(always)]
1239 pub fn is_max_content(&self) -> bool {
1240 self.0.is_max_content()
1241 }
1242
1243 #[inline(always)]
1247 pub fn definite_value(
1248 self,
1249 parent_size: Option<f32>,
1250 calc_resolver: impl Fn(*const (), f32) -> f32,
1251 ) -> Option<f32> {
1252 match self.0.tag() {
1253 CompactLength::LENGTH_TAG => Some(self.0.value()),
1254 CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1255 #[cfg(feature = "calc")]
1256 _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1257 _ => None,
1258 }
1259 }
1260
1261 #[inline(always)]
1264 pub fn resolved_percentage_size(
1265 self,
1266 parent_size: f32,
1267 calc_resolver: impl Fn(*const (), f32) -> f32,
1268 ) -> Option<f32> {
1269 self.0.resolved_percentage_size(parent_size, calc_resolver)
1270 }
1271
1272 #[inline(always)]
1274 pub fn uses_percentage(self) -> bool {
1275 #[cfg(feature = "calc")]
1276 {
1277 matches!(self.0.tag(), CompactLength::PERCENT_TAG) || self.0.is_calc()
1278 }
1279 #[cfg(not(feature = "calc"))]
1280 {
1281 matches!(self.0.tag(), CompactLength::PERCENT_TAG)
1282 }
1283 }
1284}
1285
1286pub type TrackSizingFunction = MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>;
1291impl TrackSizingFunction {
1292 pub fn min_sizing_function(&self) -> MinTrackSizingFunction {
1294 self.min
1295 }
1296 pub fn max_sizing_function(&self) -> MaxTrackSizingFunction {
1298 self.max
1299 }
1300 pub fn has_fixed_component(&self) -> bool {
1302 self.min.0.is_length_or_percentage() || self.max.0.is_length_or_percentage()
1303 }
1304}
1305impl TaffyAuto for TrackSizingFunction {
1306 const AUTO: Self = Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::AUTO };
1307}
1308impl TaffyMinContent for TrackSizingFunction {
1309 const MIN_CONTENT: Self =
1310 Self { min: MinTrackSizingFunction::MIN_CONTENT, max: MaxTrackSizingFunction::MIN_CONTENT };
1311}
1312impl TaffyMaxContent for TrackSizingFunction {
1313 const MAX_CONTENT: Self =
1314 Self { min: MinTrackSizingFunction::MAX_CONTENT, max: MaxTrackSizingFunction::MAX_CONTENT };
1315}
1316impl TaffyFitContent for TrackSizingFunction {
1317 fn fit_content(argument: LengthPercentage) -> Self {
1318 Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::fit_content(argument) }
1319 }
1320}
1321impl TaffyZero for TrackSizingFunction {
1322 const ZERO: Self = Self { min: MinTrackSizingFunction::ZERO, max: MaxTrackSizingFunction::ZERO };
1323}
1324impl FromLength for TrackSizingFunction {
1325 fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1326 Self { min: MinTrackSizingFunction::from_length(value), max: MaxTrackSizingFunction::from_length(value) }
1327 }
1328}
1329impl FromPercent for TrackSizingFunction {
1330 fn from_percent<Input: Into<f64> + Copy>(percent: Input) -> Self {
1331 Self { min: MinTrackSizingFunction::from_percent(percent), max: MaxTrackSizingFunction::from_percent(percent) }
1332 }
1333}
1334impl FromFr for TrackSizingFunction {
1335 fn from_fr<Input: Into<f64> + Copy>(flex: Input) -> Self {
1336 Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::from_fr(flex) }
1337 }
1338}
1339impl From<LengthPercentage> for TrackSizingFunction {
1340 fn from(input: LengthPercentage) -> Self {
1341 Self { min: input.into(), max: input.into() }
1342 }
1343}
1344impl From<LengthPercentageAuto> for TrackSizingFunction {
1345 fn from(input: LengthPercentageAuto) -> Self {
1346 Self { min: input.into(), max: input.into() }
1347 }
1348}
1349impl From<Dimension> for TrackSizingFunction {
1350 fn from(input: Dimension) -> Self {
1351 Self { min: input.into(), max: input.into() }
1352 }
1353}
1354
1355#[cfg(feature = "parse")]
1356impl FromCss for TrackSizingFunction {
1357 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1358 if let Ok(value) = parser.try_parse(|parser| {
1360 parser.expect_function_matching("minmax")?;
1361 parser.parse_nested_block(|parser| {
1362 let min = MinTrackSizingFunction::from_css(parser)?;
1363 parser.expect_comma()?;
1364 let max = MaxTrackSizingFunction::from_css(parser)?;
1365
1366 Ok(Self { min, max })
1367 })
1368 }) {
1369 return Ok(value);
1370 }
1371
1372 let max = MaxTrackSizingFunction::from_css(parser)?;
1374 let min = max.into();
1375 Ok(Self { min, max })
1376 }
1377}
1378
1379#[cfg(feature = "parse")]
1380from_str_from_css!(TrackSizingFunction);
1381
1382#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1387#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1388pub enum RepetitionCount {
1389 AutoFill,
1392 AutoFit,
1395 Count(u16),
1397}
1398impl From<u16> for RepetitionCount {
1399 fn from(value: u16) -> Self {
1400 Self::Count(value)
1401 }
1402}
1403
1404#[derive(Debug)]
1407pub struct InvalidStringRepetitionValue;
1408#[cfg(feature = "std")]
1409impl std::error::Error for InvalidStringRepetitionValue {}
1410impl core::fmt::Display for InvalidStringRepetitionValue {
1411 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1412 f.write_str("&str can only be converted to GridTrackRepetition if it's value is 'auto-fit' or 'auto-fill'")
1413 }
1414}
1415impl TryFrom<&str> for RepetitionCount {
1416 type Error = InvalidStringRepetitionValue;
1417 fn try_from(value: &str) -> Result<Self, InvalidStringRepetitionValue> {
1418 match value {
1419 "auto-fit" => Ok(Self::AutoFit),
1420 "auto-fill" => Ok(Self::AutoFill),
1421 _ => Err(InvalidStringRepetitionValue),
1422 }
1423 }
1424}
1425
1426#[cfg(feature = "parse")]
1427impl FromCss for RepetitionCount {
1428 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1429 match parser.next()?.clone() {
1430 Token::Number { int_value: Some(value), .. } if value.is_positive() => {
1431 Ok(Self::Count(saturating_u16(value)))
1432 }
1433 Token::Ident(ident) if ident == "auto-fit" => Ok(Self::AutoFit),
1434 Token::Ident(ident) if ident == "auto-fill" => Ok(Self::AutoFill),
1435 token => Err(parser.new_unexpected_token_error(token))?,
1436 }
1437 }
1438}
1439#[cfg(feature = "parse")]
1440from_str_from_css!(RepetitionCount);
1441
1442#[derive(Clone, PartialEq, Debug)]
1444#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1445pub struct GridTemplateRepetition<S: CheapCloneStr> {
1446 pub count: RepetitionCount,
1448 pub tracks: Vec<TrackSizingFunction>,
1450 pub line_names: Vec<Vec<S>>,
1452}
1453
1454#[rustfmt::skip]
1455impl<S: CheapCloneStr> GenericRepetition for &'_ GridTemplateRepetition<S> {
1456 type CustomIdent = S;
1457 type RepetitionTrackList<'a> = core::iter::Copied<core::slice::Iter<'a, TrackSizingFunction>> where Self: 'a;
1458 type TemplateLineNames<'a> = core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>> where Self: 'a;
1459 #[inline(always)]
1460 fn count(&self) -> RepetitionCount {
1461 self.count
1462 }
1463 #[inline(always)]
1464 fn track_count(&self) -> u16 {
1465 self.tracks.len().min(u16::MAX as usize) as u16
1466 }
1467 #[inline(always)]
1468 fn tracks(&self) -> Self::RepetitionTrackList<'_> {
1469 self.tracks.iter().copied()
1470 }
1471 #[inline(always)]
1472 fn lines_names(&self) -> Self::TemplateLineNames<'_> {
1473 self.line_names.iter().map(|names| names.iter())
1474 }
1475}
1476
1477#[derive(Clone, PartialEq, Debug)]
1482#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1483pub enum GridTemplateComponent<S: CheapCloneStr> {
1484 Single(TrackSizingFunction),
1486 Repeat(GridTemplateRepetition<S>),
1489}
1490
1491impl<S: CheapCloneStr> GridTemplateComponent<S> {
1492 pub fn as_component_ref(&self) -> GenericGridTemplateComponent<S, &GridTemplateRepetition<S>> {
1494 match self {
1495 GridTemplateComponent::Single(size) => GenericGridTemplateComponent::Single(*size),
1496 GridTemplateComponent::Repeat(repetition) => GenericGridTemplateComponent::Repeat(repetition),
1497 }
1498 }
1499}
1500
1501impl<S: CheapCloneStr> GridTemplateComponent<S> {
1502 pub fn is_auto_repetition(&self) -> bool {
1504 matches!(
1505 self,
1506 Self::Repeat(GridTemplateRepetition { count: RepetitionCount::AutoFit | RepetitionCount::AutoFill, .. })
1507 )
1508 }
1509}
1510impl<S: CheapCloneStr> TaffyAuto for GridTemplateComponent<S> {
1511 const AUTO: Self = Self::Single(TrackSizingFunction::AUTO);
1512}
1513impl<S: CheapCloneStr> TaffyMinContent for GridTemplateComponent<S> {
1514 const MIN_CONTENT: Self = Self::Single(TrackSizingFunction::MIN_CONTENT);
1515}
1516impl<S: CheapCloneStr> TaffyMaxContent for GridTemplateComponent<S> {
1517 const MAX_CONTENT: Self = Self::Single(TrackSizingFunction::MAX_CONTENT);
1518}
1519impl<S: CheapCloneStr> TaffyFitContent for GridTemplateComponent<S> {
1520 fn fit_content(argument: LengthPercentage) -> Self {
1521 Self::Single(TrackSizingFunction::fit_content(argument))
1522 }
1523}
1524impl<S: CheapCloneStr> TaffyZero for GridTemplateComponent<S> {
1525 const ZERO: Self = Self::Single(TrackSizingFunction::ZERO);
1526}
1527impl<S: CheapCloneStr> FromLength for GridTemplateComponent<S> {
1528 fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1529 Self::Single(TrackSizingFunction::from_length(value))
1530 }
1531}
1532impl<S: CheapCloneStr> FromPercent for GridTemplateComponent<S> {
1533 fn from_percent<Input: Into<f64> + Copy>(percent: Input) -> Self {
1534 Self::Single(TrackSizingFunction::from_percent(percent))
1535 }
1536}
1537impl<S: CheapCloneStr> FromFr for GridTemplateComponent<S> {
1538 fn from_fr<Input: Into<f64> + Copy>(flex: Input) -> Self {
1539 Self::Single(TrackSizingFunction::from_fr(flex))
1540 }
1541}
1542impl<S: CheapCloneStr> From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>> for GridTemplateComponent<S> {
1543 fn from(input: MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>) -> Self {
1544 Self::Single(input)
1545 }
1546}
1547
1548#[cfg(feature = "parse")]
1549impl<S: CheapCloneStr> FromCss for GridTemplateComponent<S> {
1550 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1551 if let Ok(value) = parser.try_parse(|parser| {
1553 parser.expect_function_matching("repeat")?;
1554 parser.parse_nested_block(|parser| {
1555 let count = RepetitionCount::from_css(parser)?;
1556 parser.expect_comma()?;
1557 let tracks = GridTemplateTracks::<S, TrackSizingFunction>::from_css(parser)?;
1558
1559 Ok(Self::Repeat(GridTemplateRepetition { count, tracks: tracks.tracks, line_names: tracks.line_names }))
1560 })
1561 }) {
1562 return Ok(value);
1563 }
1564
1565 let track_sizing_function = TrackSizingFunction::from_css(parser)?;
1567 Ok(Self::Single(track_sizing_function))
1568 }
1569}
1570#[cfg(feature = "parse")]
1571impl<S: CheapCloneStr> core::str::FromStr for GridTemplateComponent<S> {
1572 type Err = ParseError;
1573 fn from_str(input: &str) -> Result<Self, Self::Err> {
1574 parse_css_str_entirely(input)
1575 }
1576}
1577
1578#[derive(Clone, PartialEq, Debug)]
1579#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1580#[doc(hidden)]
1581pub struct GridTemplateTracks<S: CheapCloneStr, Track> {
1582 pub tracks: Vec<Track>,
1584 pub line_names: Vec<Vec<S>>,
1586}
1587
1588impl<S: CheapCloneStr, Track> Default for GridTemplateTracks<S, Track> {
1589 fn default() -> Self {
1590 Self { tracks: Vec::new(), line_names: Vec::new() }
1591 }
1592}
1593
1594#[cfg(feature = "parse")]
1595impl<S: CheapCloneStr, Track: FromCss + Debug> FromCss for GridTemplateTracks<S, Track> {
1596 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1597 fn try_parse_line_names<'i, S: CheapCloneStr>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Vec<S>> {
1598 parser.try_parse(|parser| {
1599 parser.expect_square_bracket_block()?;
1600 parser.parse_nested_block(|parser| {
1601 let mut line_names = Vec::new();
1602 while !parser.is_exhausted() {
1603 line_names.push(S::from(parser.expect_ident_cloned()?.as_ref()));
1604 }
1605 Ok(line_names)
1606 })
1607 })
1608 }
1609
1610 let mut tracks = Self::default();
1611 if let Ok(line_names) = try_parse_line_names(parser) {
1612 tracks.line_names.push(line_names);
1613 }
1614
1615 while !parser.is_exhausted() {
1616 tracks.tracks.push(Track::from_css(parser)?);
1617 if let Ok(line_names) = try_parse_line_names(parser) {
1618 tracks.line_names.push(line_names);
1619 }
1620 }
1621
1622 if tracks.tracks.is_empty() {
1623 return Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput));
1624 }
1625
1626 Ok(tracks)
1627 }
1628}
1629#[cfg(feature = "parse")]
1630impl<S: CheapCloneStr, Track: FromCss + Debug> core::str::FromStr for GridTemplateTracks<S, Track> {
1631 type Err = ParseError;
1632 fn from_str(input: &str) -> Result<Self, Self::Err> {
1633 parse_css_str_entirely(input)
1634 }
1635}
1636
1637#[derive(Default)]
1638#[doc(hidden)]
1639pub struct GridAutoTracks(pub Vec<TrackSizingFunction>);
1640
1641#[cfg(feature = "parse")]
1642impl FromCss for GridAutoTracks {
1643 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1644 let mut tracks = Self::default();
1645 while !parser.is_exhausted() {
1646 tracks.0.push(TrackSizingFunction::from_css(parser)?);
1647 }
1648 if tracks.0.is_empty() {
1649 return Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput));
1650 }
1651 Ok(tracks)
1652 }
1653}
1654#[cfg(feature = "parse")]
1655from_str_from_css!(GridAutoTracks);
1656
1657#[cfg(all(test, feature = "parse"))]
1658mod tests {
1659 use super::*;
1660 use crate::sys::DefaultCheapStr;
1661
1662 #[test]
1663 fn grid_placement_parser_saturates_numeric_values() {
1664 assert_eq!(
1665 "32768".parse::<GridPlacement<DefaultCheapStr>>().unwrap(),
1666 GridPlacement::Line(GridLine::from(i16::MAX))
1667 );
1668 assert_eq!(
1669 "-32769".parse::<GridPlacement<DefaultCheapStr>>().unwrap(),
1670 GridPlacement::Line(GridLine::from(i16::MIN))
1671 );
1672 assert_eq!("span 65536".parse::<GridPlacement<DefaultCheapStr>>().unwrap(), GridPlacement::Span(u16::MAX));
1673
1674 let named_line = "32768 line".parse::<GridPlacement<DefaultCheapStr>>().unwrap();
1675 assert!(matches!(named_line, GridPlacement::NamedLine(_, i16::MAX)));
1676
1677 let named_span = "span 65536 line".parse::<GridPlacement<DefaultCheapStr>>().unwrap();
1678 assert!(matches!(named_span, GridPlacement::NamedSpan(_, u16::MAX)));
1679 }
1680
1681 #[test]
1682 fn repetition_parser_saturates_numeric_values() {
1683 assert_eq!("65536".parse::<RepetitionCount>().unwrap(), RepetitionCount::Count(u16::MAX));
1684
1685 let component = "repeat(65536, 1px)".parse::<GridTemplateComponent<DefaultCheapStr>>().unwrap();
1686 assert!(matches!(
1687 component,
1688 GridTemplateComponent::Repeat(GridTemplateRepetition { count: RepetitionCount::Count(u16::MAX), .. })
1689 ));
1690 }
1691
1692 #[test]
1693 fn repetition_track_count_saturates() {
1694 let repetition = GridTemplateRepetition::<DefaultCheapStr> {
1695 count: RepetitionCount::Count(1),
1696 tracks: vec![TrackSizingFunction::AUTO; u16::MAX as usize + 1],
1697 line_names: Vec::new(),
1698 };
1699 assert_eq!((&repetition).track_count(), u16::MAX);
1700 }
1701}