1use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::typed_om::{NumericType, NumericValue, ToTyped, TypedValue, UnitValue};
11use crate::values::specified;
12use crate::values::{CSSFloat, CustomIdent};
13use crate::{One, Zero};
14use cssparser::Parser;
15use std::fmt::{self, Write};
16use style_traits::values::specified::AllowedNumericType;
17use style_traits::{CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss};
18use thin_vec::ThinVec;
19
20#[derive(
24 Clone,
25 Debug,
26 Default,
27 MallocSizeOf,
28 PartialEq,
29 SpecifiedValueInfo,
30 ToComputedValue,
31 ToResolvedValue,
32 ToShmem,
33 ToTyped,
34)]
35#[repr(C)]
36#[typed(todo_derive_fields)]
37pub struct GenericGridLine<Integer> {
38 pub ident: CustomIdent,
42 pub line_num: Integer,
44 pub is_span: bool,
46}
47
48pub use self::GenericGridLine as GridLine;
49
50impl<Integer> GridLine<Integer>
51where
52 Integer: PartialEq + Zero,
53{
54 pub fn auto() -> Self {
56 Self {
57 is_span: false,
58 line_num: Zero::zero(),
59 ident: CustomIdent(atom!("")),
60 }
61 }
62
63 pub fn is_auto(&self) -> bool {
65 self.ident.0 == atom!("") && self.line_num.is_zero() && !self.is_span
66 }
67
68 pub fn is_ident_only(&self) -> bool {
70 self.ident.0 != atom!("") && self.line_num.is_zero() && !self.is_span
71 }
72
73 pub fn can_omit(&self, other: &Self) -> bool {
77 if self.is_ident_only() {
78 self == other
79 } else {
80 other.is_auto()
81 }
82 }
83}
84
85impl<Integer> ToCss for GridLine<Integer>
86where
87 Integer: ToCss + PartialEq + Zero + One,
88{
89 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
90 where
91 W: Write,
92 {
93 if self.is_auto() {
95 return dest.write_str("auto");
96 }
97
98 if self.is_ident_only() {
100 return self.ident.to_css(dest);
101 }
102
103 let has_ident = self.ident.0 != atom!("");
105 if self.is_span {
106 dest.write_str("span")?;
107 debug_assert!(!self.line_num.is_zero() || has_ident);
108
109 if !self.line_num.is_zero() && !(self.line_num.is_one() && has_ident) {
114 dest.write_char(' ')?;
115 self.line_num.to_css(dest)?;
116 }
117
118 if has_ident {
119 dest.write_char(' ')?;
120 self.ident.to_css(dest)?;
121 }
122 return Ok(());
123 }
124
125 debug_assert!(!self.line_num.is_zero());
127 self.line_num.to_css(dest)?;
128 if has_ident {
129 dest.write_char(' ')?;
130 self.ident.to_css(dest)?;
131 }
132 Ok(())
133 }
134}
135
136impl Parse for GridLine<specified::Integer> {
137 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
138 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
139 return Ok(Self::auto());
140 }
141
142 let mut is_span = false;
143 let mut line_num: Option<specified::Integer> = None;
144 let mut ident: Option<CustomIdent> = None;
145
146 let mut val_before_span = false;
151
152 for _ in 0..3 {
153 if input.try_parse(|i| i.expect_ident_matching("span")).is_ok() {
155 if is_span {
156 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
157 }
158
159 if line_num.is_some() || ident.is_some() {
160 val_before_span = true;
161 }
162
163 is_span = true;
164 continue;
165 }
166 if let Ok(i) = input.try_parse(|i| specified::Integer::parse(context, i)) {
167 if val_before_span || line_num.is_some() {
168 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
169 }
170
171 if matches!(i.get(), Some(0)) {
172 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
173 }
174
175 line_num = Some(i);
176 continue;
177 }
178 if let Ok(name) = input.try_parse(|i| CustomIdent::parse(i, &["auto"])) {
179 if val_before_span || ident.is_some() {
180 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
181 }
182 ident = Some(name);
185 continue;
186 }
187 break;
188 }
189
190 if line_num.is_none() && ident.is_none() {
191 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
192 }
193
194 let mut grid_line = Self::auto();
195 grid_line.is_span = is_span;
196 if let Some(mut line_num) = line_num {
197 if is_span
198 && line_num
199 .ensure_clamping_mode(AllowedNumericType::AtLeastOne)
200 .is_err()
201 {
202 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
204 }
205 grid_line.line_num = line_num;
206 }
207 if let Some(ident) = ident {
208 grid_line.ident = ident;
209 }
210 Ok(grid_line)
211 }
212}
213
214pub struct FlexUnit;
216
217impl FlexUnit {
218 #[inline]
220 pub fn matches(unit: &str) -> bool {
221 unit.eq_ignore_ascii_case("fr")
222 }
223
224 #[inline]
226 pub fn name() -> &'static str {
227 "fr"
228 }
229}
230
231#[derive(
235 Animate,
236 Clone,
237 Copy,
238 Debug,
239 MallocSizeOf,
240 PartialEq,
241 SpecifiedValueInfo,
242 ToAnimatedValue,
243 ToComputedValue,
244 ToResolvedValue,
245 ToShmem,
246)]
247#[repr(C)]
248pub struct Flex(pub CSSFloat);
249
250impl ToCss for Flex {
251 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
252 where
253 W: Write,
254 {
255 self.0.to_css(dest)?;
256 dest.write_str(FlexUnit::name())
257 }
258}
259
260impl ToTyped for Flex {
261 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
262 let numeric_type = NumericType::flex();
263 let value = self.0;
264 let unit = CssString::from("fr");
265 dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
266 numeric_type,
267 value,
268 unit,
269 })));
270 Ok(())
271 }
272}
273
274#[derive(
279 Animate,
280 Clone,
281 Debug,
282 MallocSizeOf,
283 PartialEq,
284 SpecifiedValueInfo,
285 ToAnimatedValue,
286 ToComputedValue,
287 ToCss,
288 ToResolvedValue,
289 ToShmem,
290 ToTyped,
291)]
292#[repr(C, u8)]
293pub enum GenericTrackBreadth<L> {
294 Breadth(L),
296 Flex(Flex),
298 Auto,
300 MinContent,
302 MaxContent,
304}
305
306pub use self::GenericTrackBreadth as TrackBreadth;
307
308impl<L> TrackBreadth<L> {
309 #[inline]
313 pub fn is_fixed(&self) -> bool {
314 matches!(*self, TrackBreadth::Breadth(..))
315 }
316}
317
318#[derive(
323 Clone,
324 Debug,
325 MallocSizeOf,
326 PartialEq,
327 SpecifiedValueInfo,
328 ToAnimatedValue,
329 ToComputedValue,
330 ToResolvedValue,
331 ToShmem,
332)]
333#[repr(C, u8)]
334pub enum GenericTrackSize<L> {
335 Breadth(GenericTrackBreadth<L>),
337 #[css(function)]
342 Minmax(GenericTrackBreadth<L>, GenericTrackBreadth<L>),
343 #[css(function)]
350 FitContent(GenericTrackBreadth<L>),
351}
352
353pub use self::GenericTrackSize as TrackSize;
354
355impl<L> TrackSize<L> {
356 const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto);
358
359 pub const fn initial_value() -> Self {
361 Self::INITIAL_VALUE
362 }
363
364 pub fn is_initial(&self) -> bool {
366 matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) }
368
369 pub fn is_fixed(&self) -> bool {
373 match *self {
374 TrackSize::Breadth(ref breadth) => breadth.is_fixed(),
375 TrackSize::Minmax(ref breadth_1, ref breadth_2) => {
380 if breadth_1.is_fixed() {
381 return true; }
383
384 match *breadth_1 {
385 TrackBreadth::Flex(_) => false, _ => breadth_2.is_fixed(),
387 }
388 },
389 TrackSize::FitContent(_) => false,
390 }
391 }
392}
393
394impl<L> Default for TrackSize<L> {
395 fn default() -> Self {
396 Self::initial_value()
397 }
398}
399
400impl<L: ToCss> ToCss for TrackSize<L> {
401 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
402 where
403 W: Write,
404 {
405 match *self {
406 TrackSize::Breadth(ref breadth) => breadth.to_css(dest),
407 TrackSize::Minmax(ref min, ref max) => {
408 if let TrackBreadth::Auto = *min {
411 if let TrackBreadth::Flex(_) = *max {
412 return max.to_css(dest);
413 }
414 }
415
416 dest.write_str("minmax(")?;
417 min.to_css(dest)?;
418 dest.write_str(", ")?;
419 max.to_css(dest)?;
420 dest.write_char(')')
421 },
422 TrackSize::FitContent(ref lp) => {
423 dest.write_str("fit-content(")?;
424 lp.to_css(dest)?;
425 dest.write_char(')')
426 },
427 }
428 }
429}
430
431impl<L: ToTyped> ToTyped for TrackSize<L> {
432 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
433 match *self {
434 TrackSize::Breadth(ref breadth) => breadth.to_typed(dest),
435 _ => Err(()),
436 }
437 }
438}
439
440#[derive(
444 Clone,
445 Debug,
446 Default,
447 MallocSizeOf,
448 PartialEq,
449 SpecifiedValueInfo,
450 ToComputedValue,
451 ToCss,
452 ToResolvedValue,
453 ToShmem,
454 ToTyped,
455)]
456#[repr(transparent)]
457pub struct GenericImplicitGridTracks<T>(
458 #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice<T>,
459);
460
461pub use self::GenericImplicitGridTracks as ImplicitGridTracks;
462
463impl<T: fmt::Debug + Default + PartialEq> ImplicitGridTracks<T> {
464 pub fn is_initial(&self) -> bool {
466 debug_assert_ne!(
467 *self,
468 ImplicitGridTracks(crate::OwnedSlice::from(vec![Default::default()]))
469 );
470 self.0.is_empty()
471 }
472}
473
474pub fn concat_serialize_idents<W>(
477 prefix: &str,
478 suffix: &str,
479 slice: &[CustomIdent],
480 sep: &str,
481 dest: &mut CssWriter<W>,
482) -> fmt::Result
483where
484 W: Write,
485{
486 if let Some((ref first, rest)) = slice.split_first() {
487 dest.write_str(prefix)?;
488 first.to_css(dest)?;
489 for thing in rest {
490 dest.write_str(sep)?;
491 thing.to_css(dest)?;
492 }
493
494 dest.write_str(suffix)?;
495 }
496
497 Ok(())
498}
499
500#[derive(
504 Clone,
505 Copy,
506 Debug,
507 MallocSizeOf,
508 PartialEq,
509 SpecifiedValueInfo,
510 ToAnimatedValue,
511 ToComputedValue,
512 ToCss,
513 ToResolvedValue,
514 ToShmem,
515)]
516#[repr(C, u8)]
517pub enum RepeatCount<Integer> {
518 Number(Integer),
520 AutoFill,
522 AutoFit,
524}
525
526impl Parse for RepeatCount<specified::Integer> {
527 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
528 if let Ok(i) = input.try_parse(|i| specified::Integer::parse_positive(context, i)) {
529 return Ok(RepeatCount::Number(i));
530 }
531 try_match_ident_ignore_ascii_case! { input,
532 "auto-fill" => Ok(RepeatCount::AutoFill),
533 "auto-fit" => Ok(RepeatCount::AutoFit),
534 }
535 }
536}
537
538#[derive(
540 Clone,
541 Debug,
542 MallocSizeOf,
543 PartialEq,
544 SpecifiedValueInfo,
545 ToAnimatedValue,
546 ToComputedValue,
547 ToResolvedValue,
548 ToShmem,
549)]
550#[css(function = "repeat")]
551#[repr(C)]
552pub struct GenericTrackRepeat<L, I> {
553 pub count: RepeatCount<I>,
555 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
561 pub track_sizes: crate::OwnedSlice<GenericTrackSize<L>>,
563}
564
565pub use self::GenericTrackRepeat as TrackRepeat;
566
567impl<L: ToCss, I: ToCss> ToCss for TrackRepeat<L, I> {
568 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
569 where
570 W: Write,
571 {
572 dest.write_str("repeat(")?;
573 self.count.to_css(dest)?;
574 dest.write_str(", ")?;
575
576 let mut line_names_iter = self.line_names.iter();
577 for (i, (ref size, names)) in self
578 .track_sizes
579 .iter()
580 .zip(&mut line_names_iter)
581 .enumerate()
582 {
583 if i > 0 {
584 dest.write_char(' ')?;
585 }
586
587 concat_serialize_idents("[", "] ", names, " ", dest)?;
588 size.to_css(dest)?;
589 }
590
591 if let Some(line_names_last) = line_names_iter.next() {
592 concat_serialize_idents(" [", "]", line_names_last, " ", dest)?;
593 }
594
595 dest.write_char(')')?;
596
597 Ok(())
598 }
599}
600
601#[derive(
603 Animate,
604 Clone,
605 Debug,
606 MallocSizeOf,
607 PartialEq,
608 SpecifiedValueInfo,
609 ToAnimatedValue,
610 ToComputedValue,
611 ToCss,
612 ToResolvedValue,
613 ToShmem,
614 ToTyped,
615)]
616#[repr(C, u8)]
617pub enum GenericTrackListValue<LengthPercentage, Integer> {
618 TrackSize(#[animation(field_bound)] GenericTrackSize<LengthPercentage>),
620 #[typed(skip)]
622 TrackRepeat(#[animation(field_bound)] GenericTrackRepeat<LengthPercentage, Integer>),
623}
624
625pub use self::GenericTrackListValue as TrackListValue;
626
627impl<L, I> TrackListValue<L, I> {
628 const INITIAL_VALUE: Self = TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto));
630
631 fn is_repeat(&self) -> bool {
632 matches!(*self, TrackListValue::TrackRepeat(..))
633 }
634
635 pub fn is_initial(&self) -> bool {
637 matches!(
638 *self,
639 TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto))
640 ) }
642}
643
644impl<L, I> Default for TrackListValue<L, I> {
645 #[inline]
646 fn default() -> Self {
647 Self::INITIAL_VALUE
648 }
649}
650
651#[derive(
655 Clone,
656 Debug,
657 MallocSizeOf,
658 PartialEq,
659 SpecifiedValueInfo,
660 ToAnimatedValue,
661 ToComputedValue,
662 ToResolvedValue,
663 ToShmem,
664)]
665#[repr(C)]
666pub struct GenericTrackList<LengthPercentage, Integer> {
667 #[css(skip)]
669 pub auto_repeat_index: usize,
670 pub values: crate::OwnedSlice<GenericTrackListValue<LengthPercentage, Integer>>,
672 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
678}
679
680pub use self::GenericTrackList as TrackList;
681
682impl<L, I> TrackList<L, I> {
683 pub fn is_explicit(&self) -> bool {
686 !self.values.iter().any(|v| v.is_repeat())
687 }
688
689 pub fn has_auto_repeat(&self) -> bool {
691 self.auto_repeat_index < self.values.len()
692 }
693}
694
695impl<L: ToCss, I: ToCss> ToCss for TrackList<L, I> {
696 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
697 where
698 W: Write,
699 {
700 let mut values_iter = self.values.iter().peekable();
701 let mut line_names_iter = self.line_names.iter().peekable();
702
703 for idx in 0.. {
704 let names = line_names_iter.next().unwrap(); concat_serialize_idents("[", "]", names, " ", dest)?;
706
707 match values_iter.next() {
708 Some(value) => {
709 if !names.is_empty() {
710 dest.write_char(' ')?;
711 }
712
713 value.to_css(dest)?;
714 },
715 None => break,
716 }
717
718 if values_iter.peek().is_some()
719 || line_names_iter.peek().is_some_and(|v| !v.is_empty())
720 || (idx + 1 == self.auto_repeat_index)
721 {
722 dest.write_char(' ')?;
723 }
724 }
725
726 Ok(())
727 }
728}
729
730impl<L: ToTyped, I: ToTyped> ToTyped for TrackList<L, I> {
731 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
736 if self.values.len() != 1 {
737 return Err(());
738 }
739
740 if self.line_names.iter().any(|names| !names.is_empty()) {
741 return Err(());
742 }
743
744 self.values[0].to_typed(dest)
745 }
746}
747
748#[derive(
754 Clone,
755 Debug,
756 MallocSizeOf,
757 PartialEq,
758 SpecifiedValueInfo,
759 ToAnimatedValue,
760 ToComputedValue,
761 ToResolvedValue,
762 ToShmem,
763)]
764#[repr(C)]
765pub struct GenericNameRepeat<I> {
766 pub count: RepeatCount<I>,
769 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
771}
772
773pub use self::GenericNameRepeat as NameRepeat;
774
775impl<I: ToCss> ToCss for NameRepeat<I> {
776 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
777 where
778 W: Write,
779 {
780 dest.write_str("repeat(")?;
781 self.count.to_css(dest)?;
782 dest.write_char(',')?;
783
784 for names in self.line_names.iter() {
785 if names.is_empty() {
786 dest.write_str(" []")?;
789 } else {
790 concat_serialize_idents(" [", "]", names, " ", dest)?;
791 }
792 }
793
794 dest.write_char(')')
795 }
796}
797
798impl<I> NameRepeat<I> {
799 #[inline]
801 pub fn is_auto_fill(&self) -> bool {
802 matches!(self.count, RepeatCount::AutoFill)
803 }
804}
805
806#[derive(
808 Clone,
809 Debug,
810 MallocSizeOf,
811 PartialEq,
812 SpecifiedValueInfo,
813 ToAnimatedValue,
814 ToComputedValue,
815 ToResolvedValue,
816 ToShmem,
817)]
818#[repr(C, u8)]
819pub enum GenericLineNameListValue<I> {
820 LineNames(crate::OwnedSlice<CustomIdent>),
822 Repeat(GenericNameRepeat<I>),
824}
825
826pub use self::GenericLineNameListValue as LineNameListValue;
827
828impl<I: ToCss> ToCss for LineNameListValue<I> {
829 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
830 where
831 W: Write,
832 {
833 match *self {
834 Self::Repeat(ref r) => r.to_css(dest),
835 Self::LineNames(ref names) => {
836 dest.write_char('[')?;
837
838 if let Some((ref first, rest)) = names.split_first() {
839 first.to_css(dest)?;
840 for name in rest {
841 dest.write_char(' ')?;
842 name.to_css(dest)?;
843 }
844 }
845
846 dest.write_char(']')
847 },
848 }
849 }
850}
851
852#[derive(
859 Clone,
860 Debug,
861 Default,
862 MallocSizeOf,
863 PartialEq,
864 SpecifiedValueInfo,
865 ToAnimatedValue,
866 ToComputedValue,
867 ToResolvedValue,
868 ToShmem,
869)]
870#[repr(C)]
871pub struct GenericLineNameList<I> {
872 pub expanded_line_names_length: usize,
876 pub line_names: crate::OwnedSlice<GenericLineNameListValue<I>>,
878}
879
880pub use self::GenericLineNameList as LineNameList;
881
882impl<I: ToCss> ToCss for LineNameList<I> {
883 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
884 where
885 W: Write,
886 {
887 dest.write_str("subgrid")?;
888
889 for value in self.line_names.iter() {
890 dest.write_char(' ')?;
891 value.to_css(dest)?;
892 }
893
894 Ok(())
895 }
896}
897
898#[derive(
900 Animate,
901 Clone,
902 Debug,
903 MallocSizeOf,
904 PartialEq,
905 SpecifiedValueInfo,
906 ToAnimatedValue,
907 ToComputedValue,
908 ToCss,
909 ToResolvedValue,
910 ToShmem,
911 ToTyped,
912)]
913#[value_info(other_values = "subgrid")]
914#[repr(C, u8)]
915pub enum GenericGridTemplateComponent<L, I> {
916 None,
918 TrackList(
920 #[animation(field_bound)]
921 #[compute(field_bound)]
922 #[resolve(field_bound)]
923 #[shmem(field_bound)]
924 Box<GenericTrackList<L, I>>,
925 ),
926 #[animation(error)]
929 #[typed(skip)]
930 Subgrid(Box<GenericLineNameList<I>>),
931 #[typed(skip)]
934 Masonry,
935}
936
937pub use self::GenericGridTemplateComponent as GridTemplateComponent;
938
939impl<L, I> GridTemplateComponent<L, I> {
940 const INITIAL_VALUE: Self = Self::None;
942
943 pub fn track_list_len(&self) -> usize {
945 match *self {
946 GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(),
947 _ => 0,
948 }
949 }
950
951 pub fn is_initial(&self) -> bool {
953 matches!(*self, Self::None) }
955}
956
957impl<L, I> Default for GridTemplateComponent<L, I> {
958 #[inline]
959 fn default() -> Self {
960 Self::INITIAL_VALUE
961 }
962}