1use crate::parser::{Parse, ParserContext};
9use crate::values::specified;
10use crate::values::{CSSFloat, CustomIdent};
11use crate::{One, Zero};
12use cssparser::Parser;
13use std::fmt::{self, Write};
14use std::{cmp, usize};
15use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
16
17pub const MIN_GRID_LINE: i32 = -10000;
21pub const MAX_GRID_LINE: i32 = 10000;
23
24#[derive(
28 Clone,
29 Debug,
30 Default,
31 MallocSizeOf,
32 PartialEq,
33 SpecifiedValueInfo,
34 ToComputedValue,
35 ToResolvedValue,
36 ToShmem,
37)]
38#[repr(C)]
39pub struct GenericGridLine<Integer> {
40 pub ident: CustomIdent,
44 pub line_num: Integer,
52 pub is_span: bool,
54}
55
56pub use self::GenericGridLine as GridLine;
57
58impl<Integer> GridLine<Integer>
59where
60 Integer: PartialEq + Zero,
61{
62 pub fn auto() -> Self {
64 Self {
65 is_span: false,
66 line_num: Zero::zero(),
67 ident: CustomIdent(atom!("")),
68 }
69 }
70
71 pub fn is_auto(&self) -> bool {
73 self.ident.0 == atom!("") && self.line_num.is_zero() && !self.is_span
74 }
75
76 pub fn is_ident_only(&self) -> bool {
78 self.ident.0 != atom!("") && self.line_num.is_zero() && !self.is_span
79 }
80
81 pub fn can_omit(&self, other: &Self) -> bool {
85 if self.is_ident_only() {
86 self == other
87 } else {
88 other.is_auto()
89 }
90 }
91}
92
93impl<Integer> ToCss for GridLine<Integer>
94where
95 Integer: ToCss + PartialEq + Zero + One,
96{
97 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
98 where
99 W: Write,
100 {
101 if self.is_auto() {
103 return dest.write_str("auto");
104 }
105
106 if self.is_ident_only() {
108 return self.ident.to_css(dest);
109 }
110
111 let has_ident = self.ident.0 != atom!("");
113 if self.is_span {
114 dest.write_str("span")?;
115 debug_assert!(!self.line_num.is_zero() || has_ident);
116
117 if !self.line_num.is_zero() && !(self.line_num.is_one() && has_ident) {
122 dest.write_char(' ')?;
123 self.line_num.to_css(dest)?;
124 }
125
126 if has_ident {
127 dest.write_char(' ')?;
128 self.ident.to_css(dest)?;
129 }
130 return Ok(());
131 }
132
133 debug_assert!(!self.line_num.is_zero());
135 self.line_num.to_css(dest)?;
136 if has_ident {
137 dest.write_char(' ')?;
138 self.ident.to_css(dest)?;
139 }
140 Ok(())
141 }
142}
143
144impl Parse for GridLine<specified::Integer> {
145 fn parse<'i, 't>(
146 context: &ParserContext,
147 input: &mut Parser<'i, 't>,
148 ) -> Result<Self, ParseError<'i>> {
149 let mut grid_line = Self::auto();
150 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
151 return Ok(grid_line);
152 }
153
154 let mut val_before_span = false;
159
160 for _ in 0..3 {
161 let location = input.current_source_location();
163 if input.try_parse(|i| i.expect_ident_matching("span")).is_ok() {
164 if grid_line.is_span {
165 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
166 }
167
168 if !grid_line.line_num.is_zero() || grid_line.ident.0 != atom!("") {
169 val_before_span = true;
170 }
171
172 grid_line.is_span = true;
173 } else if let Ok(i) = input.try_parse(|i| specified::Integer::parse(context, i)) {
174 let value = i.value();
176 if value == 0 || val_before_span || !grid_line.line_num.is_zero() {
177 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
178 }
179
180 grid_line.line_num = specified::Integer::new(cmp::max(
181 MIN_GRID_LINE,
182 cmp::min(value, MAX_GRID_LINE),
183 ));
184 } else if let Ok(name) = input.try_parse(|i| CustomIdent::parse(i, &["auto"])) {
185 if val_before_span || grid_line.ident.0 != atom!("") {
186 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
187 }
188 grid_line.ident = name;
191 } else {
192 break;
193 }
194 }
195
196 if grid_line.is_auto() {
197 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
198 }
199
200 if grid_line.is_span {
201 if !grid_line.line_num.is_zero() {
202 if grid_line.line_num.value() <= 0 {
203 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
205 }
206 } else if grid_line.ident.0 == atom!("") {
207 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
209 }
210 }
211
212 Ok(grid_line)
213 }
214}
215
216#[derive(
221 Animate,
222 Clone,
223 Debug,
224 MallocSizeOf,
225 PartialEq,
226 SpecifiedValueInfo,
227 ToAnimatedValue,
228 ToComputedValue,
229 ToCss,
230 ToResolvedValue,
231 ToShmem,
232)]
233#[repr(C, u8)]
234pub enum GenericTrackBreadth<L> {
235 Breadth(L),
237 #[css(dimension)]
239 Fr(CSSFloat),
240 Auto,
242 MinContent,
244 MaxContent,
246}
247
248pub use self::GenericTrackBreadth as TrackBreadth;
249
250impl<L> TrackBreadth<L> {
251 #[inline]
255 pub fn is_fixed(&self) -> bool {
256 matches!(*self, TrackBreadth::Breadth(..))
257 }
258}
259
260#[derive(
265 Clone,
266 Debug,
267 MallocSizeOf,
268 PartialEq,
269 SpecifiedValueInfo,
270 ToAnimatedValue,
271 ToComputedValue,
272 ToResolvedValue,
273 ToShmem,
274)]
275#[repr(C, u8)]
276pub enum GenericTrackSize<L> {
277 Breadth(GenericTrackBreadth<L>),
279 #[css(function)]
284 Minmax(GenericTrackBreadth<L>, GenericTrackBreadth<L>),
285 #[css(function)]
292 FitContent(GenericTrackBreadth<L>),
293}
294
295pub use self::GenericTrackSize as TrackSize;
296
297impl<L> TrackSize<L> {
298 const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto);
300
301 pub const fn initial_value() -> Self {
303 Self::INITIAL_VALUE
304 }
305
306 pub fn is_initial(&self) -> bool {
308 matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) }
310
311 pub fn is_fixed(&self) -> bool {
315 match *self {
316 TrackSize::Breadth(ref breadth) => breadth.is_fixed(),
317 TrackSize::Minmax(ref breadth_1, ref breadth_2) => {
322 if breadth_1.is_fixed() {
323 return true; }
325
326 match *breadth_1 {
327 TrackBreadth::Fr(_) => false, _ => breadth_2.is_fixed(),
329 }
330 },
331 TrackSize::FitContent(_) => false,
332 }
333 }
334}
335
336impl<L> Default for TrackSize<L> {
337 fn default() -> Self {
338 Self::initial_value()
339 }
340}
341
342impl<L: ToCss> ToCss for TrackSize<L> {
343 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
344 where
345 W: Write,
346 {
347 match *self {
348 TrackSize::Breadth(ref breadth) => breadth.to_css(dest),
349 TrackSize::Minmax(ref min, ref max) => {
350 if let TrackBreadth::Auto = *min {
353 if let TrackBreadth::Fr(_) = *max {
354 return max.to_css(dest);
355 }
356 }
357
358 dest.write_str("minmax(")?;
359 min.to_css(dest)?;
360 dest.write_str(", ")?;
361 max.to_css(dest)?;
362 dest.write_char(')')
363 },
364 TrackSize::FitContent(ref lp) => {
365 dest.write_str("fit-content(")?;
366 lp.to_css(dest)?;
367 dest.write_char(')')
368 },
369 }
370 }
371}
372
373#[derive(
377 Clone,
378 Debug,
379 Default,
380 MallocSizeOf,
381 PartialEq,
382 SpecifiedValueInfo,
383 ToComputedValue,
384 ToCss,
385 ToResolvedValue,
386 ToShmem,
387)]
388#[repr(transparent)]
389pub struct GenericImplicitGridTracks<T>(
390 #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice<T>,
391);
392
393pub use self::GenericImplicitGridTracks as ImplicitGridTracks;
394
395impl<T: fmt::Debug + Default + PartialEq> ImplicitGridTracks<T> {
396 pub fn is_initial(&self) -> bool {
398 debug_assert_ne!(
399 *self,
400 ImplicitGridTracks(crate::OwnedSlice::from(vec![Default::default()]))
401 );
402 self.0.is_empty()
403 }
404}
405
406pub fn concat_serialize_idents<W>(
409 prefix: &str,
410 suffix: &str,
411 slice: &[CustomIdent],
412 sep: &str,
413 dest: &mut CssWriter<W>,
414) -> fmt::Result
415where
416 W: Write,
417{
418 if let Some((ref first, rest)) = slice.split_first() {
419 dest.write_str(prefix)?;
420 first.to_css(dest)?;
421 for thing in rest {
422 dest.write_str(sep)?;
423 thing.to_css(dest)?;
424 }
425
426 dest.write_str(suffix)?;
427 }
428
429 Ok(())
430}
431
432#[derive(
436 Clone,
437 Copy,
438 Debug,
439 MallocSizeOf,
440 PartialEq,
441 SpecifiedValueInfo,
442 ToAnimatedValue,
443 ToComputedValue,
444 ToCss,
445 ToResolvedValue,
446 ToShmem,
447)]
448#[repr(C, u8)]
449pub enum RepeatCount<Integer> {
450 Number(Integer),
452 AutoFill,
454 AutoFit,
456}
457
458impl Parse for RepeatCount<specified::Integer> {
459 fn parse<'i, 't>(
460 context: &ParserContext,
461 input: &mut Parser<'i, 't>,
462 ) -> Result<Self, ParseError<'i>> {
463 if let Ok(mut i) = input.try_parse(|i| specified::Integer::parse_positive(context, i)) {
464 if i.value() > MAX_GRID_LINE {
465 i = specified::Integer::new(MAX_GRID_LINE);
466 }
467 return Ok(RepeatCount::Number(i));
468 }
469 try_match_ident_ignore_ascii_case! { input,
470 "auto-fill" => Ok(RepeatCount::AutoFill),
471 "auto-fit" => Ok(RepeatCount::AutoFit),
472 }
473 }
474}
475
476#[derive(
478 Clone,
479 Debug,
480 MallocSizeOf,
481 PartialEq,
482 SpecifiedValueInfo,
483 ToAnimatedValue,
484 ToComputedValue,
485 ToResolvedValue,
486 ToShmem,
487)]
488#[css(function = "repeat")]
489#[repr(C)]
490pub struct GenericTrackRepeat<L, I> {
491 pub count: RepeatCount<I>,
493 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
499 pub track_sizes: crate::OwnedSlice<GenericTrackSize<L>>,
501}
502
503pub use self::GenericTrackRepeat as TrackRepeat;
504
505impl<L: ToCss, I: ToCss> ToCss for TrackRepeat<L, I> {
506 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
507 where
508 W: Write,
509 {
510 dest.write_str("repeat(")?;
511 self.count.to_css(dest)?;
512 dest.write_str(", ")?;
513
514 let mut line_names_iter = self.line_names.iter();
515 for (i, (ref size, ref names)) in self
516 .track_sizes
517 .iter()
518 .zip(&mut line_names_iter)
519 .enumerate()
520 {
521 if i > 0 {
522 dest.write_char(' ')?;
523 }
524
525 concat_serialize_idents("[", "] ", names, " ", dest)?;
526 size.to_css(dest)?;
527 }
528
529 if let Some(line_names_last) = line_names_iter.next() {
530 concat_serialize_idents(" [", "]", line_names_last, " ", dest)?;
531 }
532
533 dest.write_char(')')?;
534
535 Ok(())
536 }
537}
538
539#[derive(
541 Animate,
542 Clone,
543 Debug,
544 MallocSizeOf,
545 PartialEq,
546 SpecifiedValueInfo,
547 ToAnimatedValue,
548 ToComputedValue,
549 ToCss,
550 ToResolvedValue,
551 ToShmem,
552)]
553#[repr(C, u8)]
554pub enum GenericTrackListValue<LengthPercentage, Integer> {
555 TrackSize(#[animation(field_bound)] GenericTrackSize<LengthPercentage>),
557 TrackRepeat(#[animation(field_bound)] GenericTrackRepeat<LengthPercentage, Integer>),
559}
560
561pub use self::GenericTrackListValue as TrackListValue;
562
563impl<L, I> TrackListValue<L, I> {
564 const INITIAL_VALUE: Self = TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto));
566
567 fn is_repeat(&self) -> bool {
568 matches!(*self, TrackListValue::TrackRepeat(..))
569 }
570
571 pub fn is_initial(&self) -> bool {
573 matches!(
574 *self,
575 TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto))
576 ) }
578}
579
580impl<L, I> Default for TrackListValue<L, I> {
581 #[inline]
582 fn default() -> Self {
583 Self::INITIAL_VALUE
584 }
585}
586
587#[derive(
591 Clone,
592 Debug,
593 MallocSizeOf,
594 PartialEq,
595 SpecifiedValueInfo,
596 ToAnimatedValue,
597 ToComputedValue,
598 ToResolvedValue,
599 ToShmem,
600)]
601#[repr(C)]
602pub struct GenericTrackList<LengthPercentage, Integer> {
603 #[css(skip)]
605 pub auto_repeat_index: usize,
606 pub values: crate::OwnedSlice<GenericTrackListValue<LengthPercentage, Integer>>,
608 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
614}
615
616pub use self::GenericTrackList as TrackList;
617
618impl<L, I> TrackList<L, I> {
619 pub fn is_explicit(&self) -> bool {
622 !self.values.iter().any(|v| v.is_repeat())
623 }
624
625 pub fn has_auto_repeat(&self) -> bool {
627 self.auto_repeat_index < self.values.len()
628 }
629}
630
631impl<L: ToCss, I: ToCss> ToCss for TrackList<L, I> {
632 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
633 where
634 W: Write,
635 {
636 let mut values_iter = self.values.iter().peekable();
637 let mut line_names_iter = self.line_names.iter().peekable();
638
639 for idx in 0.. {
640 let names = line_names_iter.next().unwrap(); concat_serialize_idents("[", "]", names, " ", dest)?;
642
643 match values_iter.next() {
644 Some(value) => {
645 if !names.is_empty() {
646 dest.write_char(' ')?;
647 }
648
649 value.to_css(dest)?;
650 },
651 None => break,
652 }
653
654 if values_iter.peek().is_some()
655 || line_names_iter.peek().map_or(false, |v| !v.is_empty())
656 || (idx + 1 == self.auto_repeat_index)
657 {
658 dest.write_char(' ')?;
659 }
660 }
661
662 Ok(())
663 }
664}
665
666#[derive(
672 Clone,
673 Debug,
674 MallocSizeOf,
675 PartialEq,
676 SpecifiedValueInfo,
677 ToAnimatedValue,
678 ToComputedValue,
679 ToResolvedValue,
680 ToShmem,
681)]
682#[repr(C)]
683pub struct GenericNameRepeat<I> {
684 pub count: RepeatCount<I>,
687 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
689}
690
691pub use self::GenericNameRepeat as NameRepeat;
692
693impl<I: ToCss> ToCss for NameRepeat<I> {
694 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
695 where
696 W: Write,
697 {
698 dest.write_str("repeat(")?;
699 self.count.to_css(dest)?;
700 dest.write_char(',')?;
701
702 for ref names in self.line_names.iter() {
703 if names.is_empty() {
704 dest.write_str(" []")?;
707 } else {
708 concat_serialize_idents(" [", "]", names, " ", dest)?;
709 }
710 }
711
712 dest.write_char(')')
713 }
714}
715
716impl<I> NameRepeat<I> {
717 #[inline]
719 pub fn is_auto_fill(&self) -> bool {
720 matches!(self.count, RepeatCount::AutoFill)
721 }
722}
723
724#[derive(
726 Clone,
727 Debug,
728 MallocSizeOf,
729 PartialEq,
730 SpecifiedValueInfo,
731 ToAnimatedValue,
732 ToComputedValue,
733 ToResolvedValue,
734 ToShmem,
735)]
736#[repr(C, u8)]
737pub enum GenericLineNameListValue<I> {
738 LineNames(crate::OwnedSlice<CustomIdent>),
740 Repeat(GenericNameRepeat<I>),
742}
743
744pub use self::GenericLineNameListValue as LineNameListValue;
745
746impl<I: ToCss> ToCss for LineNameListValue<I> {
747 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
748 where
749 W: Write,
750 {
751 match *self {
752 Self::Repeat(ref r) => r.to_css(dest),
753 Self::LineNames(ref names) => {
754 dest.write_char('[')?;
755
756 if let Some((ref first, rest)) = names.split_first() {
757 first.to_css(dest)?;
758 for name in rest {
759 dest.write_char(' ')?;
760 name.to_css(dest)?;
761 }
762 }
763
764 dest.write_char(']')
765 },
766 }
767 }
768}
769
770#[derive(
777 Clone,
778 Debug,
779 Default,
780 MallocSizeOf,
781 PartialEq,
782 SpecifiedValueInfo,
783 ToAnimatedValue,
784 ToComputedValue,
785 ToResolvedValue,
786 ToShmem,
787)]
788#[repr(C)]
789pub struct GenericLineNameList<I> {
790 pub expanded_line_names_length: usize,
794 pub line_names: crate::OwnedSlice<GenericLineNameListValue<I>>,
796}
797
798pub use self::GenericLineNameList as LineNameList;
799
800impl<I: ToCss> ToCss for LineNameList<I> {
801 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
802 where
803 W: Write,
804 {
805 dest.write_str("subgrid")?;
806
807 for value in self.line_names.iter() {
808 dest.write_char(' ')?;
809 value.to_css(dest)?;
810 }
811
812 Ok(())
813 }
814}
815
816#[derive(
818 Animate,
819 Clone,
820 Debug,
821 MallocSizeOf,
822 PartialEq,
823 SpecifiedValueInfo,
824 ToAnimatedValue,
825 ToComputedValue,
826 ToCss,
827 ToResolvedValue,
828 ToShmem,
829)]
830#[value_info(other_values = "subgrid")]
831#[repr(C, u8)]
832pub enum GenericGridTemplateComponent<L, I> {
833 None,
835 TrackList(
837 #[animation(field_bound)]
838 #[compute(field_bound)]
839 #[resolve(field_bound)]
840 #[shmem(field_bound)]
841 Box<GenericTrackList<L, I>>,
842 ),
843 #[animation(error)]
846 Subgrid(Box<GenericLineNameList<I>>),
847 Masonry,
850}
851
852pub use self::GenericGridTemplateComponent as GridTemplateComponent;
853
854impl<L, I> GridTemplateComponent<L, I> {
855 const INITIAL_VALUE: Self = Self::None;
857
858 pub fn track_list_len(&self) -> usize {
860 match *self {
861 GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(),
862 _ => 0,
863 }
864 }
865
866 pub fn is_initial(&self) -> bool {
868 matches!(*self, Self::None) }
870}
871
872impl<L, I> Default for GridTemplateComponent<L, I> {
873 #[inline]
874 fn default() -> Self {
875 Self::INITIAL_VALUE
876 }
877}