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<'_>;
107}
108
109#[rustfmt::skip]
112pub trait TemplateLineNames<'a, S: CheapCloneStr> : Iterator<Item = Self::LineNameSet<'a>> + ExactSizeIterator + Clone where Self: 'a {
113 type LineNameSet<'b>: Iterator<Item = &'b S> + ExactSizeIterator + Clone where Self: 'b;
116}
117
118impl<'a, S: CheapCloneStr> TemplateLineNames<'a, S>
119 for core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>>
120{
121 type LineNameSet<'b>
122 = core::slice::Iter<'b, S>
123 where
124 Self: 'b;
125}
126
127#[derive(Copy, Clone)]
128pub enum GenericGridTemplateComponent<S, Repetition>
131where
132 S: CheapCloneStr,
133 Repetition: GenericRepetition<CustomIdent = S>,
134{
135 Single(TrackSizingFunction),
137 Repeat(Repetition),
139}
140
141impl<S, Repetition> GenericGridTemplateComponent<S, Repetition>
142where
143 S: CheapCloneStr,
144 Repetition: GenericRepetition<CustomIdent = S>,
145{
146 pub fn is_auto_repetition(&self) -> bool {
148 match self {
149 Self::Single(_) => false,
150 Self::Repeat(repeat) => matches!(repeat.count(), RepetitionCount::AutoFit | RepetitionCount::AutoFill),
151 }
152 }
153}
154
155pub trait GridContainerStyle: CoreStyle {
157 type Repetition<'a>: GenericRepetition<CustomIdent = Self::CustomIdent>
159 where
160 Self: 'a;
161
162 type TemplateTrackList<'a>: Iterator<Item = GenericGridTemplateComponent<Self::CustomIdent, Self::Repetition<'a>>>
164 + ExactSizeIterator
165 + Clone
166 where
167 Self: 'a;
168
169 type AutoTrackList<'a>: Iterator<Item = TrackSizingFunction> + ExactSizeIterator + Clone
171 where
172 Self: 'a;
173
174 type TemplateLineNames<'a>: TemplateLineNames<'a, Self::CustomIdent>
177 where
178 Self: 'a;
179
180 type GridTemplateAreas<'a>: IntoIterator<Item = GridTemplateArea<Self::CustomIdent>>
182 where
183 Self: 'a;
184
185 fn grid_template_rows(&self) -> Option<Self::TemplateTrackList<'_>>;
190 fn grid_template_columns(&self) -> Option<Self::TemplateTrackList<'_>>;
192 fn grid_auto_rows(&self) -> Self::AutoTrackList<'_>;
194 fn grid_auto_columns(&self) -> Self::AutoTrackList<'_>;
196
197 fn grid_template_areas(&self) -> Option<Self::GridTemplateAreas<'_>>;
199 fn grid_template_area_row_count(&self) -> u16 {
202 self.grid_template_areas()
203 .map(|areas| areas.into_iter().map(|area| area.row_end.max(1) - 1).max().unwrap_or(0))
204 .unwrap_or(0)
205 }
206 fn grid_template_area_column_count(&self) -> u16 {
209 self.grid_template_areas()
210 .map(|areas| areas.into_iter().map(|area| area.column_end.max(1) - 1).max().unwrap_or(0))
211 .unwrap_or(0)
212 }
213 fn grid_template_column_names(&self) -> Option<Self::TemplateLineNames<'_>>;
215 fn grid_template_row_names(&self) -> Option<Self::TemplateLineNames<'_>>;
217
218 #[inline(always)]
220 fn grid_auto_flow(&self) -> GridAutoFlow {
221 Style::<Self::CustomIdent>::DEFAULT.grid_auto_flow
222 }
223
224 #[inline(always)]
226 fn gap(&self) -> Size<LengthPercentage> {
227 Style::<Self::CustomIdent>::DEFAULT.gap
228 }
229
230 #[inline(always)]
234 fn align_content(&self) -> Option<AlignContent> {
235 Style::<Self::CustomIdent>::DEFAULT.align_content
236 }
237 #[inline(always)]
239 fn justify_content(&self) -> Option<JustifyContent> {
240 Style::<Self::CustomIdent>::DEFAULT.justify_content
241 }
242 #[inline(always)]
244 fn align_items(&self) -> Option<AlignItems> {
245 Style::<Self::CustomIdent>::DEFAULT.align_items
246 }
247 #[inline(always)]
249 fn justify_items(&self) -> Option<AlignItems> {
250 Style::<Self::CustomIdent>::DEFAULT.justify_items
251 }
252
253 #[inline(always)]
255 fn grid_template_tracks(&self, axis: AbsoluteAxis) -> Option<Self::TemplateTrackList<'_>> {
256 match axis {
257 AbsoluteAxis::Horizontal => self.grid_template_columns(),
258 AbsoluteAxis::Vertical => self.grid_template_rows(),
259 }
260 }
261
262 #[inline(always)]
264 fn grid_align_content(&self, axis: AbstractAxis) -> AlignContent {
265 match axis {
266 AbstractAxis::Inline => self.justify_content().unwrap_or(AlignContent::STRETCH),
267 AbstractAxis::Block => self.align_content().unwrap_or(AlignContent::STRETCH),
268 }
269 }
270}
271
272pub trait GridItemStyle: CoreStyle {
274 #[inline(always)]
276 fn grid_row(&self) -> Line<GridPlacement<Self::CustomIdent>> {
277 Default::default()
278 }
279 #[inline(always)]
281 fn grid_column(&self) -> Line<GridPlacement<Self::CustomIdent>> {
282 Default::default()
283 }
284
285 #[inline(always)]
288 fn align_self(&self) -> Option<AlignSelf> {
289 Style::<Self::CustomIdent>::DEFAULT.align_self
290 }
291 #[inline(always)]
294 fn justify_self(&self) -> Option<AlignSelf> {
295 Style::<Self::CustomIdent>::DEFAULT.justify_self
296 }
297
298 #[inline(always)]
300 fn grid_placement(&self, axis: AbsoluteAxis) -> Line<GridPlacement<Self::CustomIdent>> {
301 match axis {
302 AbsoluteAxis::Horizontal => self.grid_column(),
303 AbsoluteAxis::Vertical => self.grid_row(),
304 }
305 }
306}
307
308#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
316#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
317pub enum GridAutoFlow {
318 #[default]
320 Row,
321 Column,
323 RowDense,
325 ColumnDense,
327}
328
329#[cfg(feature = "parse")]
330impl FromCss for GridAutoFlow {
331 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
332 let mut axis: Option<&'static str> = None;
333 let mut dense = false;
334
335 for _ in 0..2 {
336 if let Ok(ident) = parser.try_parse(|parser| parser.expect_ident_cloned()) {
337 match &*ident {
338 "row" => {
339 axis = Some("row");
340 }
341 "column" => {
342 axis = Some("column");
343 }
344 "dense" => dense = true,
345 _ => {
346 return Err(parser.new_unexpected_token_error(Token::Ident(ident)));
347 }
348 }
349 } else {
350 break;
351 }
352 }
353
354 match (axis, dense) {
355 (Some("row"), false) => Ok(Self::Row),
356 (Some("row") | None, true) => Ok(Self::RowDense),
357 (Some("column"), false) => Ok(Self::Column),
358 (Some("column"), true) => Ok(Self::ColumnDense),
359 (None, false) => {
360 let token = parser.next().cloned()?;
361 Err(parser.new_unexpected_token_error(token))
362 }
363 _ => unreachable!(),
364 }
365 }
366}
367#[cfg(feature = "parse")]
368from_str_from_css!(GridAutoFlow);
369
370impl GridAutoFlow {
371 pub const fn is_dense(&self) -> bool {
374 match self {
375 Self::Row | Self::Column => false,
376 Self::RowDense | Self::ColumnDense => true,
377 }
378 }
379
380 pub const fn primary_axis(&self) -> AbsoluteAxis {
383 match self {
384 Self::Row | Self::RowDense => AbsoluteAxis::Horizontal,
385 Self::Column | Self::ColumnDense => AbsoluteAxis::Vertical,
386 }
387 }
388}
389
390#[derive(Copy, Clone, PartialEq, Eq, Debug)]
396#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
397pub enum GenericGridPlacement<LineType: GridCoordinate> {
398 Auto,
400 Line(LineType),
402 Span(u16),
404}
405
406pub(crate) type OriginZeroGridPlacement = GenericGridPlacement<OriginZeroLine>;
408
409pub(crate) type NonNamedGridPlacement = GenericGridPlacement<GridLine>;
413
414#[derive(Clone, PartialEq, Debug, Default)]
420#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
421pub enum GridPlacement<S: CheapCloneStr = DefaultCheapStr> {
422 #[default]
424 Auto,
425 Line(GridLine),
427 NamedLine(S, i16),
429 Span(u16),
431 NamedSpan(S, u16),
436}
437impl<S: CheapCloneStr> TaffyAuto for GridPlacement<S> {
438 const AUTO: Self = Self::Auto;
439}
440impl<S: CheapCloneStr> TaffyGridLine for GridPlacement<S> {
441 fn from_line_index(index: i16) -> Self {
442 GridPlacement::<S>::Line(GridLine::from(index))
443 }
444}
445impl<S: CheapCloneStr> TaffyGridLine for Line<GridPlacement<S>> {
446 fn from_line_index(index: i16) -> Self {
447 Line { start: GridPlacement::<S>::from_line_index(index), end: GridPlacement::<S>::Auto }
448 }
449}
450impl<S: CheapCloneStr> TaffyGridSpan for GridPlacement<S> {
451 fn from_span(span: u16) -> Self {
452 GridPlacement::<S>::Span(span)
453 }
454}
455impl<S: CheapCloneStr> TaffyGridSpan for Line<GridPlacement<S>> {
456 fn from_span(span: u16) -> Self {
457 Line { start: GridPlacement::<S>::from_span(span), end: GridPlacement::<S>::Auto }
458 }
459}
460
461#[cfg(feature = "parse")]
462fn saturating_i16(value: i32) -> i16 {
464 value.clamp(i16::MIN as i32, i16::MAX as i32) as i16
465}
466
467#[cfg(feature = "parse")]
468fn saturating_u16(value: i32) -> u16 {
470 value.clamp(u16::MIN as i32, u16::MAX as i32) as u16
471}
472
473#[cfg(feature = "parse")]
474impl<S: CheapCloneStr> FromCss for GridPlacement<S> {
475 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
476 let mut span = false;
477 let mut number = None;
478 let mut ident = None;
479
480 while !parser.is_exhausted() {
481 let token = parser.next()?.clone();
482 match &token {
483 Token::Ident(s) => match s.as_ref() {
484 "auto" => {
485 if span || number.is_some() || ident.is_some() {
486 return Err(parser.new_unexpected_token_error(token));
487 }
488 parser.expect_exhausted()?;
489 return Ok(Self::Auto);
490 }
491 "span" => {
492 if span {
493 return Err(parser.new_unexpected_token_error(token));
494 }
495 span = true;
496 }
497 other => {
498 if ident.is_some() {
499 return Err(parser.new_unexpected_token_error(token));
500 }
501 ident = Some(S::from(other));
502 }
503 },
504 Token::Number { int_value: Some(value), .. } if *value != 0 => {
505 if number.is_some() {
506 return Err(parser.new_unexpected_token_error(token));
507 }
508 number = Some(*value);
509 }
510 _ => return Err(parser.new_unexpected_token_error(token)),
511 };
512 }
513
514 match (span, number, ident) {
515 (true, None, None) => Ok(Self::Span(0)),
516 (true, Some(number), None) => Ok(Self::Span(saturating_u16(number))),
517 (true, None, Some(ident)) => Ok(Self::NamedSpan(ident, 0)),
518 (true, Some(number), Some(ident)) => Ok(Self::NamedSpan(ident, saturating_u16(number))),
519 (false, Some(number), None) => Ok(Self::Line(GridLine::from(saturating_i16(number)))),
520 (false, Some(number), Some(ident)) => Ok(Self::NamedLine(ident, saturating_i16(number))),
521 (false, None, Some(ident)) => Ok(Self::NamedLine(ident, 0)),
522 (false, None, None) => Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput)),
523 }
524 }
525}
526
527#[cfg(feature = "parse")]
528impl<S: CheapCloneStr> core::str::FromStr for GridPlacement<S> {
529 type Err = ParseError;
530 fn from_str(input: &str) -> Result<Self, Self::Err> {
531 parse_css_str_entirely(input)
532 }
533}
534
535impl<S: CheapCloneStr> GridPlacement<S> {
536 pub fn into_origin_zero_placement_ignoring_named(&self, explicit_track_count: u16) -> OriginZeroGridPlacement {
538 match self {
539 Self::Auto => OriginZeroGridPlacement::Auto,
540 Self::Span(span) => OriginZeroGridPlacement::Span((*span).clamp(1, MAX_GRID_TRACKS)),
543 Self::Line(line) => match line.as_i16() {
546 0 => OriginZeroGridPlacement::Auto,
547 _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
548 },
549 Self::NamedLine(_, _) => OriginZeroGridPlacement::Auto,
550 Self::NamedSpan(_, _) => OriginZeroGridPlacement::Auto,
551 }
552 }
553}
554
555impl<S: CheapCloneStr> Line<GridPlacement<S>> {
556 pub fn into_origin_zero_ignoring_named(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
558 Line {
559 start: self.start.into_origin_zero_placement_ignoring_named(explicit_track_count),
560 end: self.end.into_origin_zero_placement_ignoring_named(explicit_track_count),
561 }
562 }
563}
564
565impl NonNamedGridPlacement {
566 pub fn into_origin_zero_placement(
568 &self,
569 explicit_track_count: u16,
570 ) -> OriginZeroGridPlacement {
572 match self {
573 Self::Auto => OriginZeroGridPlacement::Auto,
574 Self::Span(span) => OriginZeroGridPlacement::Span((*span).clamp(1, MAX_GRID_TRACKS)),
577 Self::Line(line) => match line.as_i16() {
580 0 => OriginZeroGridPlacement::Auto,
581 _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
582 },
583 }
584 }
585}
586
587impl<T: GridCoordinate> Line<GenericGridPlacement<T>> {
588 pub const fn indefinite_span(&self) -> u16 {
591 use GenericGridPlacement as GP;
592 match (self.start, self.end) {
593 (GP::Line(_), GP::Auto) => 1,
594 (GP::Auto, GP::Line(_)) => 1,
595 (GP::Auto, GP::Auto) => 1,
596 (GP::Line(_), GP::Span(span)) => span,
597 (GP::Span(span), GP::Line(_)) => span,
598 (GP::Span(span), GP::Auto) => span,
599 (GP::Auto, GP::Span(span)) => span,
600 (GP::Span(span), GP::Span(_)) => span,
601 (GP::Line(_), GP::Line(_)) => panic!("indefinite_span should only be called on indefinite grid tracks"),
602 }
603 }
604}
605
606impl<S: CheapCloneStr> Line<GridPlacement<S>> {
607 #[inline]
608 pub fn is_definite(&self) -> bool {
612 match (&self.start, &self.end) {
613 (GridPlacement::Line(line), _) if line.as_i16() != 0 => true,
614 (_, GridPlacement::Line(line)) if line.as_i16() != 0 => true,
615 (GridPlacement::NamedLine(_, _), _) => true,
616 (_, GridPlacement::NamedLine(_, _)) => true,
617 _ => false,
618 }
619 }
620}
621
622impl Line<NonNamedGridPlacement> {
623 #[inline]
624 pub fn is_definite(&self) -> bool {
628 match (&self.start, &self.end) {
629 (GenericGridPlacement::Line(line), _) if line.as_i16() != 0 => true,
630 (_, GenericGridPlacement::Line(line)) if line.as_i16() != 0 => true,
631 _ => false,
632 }
633 }
634
635 pub fn into_origin_zero(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
637 Line {
638 start: self.start.into_origin_zero_placement(explicit_track_count),
639 end: self.end.into_origin_zero_placement(explicit_track_count),
640 }
641 }
642}
643
644impl Line<OriginZeroGridPlacement> {
645 #[inline]
646 pub const fn is_definite(&self) -> bool {
649 matches!((self.start, self.end), (GenericGridPlacement::Line(_), _) | (_, GenericGridPlacement::Line(_)))
650 }
651
652 pub fn resolve_definite_grid_lines(&self) -> Line<OriginZeroLine> {
655 use OriginZeroGridPlacement as GP;
656 match (self.start, self.end) {
657 (GP::Line(line1), GP::Line(line2)) => {
658 if line1 == line2 {
659 Line { start: line1, end: line1 + 1 }
660 } else {
661 Line { start: min(line1, line2), end: max(line1, line2) }
662 }
663 }
664 (GP::Line(line), GP::Span(span)) => Line { start: line, end: line + span },
665 (GP::Line(line), GP::Auto) => Line { start: line, end: line + 1 },
666 (GP::Span(span), GP::Line(line)) => Line { start: line - span, end: line },
667 (GP::Auto, GP::Line(line)) => Line { start: line - 1, end: line },
668 _ => panic!("resolve_definite_grid_tracks should only be called on definite grid tracks"),
669 }
670 }
671
672 pub fn resolve_absolutely_positioned_grid_tracks(&self) -> Line<Option<OriginZeroLine>> {
682 use OriginZeroGridPlacement as GP;
683 match (self.start, self.end) {
684 (GP::Line(track1), GP::Line(track2)) => {
685 if track1 == track2 {
686 Line { start: Some(track1), end: Some(track1 + 1) }
687 } else {
688 Line { start: Some(min(track1, track2)), end: Some(max(track1, track2)) }
689 }
690 }
691 (GP::Line(track), GP::Span(span)) => Line { start: Some(track), end: Some(track + span) },
692 (GP::Line(track), GP::Auto) => Line { start: Some(track), end: None },
693 (GP::Span(span), GP::Line(track)) => Line { start: Some(track - span), end: Some(track) },
694 (GP::Auto, GP::Line(track)) => Line { start: None, end: Some(track) },
695 _ => Line { start: None, end: None },
696 }
697 }
698
699 pub fn resolve_indefinite_grid_tracks(&self, start: OriginZeroLine) -> Line<OriginZeroLine> {
702 use OriginZeroGridPlacement as GP;
703 match (self.start, self.end) {
704 (GP::Auto, GP::Auto) => Line { start, end: start + 1 },
705 (GP::Span(span), GP::Auto) => Line { start, end: start + span },
706 (GP::Auto, GP::Span(span)) => Line { start, end: start + span },
707 (GP::Span(span), GP::Span(_)) => Line { start, end: start + span },
708 _ => panic!("resolve_indefinite_grid_tracks should only be called on indefinite grid tracks"),
709 }
710 }
711}
712
713impl<S: CheapCloneStr> Default for Line<GridPlacement<S>> {
715 fn default() -> Self {
716 Line { start: GridPlacement::<S>::Auto, end: GridPlacement::<S>::Auto }
717 }
718}
719
720#[derive(Copy, Clone, PartialEq, Debug)]
726#[cfg_attr(feature = "serde", derive(Serialize))]
727pub struct MaxTrackSizingFunction(pub(crate) CompactLength);
728impl TaffyZero for MaxTrackSizingFunction {
729 const ZERO: Self = Self(CompactLength::ZERO);
730}
731impl TaffyAuto for MaxTrackSizingFunction {
732 const AUTO: Self = Self(CompactLength::AUTO);
733}
734impl TaffyMinContent for MaxTrackSizingFunction {
735 const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
736}
737impl TaffyMaxContent for MaxTrackSizingFunction {
738 const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
739}
740impl FromLength for MaxTrackSizingFunction {
741 fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
742 Self::length(value.into() as f32)
743 }
744}
745impl FromPercent for MaxTrackSizingFunction {
746 fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
747 Self::percent(value.into() as f32)
748 }
749}
750impl TaffyFitContent for MaxTrackSizingFunction {
751 fn fit_content(argument: LengthPercentage) -> Self {
752 Self(CompactLength::fit_content(argument))
753 }
754}
755impl FromFr for MaxTrackSizingFunction {
756 fn from_fr<Input: Into<f64> + Copy>(value: Input) -> Self {
757 Self::fr(value.into() as f32)
758 }
759}
760impl From<LengthPercentage> for MaxTrackSizingFunction {
761 fn from(input: LengthPercentage) -> Self {
762 Self(input.0)
763 }
764}
765impl From<LengthPercentageAuto> for MaxTrackSizingFunction {
766 fn from(input: LengthPercentageAuto) -> Self {
767 Self(input.0)
768 }
769}
770impl From<Dimension> for MaxTrackSizingFunction {
771 fn from(input: Dimension) -> Self {
772 match input.0.tag() {
775 CompactLength::FIT_CONTENT_KEYWORD_TAG | CompactLength::STRETCH_TAG | CompactLength::CONTENT_TAG => {
776 Self::auto()
777 }
778 _ => Self(input.0),
779 }
780 }
781}
782impl From<MinTrackSizingFunction> for MaxTrackSizingFunction {
783 fn from(input: MinTrackSizingFunction) -> Self {
784 Self(input.0)
785 }
786}
787
788#[cfg(feature = "parse")]
789impl FromCss for MaxTrackSizingFunction {
790 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
791 let token = parser.next()?.clone();
792 match token {
793 Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
794 Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
795 Token::Dimension { unit, value, .. } if unit == "fr" && value.is_sign_positive() => Ok(Self::fr(value)),
796 Token::Ident(ref ident) => match ident.as_ref() {
797 "auto" => Ok(Self::auto()),
798 "min-content" => Ok(Self::min_content()),
799 "max-content" => Ok(Self::max_content()),
800 _ => Err(parser.new_unexpected_token_error(token))?,
801 },
802 Token::Function(ref name) if name.as_ref() == "fit-content" => parser.parse_nested_block(|parser| {
803 let token = parser.next()?.clone();
804 match token {
805 Token::Percentage { unit_value, .. } => Ok(Self::fit_content_percent(unit_value)),
806 Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::fit_content_px(value)),
807 token => Err(parser.new_unexpected_token_error(token))?,
808 }
809 }),
810 token => Err(parser.new_unexpected_token_error(token))?,
811 }
812 }
813}
814
815#[cfg(feature = "parse")]
816from_str_from_css!(MaxTrackSizingFunction);
817
818#[cfg(feature = "serde")]
819impl<'de> serde::Deserialize<'de> for MaxTrackSizingFunction {
820 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
821 where
822 D: serde::Deserializer<'de>,
823 {
824 let inner = CompactLength::deserialize(deserializer)?;
825 if matches!(
827 inner.tag(),
828 CompactLength::LENGTH_TAG
829 | CompactLength::PERCENT_TAG
830 | CompactLength::AUTO_TAG
831 | CompactLength::MIN_CONTENT_TAG
832 | CompactLength::MAX_CONTENT_TAG
833 | CompactLength::FIT_CONTENT_PX_TAG
834 | CompactLength::FIT_CONTENT_PERCENT_TAG
835 | CompactLength::FR_TAG
836 ) {
837 Ok(Self(inner))
838 } else {
839 Err(serde::de::Error::custom("Invalid tag"))
840 }
841 }
842}
843
844impl MaxTrackSizingFunction {
845 #[inline(always)]
848 pub const fn length(val: f32) -> Self {
849 Self(CompactLength::length(val))
850 }
851
852 #[inline(always)]
856 pub const fn percent(val: f32) -> Self {
857 Self(CompactLength::percent(val))
858 }
859
860 #[inline(always)]
863 pub const fn auto() -> Self {
864 Self(CompactLength::auto())
865 }
866
867 #[inline(always)]
870 pub const fn min_content() -> Self {
871 Self(CompactLength::min_content())
872 }
873
874 #[inline(always)]
877 pub const fn max_content() -> Self {
878 Self(CompactLength::max_content())
879 }
880
881 #[inline(always)]
891 pub const fn fit_content_px(limit: f32) -> Self {
892 Self(CompactLength::fit_content_px(limit))
893 }
894
895 #[inline(always)]
905 pub const fn fit_content_percent(limit: f32) -> Self {
906 Self(CompactLength::fit_content_percent(limit))
907 }
908
909 #[inline(always)]
913 pub const fn fr(val: f32) -> Self {
914 Self(CompactLength::fr(val))
915 }
916
917 #[inline]
922 #[cfg(feature = "calc")]
923 pub fn calc(ptr: *const ()) -> Self {
924 Self(CompactLength::calc(ptr))
925 }
926
927 #[allow(unsafe_code)]
931 pub unsafe fn from_raw(val: CompactLength) -> Self {
932 Self(val)
933 }
934
935 pub fn into_raw(self) -> CompactLength {
937 self.0
938 }
939
940 #[inline(always)]
942 pub fn is_intrinsic(&self) -> bool {
943 self.0.is_intrinsic()
944 }
945
946 #[inline(always)]
950 pub fn is_max_content_alike(&self) -> bool {
951 self.0.is_max_content_alike()
952 }
953
954 #[inline(always)]
956 pub fn is_fr(&self) -> bool {
957 self.0.is_fr()
958 }
959
960 #[inline(always)]
962 pub fn is_auto(&self) -> bool {
963 self.0.is_auto()
964 }
965
966 #[inline(always)]
968 pub fn is_min_content(&self) -> bool {
969 self.0.is_min_content()
970 }
971
972 #[inline(always)]
974 pub fn is_max_content(&self) -> bool {
975 self.0.is_max_content()
976 }
977
978 #[inline(always)]
980 pub fn is_fit_content(&self) -> bool {
981 self.0.is_fit_content()
982 }
983
984 #[inline(always)]
986 pub fn is_max_or_fit_content(&self) -> bool {
987 self.0.is_max_or_fit_content()
988 }
989
990 #[inline(always)]
992 pub fn has_definite_value(self, parent_size: Option<f32>) -> bool {
993 match self.0.tag() {
994 CompactLength::LENGTH_TAG => true,
995 CompactLength::PERCENT_TAG => parent_size.is_some(),
996 #[cfg(feature = "calc")]
997 _ if self.0.is_calc() => parent_size.is_some(),
998 _ => false,
999 }
1000 }
1001
1002 #[inline(always)]
1006 pub fn definite_value(
1007 self,
1008 parent_size: Option<f32>,
1009 calc_resolver: impl Fn(*const (), f32) -> f32,
1010 ) -> Option<f32> {
1011 match self.0.tag() {
1012 CompactLength::LENGTH_TAG => Some(self.0.value()),
1013 CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1014 #[cfg(feature = "calc")]
1015 _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1016 _ => None,
1017 }
1018 }
1019
1020 #[inline(always)]
1027 pub fn definite_limit(
1028 self,
1029 parent_size: Option<f32>,
1030 calc_resolver: impl Fn(*const (), f32) -> f32,
1031 ) -> Option<f32> {
1032 match self.0.tag() {
1033 CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
1034 CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1035 _ => self.definite_value(parent_size, calc_resolver),
1036 }
1037 }
1038
1039 #[inline(always)]
1042 pub fn resolved_percentage_size(
1043 self,
1044 parent_size: f32,
1045 calc_resolver: impl Fn(*const (), f32) -> f32,
1046 ) -> Option<f32> {
1047 self.0.resolved_percentage_size(parent_size, calc_resolver)
1048 }
1049
1050 #[inline(always)]
1052 pub fn uses_percentage(self) -> bool {
1053 self.0.uses_percentage()
1054 }
1055
1056 pub fn expand(self) -> ExpandedMaxTrackSizingFunction {
1062 match self.0.tag() {
1063 CompactLength::LENGTH_TAG => ExpandedMaxTrackSizingFunction::Length(self.0.value()),
1064 CompactLength::PERCENT_TAG => ExpandedMaxTrackSizingFunction::Percent(self.0.value()),
1065 CompactLength::AUTO_TAG => ExpandedMaxTrackSizingFunction::Auto,
1066 CompactLength::MIN_CONTENT_TAG => ExpandedMaxTrackSizingFunction::MinContent,
1067 CompactLength::MAX_CONTENT_TAG => ExpandedMaxTrackSizingFunction::MaxContent,
1068 CompactLength::FIT_CONTENT_PX_TAG => ExpandedMaxTrackSizingFunction::FitContentPx(self.0.value()),
1069 CompactLength::FIT_CONTENT_PERCENT_TAG => ExpandedMaxTrackSizingFunction::FitContentPercent(self.0.value()),
1070 CompactLength::FR_TAG => ExpandedMaxTrackSizingFunction::Fr(self.0.value()),
1071 #[cfg(feature = "calc")]
1072 _ if self.0.is_calc() => ExpandedMaxTrackSizingFunction::Calc(self.0.calc_value()),
1073 _ => unreachable!("MaxTrackSizingFunction contains a value with an invalid tag"),
1074 }
1075 }
1076}
1077
1078#[derive(Copy, Clone, PartialEq, Debug)]
1083pub enum ExpandedMaxTrackSizingFunction {
1084 Length(f32),
1086 Percent(f32),
1088 Auto,
1090 MinContent,
1092 MaxContent,
1094 FitContentPx(f32),
1096 FitContentPercent(f32),
1098 Fr(f32),
1100 #[cfg(feature = "calc")]
1103 Calc(*const ()),
1104}
1105
1106impl From<MaxTrackSizingFunction> for ExpandedMaxTrackSizingFunction {
1107 fn from(value: MaxTrackSizingFunction) -> Self {
1108 value.expand()
1109 }
1110}
1111
1112impl From<ExpandedMaxTrackSizingFunction> for MaxTrackSizingFunction {
1113 fn from(value: ExpandedMaxTrackSizingFunction) -> Self {
1114 match value {
1115 ExpandedMaxTrackSizingFunction::Length(val) => Self::length(val),
1116 ExpandedMaxTrackSizingFunction::Percent(val) => Self::percent(val),
1117 ExpandedMaxTrackSizingFunction::Auto => Self::auto(),
1118 ExpandedMaxTrackSizingFunction::MinContent => Self::min_content(),
1119 ExpandedMaxTrackSizingFunction::MaxContent => Self::max_content(),
1120 ExpandedMaxTrackSizingFunction::FitContentPx(val) => Self::fit_content_px(val),
1121 ExpandedMaxTrackSizingFunction::FitContentPercent(val) => Self::fit_content_percent(val),
1122 ExpandedMaxTrackSizingFunction::Fr(val) => Self::fr(val),
1123 #[cfg(feature = "calc")]
1124 ExpandedMaxTrackSizingFunction::Calc(ptr) => Self::calc(ptr),
1125 }
1126 }
1127}
1128
1129#[derive(Copy, Clone, PartialEq, Debug)]
1135#[cfg_attr(feature = "serde", derive(Serialize))]
1136pub struct MinTrackSizingFunction(pub(crate) CompactLength);
1137impl TaffyZero for MinTrackSizingFunction {
1138 const ZERO: Self = Self(CompactLength::ZERO);
1139}
1140impl TaffyAuto for MinTrackSizingFunction {
1141 const AUTO: Self = Self(CompactLength::AUTO);
1142}
1143impl TaffyMinContent for MinTrackSizingFunction {
1144 const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
1145}
1146impl TaffyMaxContent for MinTrackSizingFunction {
1147 const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
1148}
1149impl FromLength for MinTrackSizingFunction {
1150 fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1151 Self::length(value.into() as f32)
1152 }
1153}
1154impl FromPercent for MinTrackSizingFunction {
1155 fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
1156 Self::percent(value.into() as f32)
1157 }
1158}
1159impl From<LengthPercentage> for MinTrackSizingFunction {
1160 fn from(input: LengthPercentage) -> Self {
1161 Self(input.0)
1162 }
1163}
1164impl From<LengthPercentageAuto> for MinTrackSizingFunction {
1165 fn from(input: LengthPercentageAuto) -> Self {
1166 Self(input.0)
1167 }
1168}
1169impl From<Dimension> for MinTrackSizingFunction {
1170 fn from(input: Dimension) -> Self {
1171 match input.0.tag() {
1174 CompactLength::FIT_CONTENT_PX_TAG
1175 | CompactLength::FIT_CONTENT_PERCENT_TAG
1176 | CompactLength::FIT_CONTENT_KEYWORD_TAG
1177 | CompactLength::STRETCH_TAG
1178 | CompactLength::CONTENT_TAG => Self::auto(),
1179 _ => Self(input.0),
1180 }
1181 }
1182}
1183
1184impl From<MaxTrackSizingFunction> for MinTrackSizingFunction {
1185 fn from(input: MaxTrackSizingFunction) -> Self {
1186 if input.is_fr() || input.is_fit_content() {
1187 return Self::auto();
1188 }
1189 Self(input.0)
1190 }
1191}
1192
1193#[cfg(feature = "parse")]
1194impl FromCss for MinTrackSizingFunction {
1195 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1196 let token = parser.next()?.clone();
1197 match token {
1198 Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
1199 Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
1200 Token::Ident(ref ident) => match ident.as_ref() {
1201 "auto" => Ok(Self::auto()),
1202 "min-content" => Ok(Self::min_content()),
1203 "max-content" => Ok(Self::max_content()),
1204 _ => Err(parser.new_unexpected_token_error(token))?,
1205 },
1206 token => Err(parser.new_unexpected_token_error(token))?,
1207 }
1208 }
1209}
1210
1211#[cfg(feature = "parse")]
1212from_str_from_css!(MinTrackSizingFunction);
1213
1214#[cfg(feature = "serde")]
1215impl<'de> serde::Deserialize<'de> for MinTrackSizingFunction {
1216 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1217 where
1218 D: serde::Deserializer<'de>,
1219 {
1220 let inner = CompactLength::deserialize(deserializer)?;
1221 if matches!(
1223 inner.tag(),
1224 CompactLength::LENGTH_TAG
1225 | CompactLength::PERCENT_TAG
1226 | CompactLength::AUTO_TAG
1227 | CompactLength::MIN_CONTENT_TAG
1228 | CompactLength::MAX_CONTENT_TAG
1229 | CompactLength::FIT_CONTENT_PX_TAG
1230 | CompactLength::FIT_CONTENT_PERCENT_TAG
1231 ) {
1232 Ok(Self(inner))
1233 } else {
1234 Err(serde::de::Error::custom("Invalid tag"))
1235 }
1236 }
1237}
1238
1239impl MinTrackSizingFunction {
1240 #[inline(always)]
1243 pub const fn length(val: f32) -> Self {
1244 Self(CompactLength::length(val))
1245 }
1246
1247 #[inline(always)]
1251 pub const fn percent(val: f32) -> Self {
1252 Self(CompactLength::percent(val))
1253 }
1254
1255 #[inline(always)]
1258 pub const fn auto() -> Self {
1259 Self(CompactLength::auto())
1260 }
1261
1262 #[inline(always)]
1265 pub const fn min_content() -> Self {
1266 Self(CompactLength::min_content())
1267 }
1268
1269 #[inline(always)]
1272 pub const fn max_content() -> Self {
1273 Self(CompactLength::max_content())
1274 }
1275
1276 #[inline]
1281 #[cfg(feature = "calc")]
1282 pub fn calc(ptr: *const ()) -> Self {
1283 Self(CompactLength::calc(ptr))
1284 }
1285
1286 #[allow(unsafe_code)]
1290 pub unsafe fn from_raw(val: CompactLength) -> Self {
1291 Self(val)
1292 }
1293
1294 pub fn into_raw(self) -> CompactLength {
1296 self.0
1297 }
1298
1299 #[inline(always)]
1301 pub fn is_intrinsic(&self) -> bool {
1302 self.0.is_intrinsic()
1303 }
1304
1305 #[inline(always)]
1307 pub fn is_min_or_max_content(&self) -> bool {
1308 self.0.is_min_or_max_content()
1309 }
1310
1311 #[inline(always)]
1313 pub fn is_fr(&self) -> bool {
1314 self.0.is_fr()
1315 }
1316
1317 #[inline(always)]
1319 pub fn is_auto(&self) -> bool {
1320 self.0.is_auto()
1321 }
1322
1323 #[inline(always)]
1325 pub fn is_min_content(&self) -> bool {
1326 self.0.is_min_content()
1327 }
1328
1329 #[inline(always)]
1331 pub fn is_max_content(&self) -> bool {
1332 self.0.is_max_content()
1333 }
1334
1335 #[inline(always)]
1339 pub fn definite_value(
1340 self,
1341 parent_size: Option<f32>,
1342 calc_resolver: impl Fn(*const (), f32) -> f32,
1343 ) -> Option<f32> {
1344 match self.0.tag() {
1345 CompactLength::LENGTH_TAG => Some(self.0.value()),
1346 CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1347 #[cfg(feature = "calc")]
1348 _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1349 _ => None,
1350 }
1351 }
1352
1353 #[inline(always)]
1356 pub fn resolved_percentage_size(
1357 self,
1358 parent_size: f32,
1359 calc_resolver: impl Fn(*const (), f32) -> f32,
1360 ) -> Option<f32> {
1361 self.0.resolved_percentage_size(parent_size, calc_resolver)
1362 }
1363
1364 #[inline(always)]
1366 pub fn uses_percentage(self) -> bool {
1367 #[cfg(feature = "calc")]
1368 {
1369 matches!(self.0.tag(), CompactLength::PERCENT_TAG) || self.0.is_calc()
1370 }
1371 #[cfg(not(feature = "calc"))]
1372 {
1373 matches!(self.0.tag(), CompactLength::PERCENT_TAG)
1374 }
1375 }
1376
1377 pub fn expand(self) -> ExpandedMinTrackSizingFunction {
1383 match self.0.tag() {
1384 CompactLength::LENGTH_TAG => ExpandedMinTrackSizingFunction::Length(self.0.value()),
1385 CompactLength::PERCENT_TAG => ExpandedMinTrackSizingFunction::Percent(self.0.value()),
1386 CompactLength::AUTO_TAG => ExpandedMinTrackSizingFunction::Auto,
1387 CompactLength::MIN_CONTENT_TAG => ExpandedMinTrackSizingFunction::MinContent,
1388 CompactLength::MAX_CONTENT_TAG => ExpandedMinTrackSizingFunction::MaxContent,
1389 #[cfg(feature = "calc")]
1390 _ if self.0.is_calc() => ExpandedMinTrackSizingFunction::Calc(self.0.calc_value()),
1391 _ => unreachable!("MinTrackSizingFunction contains a value with an invalid tag"),
1392 }
1393 }
1394}
1395
1396#[derive(Copy, Clone, PartialEq, Debug)]
1401pub enum ExpandedMinTrackSizingFunction {
1402 Length(f32),
1404 Percent(f32),
1406 Auto,
1408 MinContent,
1410 MaxContent,
1412 #[cfg(feature = "calc")]
1415 Calc(*const ()),
1416}
1417
1418impl From<MinTrackSizingFunction> for ExpandedMinTrackSizingFunction {
1419 fn from(value: MinTrackSizingFunction) -> Self {
1420 value.expand()
1421 }
1422}
1423
1424impl From<ExpandedMinTrackSizingFunction> for MinTrackSizingFunction {
1425 fn from(value: ExpandedMinTrackSizingFunction) -> Self {
1426 match value {
1427 ExpandedMinTrackSizingFunction::Length(val) => Self::length(val),
1428 ExpandedMinTrackSizingFunction::Percent(val) => Self::percent(val),
1429 ExpandedMinTrackSizingFunction::Auto => Self::auto(),
1430 ExpandedMinTrackSizingFunction::MinContent => Self::min_content(),
1431 ExpandedMinTrackSizingFunction::MaxContent => Self::max_content(),
1432 #[cfg(feature = "calc")]
1433 ExpandedMinTrackSizingFunction::Calc(ptr) => Self::calc(ptr),
1434 }
1435 }
1436}
1437
1438pub type TrackSizingFunction = MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>;
1443impl TrackSizingFunction {
1444 pub fn min_sizing_function(&self) -> MinTrackSizingFunction {
1446 self.min
1447 }
1448 pub fn max_sizing_function(&self) -> MaxTrackSizingFunction {
1450 self.max
1451 }
1452 pub fn has_fixed_component(&self) -> bool {
1454 self.min.0.is_length_or_percentage() || self.max.0.is_length_or_percentage()
1455 }
1456}
1457impl TaffyAuto for TrackSizingFunction {
1458 const AUTO: Self = Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::AUTO };
1459}
1460impl TaffyMinContent for TrackSizingFunction {
1461 const MIN_CONTENT: Self =
1462 Self { min: MinTrackSizingFunction::MIN_CONTENT, max: MaxTrackSizingFunction::MIN_CONTENT };
1463}
1464impl TaffyMaxContent for TrackSizingFunction {
1465 const MAX_CONTENT: Self =
1466 Self { min: MinTrackSizingFunction::MAX_CONTENT, max: MaxTrackSizingFunction::MAX_CONTENT };
1467}
1468impl TaffyFitContent for TrackSizingFunction {
1469 fn fit_content(argument: LengthPercentage) -> Self {
1470 Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::fit_content(argument) }
1471 }
1472}
1473impl TaffyZero for TrackSizingFunction {
1474 const ZERO: Self = Self { min: MinTrackSizingFunction::ZERO, max: MaxTrackSizingFunction::ZERO };
1475}
1476impl FromLength for TrackSizingFunction {
1477 fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1478 Self { min: MinTrackSizingFunction::from_length(value), max: MaxTrackSizingFunction::from_length(value) }
1479 }
1480}
1481impl FromPercent for TrackSizingFunction {
1482 fn from_percent<Input: Into<f64> + Copy>(percent: Input) -> Self {
1483 Self { min: MinTrackSizingFunction::from_percent(percent), max: MaxTrackSizingFunction::from_percent(percent) }
1484 }
1485}
1486impl FromFr for TrackSizingFunction {
1487 fn from_fr<Input: Into<f64> + Copy>(flex: Input) -> Self {
1488 Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::from_fr(flex) }
1489 }
1490}
1491impl From<LengthPercentage> for TrackSizingFunction {
1492 fn from(input: LengthPercentage) -> Self {
1493 Self { min: input.into(), max: input.into() }
1494 }
1495}
1496impl From<LengthPercentageAuto> for TrackSizingFunction {
1497 fn from(input: LengthPercentageAuto) -> Self {
1498 Self { min: input.into(), max: input.into() }
1499 }
1500}
1501impl From<Dimension> for TrackSizingFunction {
1502 fn from(input: Dimension) -> Self {
1503 Self { min: input.into(), max: input.into() }
1504 }
1505}
1506
1507#[cfg(feature = "parse")]
1508impl FromCss for TrackSizingFunction {
1509 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1510 if let Ok(value) = parser.try_parse(|parser| {
1512 parser.expect_function_matching("minmax")?;
1513 parser.parse_nested_block(|parser| {
1514 let min = MinTrackSizingFunction::from_css(parser)?;
1515 parser.expect_comma()?;
1516 let max = MaxTrackSizingFunction::from_css(parser)?;
1517
1518 Ok(Self { min, max })
1519 })
1520 }) {
1521 return Ok(value);
1522 }
1523
1524 let max = MaxTrackSizingFunction::from_css(parser)?;
1526 let min = max.into();
1527 Ok(Self { min, max })
1528 }
1529}
1530
1531#[cfg(feature = "parse")]
1532from_str_from_css!(TrackSizingFunction);
1533
1534#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1539#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1540pub enum RepetitionCount {
1541 AutoFill,
1544 AutoFit,
1547 Count(u16),
1549}
1550impl From<u16> for RepetitionCount {
1551 fn from(value: u16) -> Self {
1552 Self::Count(value)
1553 }
1554}
1555
1556#[derive(Debug)]
1559pub struct InvalidStringRepetitionValue;
1560#[cfg(feature = "std")]
1561impl std::error::Error for InvalidStringRepetitionValue {}
1562impl core::fmt::Display for InvalidStringRepetitionValue {
1563 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1564 f.write_str("&str can only be converted to GridTrackRepetition if it's value is 'auto-fit' or 'auto-fill'")
1565 }
1566}
1567impl TryFrom<&str> for RepetitionCount {
1568 type Error = InvalidStringRepetitionValue;
1569 fn try_from(value: &str) -> Result<Self, InvalidStringRepetitionValue> {
1570 match value {
1571 "auto-fit" => Ok(Self::AutoFit),
1572 "auto-fill" => Ok(Self::AutoFill),
1573 _ => Err(InvalidStringRepetitionValue),
1574 }
1575 }
1576}
1577
1578#[cfg(feature = "parse")]
1579impl FromCss for RepetitionCount {
1580 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1581 match parser.next()?.clone() {
1582 Token::Number { int_value: Some(value), .. } if value.is_positive() => {
1583 Ok(Self::Count(saturating_u16(value)))
1584 }
1585 Token::Ident(ident) if ident == "auto-fit" => Ok(Self::AutoFit),
1586 Token::Ident(ident) if ident == "auto-fill" => Ok(Self::AutoFill),
1587 token => Err(parser.new_unexpected_token_error(token))?,
1588 }
1589 }
1590}
1591#[cfg(feature = "parse")]
1592from_str_from_css!(RepetitionCount);
1593
1594#[derive(Clone, PartialEq, Debug)]
1596#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1597pub struct GridTemplateRepetition<S: CheapCloneStr> {
1598 pub count: RepetitionCount,
1600 pub tracks: Vec<TrackSizingFunction>,
1602 pub line_names: Vec<Vec<S>>,
1608}
1609
1610#[rustfmt::skip]
1611impl<S: CheapCloneStr> GenericRepetition for &'_ GridTemplateRepetition<S> {
1612 type CustomIdent = S;
1613 type RepetitionTrackList<'a> = core::iter::Copied<core::slice::Iter<'a, TrackSizingFunction>> where Self: 'a;
1614 type TemplateLineNames<'a> = core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>> where Self: 'a;
1615 #[inline(always)]
1616 fn count(&self) -> RepetitionCount {
1617 self.count
1618 }
1619 #[inline(always)]
1620 fn track_count(&self) -> u16 {
1621 self.tracks.len().min(u16::MAX as usize) as u16
1622 }
1623 #[inline(always)]
1624 fn tracks(&self) -> Self::RepetitionTrackList<'_> {
1625 self.tracks.iter().copied()
1626 }
1627 #[inline(always)]
1628 fn lines_names(&self) -> Self::TemplateLineNames<'_> {
1629 self.line_names.iter().map(|names| names.iter())
1630 }
1631}
1632
1633#[derive(Clone, PartialEq, Debug)]
1638#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1639pub enum GridTemplateComponent<S: CheapCloneStr> {
1640 Single(TrackSizingFunction),
1642 Repeat(GridTemplateRepetition<S>),
1645}
1646
1647impl<S: CheapCloneStr> GridTemplateComponent<S> {
1648 pub fn as_component_ref(&self) -> GenericGridTemplateComponent<S, &GridTemplateRepetition<S>> {
1650 match self {
1651 GridTemplateComponent::Single(size) => GenericGridTemplateComponent::Single(*size),
1652 GridTemplateComponent::Repeat(repetition) => GenericGridTemplateComponent::Repeat(repetition),
1653 }
1654 }
1655}
1656
1657impl<S: CheapCloneStr> GridTemplateComponent<S> {
1658 pub fn is_auto_repetition(&self) -> bool {
1660 matches!(
1661 self,
1662 Self::Repeat(GridTemplateRepetition { count: RepetitionCount::AutoFit | RepetitionCount::AutoFill, .. })
1663 )
1664 }
1665}
1666impl<S: CheapCloneStr> TaffyAuto for GridTemplateComponent<S> {
1667 const AUTO: Self = Self::Single(TrackSizingFunction::AUTO);
1668}
1669impl<S: CheapCloneStr> TaffyMinContent for GridTemplateComponent<S> {
1670 const MIN_CONTENT: Self = Self::Single(TrackSizingFunction::MIN_CONTENT);
1671}
1672impl<S: CheapCloneStr> TaffyMaxContent for GridTemplateComponent<S> {
1673 const MAX_CONTENT: Self = Self::Single(TrackSizingFunction::MAX_CONTENT);
1674}
1675impl<S: CheapCloneStr> TaffyFitContent for GridTemplateComponent<S> {
1676 fn fit_content(argument: LengthPercentage) -> Self {
1677 Self::Single(TrackSizingFunction::fit_content(argument))
1678 }
1679}
1680impl<S: CheapCloneStr> TaffyZero for GridTemplateComponent<S> {
1681 const ZERO: Self = Self::Single(TrackSizingFunction::ZERO);
1682}
1683impl<S: CheapCloneStr> FromLength for GridTemplateComponent<S> {
1684 fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1685 Self::Single(TrackSizingFunction::from_length(value))
1686 }
1687}
1688impl<S: CheapCloneStr> FromPercent for GridTemplateComponent<S> {
1689 fn from_percent<Input: Into<f64> + Copy>(percent: Input) -> Self {
1690 Self::Single(TrackSizingFunction::from_percent(percent))
1691 }
1692}
1693impl<S: CheapCloneStr> FromFr for GridTemplateComponent<S> {
1694 fn from_fr<Input: Into<f64> + Copy>(flex: Input) -> Self {
1695 Self::Single(TrackSizingFunction::from_fr(flex))
1696 }
1697}
1698impl<S: CheapCloneStr> From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>> for GridTemplateComponent<S> {
1699 fn from(input: MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>) -> Self {
1700 Self::Single(input)
1701 }
1702}
1703
1704#[cfg(feature = "parse")]
1705impl<S: CheapCloneStr> FromCss for GridTemplateComponent<S> {
1706 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1707 if let Ok(value) = parser.try_parse(|parser| {
1709 parser.expect_function_matching("repeat")?;
1710 parser.parse_nested_block(|parser| {
1711 let count = RepetitionCount::from_css(parser)?;
1712 parser.expect_comma()?;
1713 let tracks = GridTemplateTracks::<S, TrackSizingFunction>::from_css(parser)?;
1714
1715 Ok(Self::Repeat(GridTemplateRepetition { count, tracks: tracks.tracks, line_names: tracks.line_names }))
1716 })
1717 }) {
1718 return Ok(value);
1719 }
1720
1721 let track_sizing_function = TrackSizingFunction::from_css(parser)?;
1723 Ok(Self::Single(track_sizing_function))
1724 }
1725}
1726#[cfg(feature = "parse")]
1727impl<S: CheapCloneStr> core::str::FromStr for GridTemplateComponent<S> {
1728 type Err = ParseError;
1729 fn from_str(input: &str) -> Result<Self, Self::Err> {
1730 parse_css_str_entirely(input)
1731 }
1732}
1733
1734#[derive(Clone, PartialEq, Debug)]
1735#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1736#[doc(hidden)]
1737pub struct GridTemplateTracks<S: CheapCloneStr, Track> {
1738 pub tracks: Vec<Track>,
1740 pub line_names: Vec<Vec<S>>,
1742}
1743
1744impl<S: CheapCloneStr, Track> Default for GridTemplateTracks<S, Track> {
1745 fn default() -> Self {
1746 Self { tracks: Vec::new(), line_names: Vec::new() }
1747 }
1748}
1749
1750#[cfg(feature = "parse")]
1751impl<S: CheapCloneStr, Track: FromCss + Debug> FromCss for GridTemplateTracks<S, Track> {
1752 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1753 fn try_parse_line_names<'i, S: CheapCloneStr>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Vec<S>> {
1754 parser.try_parse(|parser| {
1755 parser.expect_square_bracket_block()?;
1756 parser.parse_nested_block(|parser| {
1757 let mut line_names = Vec::new();
1758 while !parser.is_exhausted() {
1759 line_names.push(S::from(parser.expect_ident_cloned()?.as_ref()));
1760 }
1761 Ok(line_names)
1762 })
1763 })
1764 }
1765
1766 let mut tracks = Self::default();
1769 tracks.line_names.push(try_parse_line_names(parser).unwrap_or_default());
1770
1771 while !parser.is_exhausted() {
1772 tracks.tracks.push(Track::from_css(parser)?);
1773 tracks.line_names.push(try_parse_line_names(parser).unwrap_or_default());
1774 }
1775
1776 if tracks.tracks.is_empty() {
1777 return Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput));
1778 }
1779
1780 Ok(tracks)
1781 }
1782}
1783#[cfg(feature = "parse")]
1784impl<S: CheapCloneStr, Track: FromCss + Debug> core::str::FromStr for GridTemplateTracks<S, Track> {
1785 type Err = ParseError;
1786 fn from_str(input: &str) -> Result<Self, Self::Err> {
1787 parse_css_str_entirely(input)
1788 }
1789}
1790
1791#[derive(Default)]
1792#[doc(hidden)]
1793pub struct GridAutoTracks(pub Vec<TrackSizingFunction>);
1794
1795#[cfg(feature = "parse")]
1796impl FromCss for GridAutoTracks {
1797 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1798 let mut tracks = Self::default();
1799 while !parser.is_exhausted() {
1800 tracks.0.push(TrackSizingFunction::from_css(parser)?);
1801 }
1802 if tracks.0.is_empty() {
1803 return Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput));
1804 }
1805 Ok(tracks)
1806 }
1807}
1808#[cfg(feature = "parse")]
1809from_str_from_css!(GridAutoTracks);
1810
1811#[cfg(all(test, feature = "parse"))]
1812mod tests {
1813 use super::*;
1814 use crate::sys::DefaultCheapStr;
1815
1816 #[test]
1817 fn grid_placement_parser_saturates_numeric_values() {
1818 assert_eq!(
1819 "32768".parse::<GridPlacement<DefaultCheapStr>>().unwrap(),
1820 GridPlacement::Line(GridLine::from(i16::MAX))
1821 );
1822 assert_eq!(
1823 "-32769".parse::<GridPlacement<DefaultCheapStr>>().unwrap(),
1824 GridPlacement::Line(GridLine::from(i16::MIN))
1825 );
1826 assert_eq!("span 65536".parse::<GridPlacement<DefaultCheapStr>>().unwrap(), GridPlacement::Span(u16::MAX));
1827
1828 let named_line = "32768 line".parse::<GridPlacement<DefaultCheapStr>>().unwrap();
1829 assert!(matches!(named_line, GridPlacement::NamedLine(_, i16::MAX)));
1830
1831 let named_span = "span 65536 line".parse::<GridPlacement<DefaultCheapStr>>().unwrap();
1832 assert!(matches!(named_span, GridPlacement::NamedSpan(_, u16::MAX)));
1833 }
1834
1835 #[test]
1836 fn repetition_parser_saturates_numeric_values() {
1837 assert_eq!("65536".parse::<RepetitionCount>().unwrap(), RepetitionCount::Count(u16::MAX));
1838
1839 let component = "repeat(65536, 1px)".parse::<GridTemplateComponent<DefaultCheapStr>>().unwrap();
1840 assert!(matches!(
1841 component,
1842 GridTemplateComponent::Repeat(GridTemplateRepetition { count: RepetitionCount::Count(u16::MAX), .. })
1843 ));
1844 }
1845
1846 #[test]
1847 fn repetition_track_count_saturates() {
1848 let repetition = GridTemplateRepetition::<DefaultCheapStr> {
1849 count: RepetitionCount::Count(1),
1850 tracks: vec![TrackSizingFunction::AUTO; u16::MAX as usize + 1],
1851 line_names: Vec::new(),
1852 };
1853 assert_eq!((&repetition).track_count(), u16::MAX);
1854 }
1855}
1856
1857#[cfg(test)]
1858mod expand_tests {
1859 use super::*;
1860
1861 #[test]
1862 fn max_track_sizing_function_round_trips() {
1863 let cases = [
1864 MaxTrackSizingFunction::length(12.0),
1865 MaxTrackSizingFunction::percent(0.5),
1866 MaxTrackSizingFunction::auto(),
1867 MaxTrackSizingFunction::min_content(),
1868 MaxTrackSizingFunction::max_content(),
1869 MaxTrackSizingFunction::fit_content_px(30.0),
1870 MaxTrackSizingFunction::fit_content_percent(0.75),
1871 MaxTrackSizingFunction::fr(2.0),
1872 ];
1873 for value in cases {
1874 assert_eq!(MaxTrackSizingFunction::from(value.expand()), value);
1875 assert_eq!(ExpandedMaxTrackSizingFunction::from(value), value.expand());
1876 }
1877 assert_eq!(MaxTrackSizingFunction::fr(2.0).expand(), ExpandedMaxTrackSizingFunction::Fr(2.0));
1878 assert_eq!(
1879 MaxTrackSizingFunction::fit_content_px(30.0).expand(),
1880 ExpandedMaxTrackSizingFunction::FitContentPx(30.0)
1881 );
1882 }
1883
1884 #[test]
1885 fn min_track_sizing_function_round_trips() {
1886 let cases = [
1887 MinTrackSizingFunction::length(12.0),
1888 MinTrackSizingFunction::percent(0.5),
1889 MinTrackSizingFunction::auto(),
1890 MinTrackSizingFunction::min_content(),
1891 MinTrackSizingFunction::max_content(),
1892 ];
1893 for value in cases {
1894 assert_eq!(MinTrackSizingFunction::from(value.expand()), value);
1895 assert_eq!(ExpandedMinTrackSizingFunction::from(value), value.expand());
1896 }
1897 assert_eq!(MinTrackSizingFunction::max_content().expand(), ExpandedMinTrackSizingFunction::MaxContent);
1898 }
1899
1900 #[cfg(feature = "calc")]
1901 #[test]
1902 fn track_sizing_function_calc_round_trips() {
1903 #[allow(dead_code)]
1904 #[repr(align(8))]
1905 struct Aligned(u64);
1906 static HANDLE: Aligned = Aligned(0);
1907 let handle = &HANDLE as *const Aligned as *const ();
1908
1909 assert_eq!(MaxTrackSizingFunction::calc(handle).expand(), ExpandedMaxTrackSizingFunction::Calc(handle));
1910 assert_eq!(
1911 MaxTrackSizingFunction::from(ExpandedMaxTrackSizingFunction::Calc(handle)),
1912 MaxTrackSizingFunction::calc(handle)
1913 );
1914 assert_eq!(MinTrackSizingFunction::calc(handle).expand(), ExpandedMinTrackSizingFunction::Calc(handle));
1915 }
1916}