1use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::values::specified;
11use crate::values::{CSSFloat, CustomIdent};
12use crate::{One, Zero};
13use cssparser::Parser;
14use std::fmt::{self, Write};
15use std::{cmp, usize};
16use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
17
18pub const MIN_GRID_LINE: i32 = -10000;
22pub const MAX_GRID_LINE: i32 = 10000;
24
25#[derive(
29 Clone,
30 Debug,
31 Default,
32 MallocSizeOf,
33 PartialEq,
34 SpecifiedValueInfo,
35 ToComputedValue,
36 ToResolvedValue,
37 ToShmem,
38 ToTyped,
39)]
40#[repr(C)]
41#[typed(todo_derive_fields)]
42pub struct GenericGridLine<Integer> {
43 pub ident: CustomIdent,
47 pub line_num: Integer,
55 pub is_span: bool,
57}
58
59pub use self::GenericGridLine as GridLine;
60
61impl<Integer> GridLine<Integer>
62where
63 Integer: PartialEq + Zero,
64{
65 pub fn auto() -> Self {
67 Self {
68 is_span: false,
69 line_num: Zero::zero(),
70 ident: CustomIdent(atom!("")),
71 }
72 }
73
74 pub fn is_auto(&self) -> bool {
76 self.ident.0 == atom!("") && self.line_num.is_zero() && !self.is_span
77 }
78
79 pub fn is_ident_only(&self) -> bool {
81 self.ident.0 != atom!("") && self.line_num.is_zero() && !self.is_span
82 }
83
84 pub fn can_omit(&self, other: &Self) -> bool {
88 if self.is_ident_only() {
89 self == other
90 } else {
91 other.is_auto()
92 }
93 }
94}
95
96impl<Integer> ToCss for GridLine<Integer>
97where
98 Integer: ToCss + PartialEq + Zero + One,
99{
100 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
101 where
102 W: Write,
103 {
104 if self.is_auto() {
106 return dest.write_str("auto");
107 }
108
109 if self.is_ident_only() {
111 return self.ident.to_css(dest);
112 }
113
114 let has_ident = self.ident.0 != atom!("");
116 if self.is_span {
117 dest.write_str("span")?;
118 debug_assert!(!self.line_num.is_zero() || has_ident);
119
120 if !self.line_num.is_zero() && !(self.line_num.is_one() && has_ident) {
125 dest.write_char(' ')?;
126 self.line_num.to_css(dest)?;
127 }
128
129 if has_ident {
130 dest.write_char(' ')?;
131 self.ident.to_css(dest)?;
132 }
133 return Ok(());
134 }
135
136 debug_assert!(!self.line_num.is_zero());
138 self.line_num.to_css(dest)?;
139 if has_ident {
140 dest.write_char(' ')?;
141 self.ident.to_css(dest)?;
142 }
143 Ok(())
144 }
145}
146
147impl Parse for GridLine<specified::Integer> {
148 fn parse<'i, 't>(
149 context: &ParserContext,
150 input: &mut Parser<'i, 't>,
151 ) -> Result<Self, ParseError<'i>> {
152 let mut grid_line = Self::auto();
153 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
154 return Ok(grid_line);
155 }
156
157 let mut val_before_span = false;
162
163 for _ in 0..3 {
164 let location = input.current_source_location();
166 if input.try_parse(|i| i.expect_ident_matching("span")).is_ok() {
167 if grid_line.is_span {
168 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
169 }
170
171 if !grid_line.line_num.is_zero() || grid_line.ident.0 != atom!("") {
172 val_before_span = true;
173 }
174
175 grid_line.is_span = true;
176 } else if let Ok(i) = input.try_parse(|i| specified::Integer::parse(context, i)) {
177 let value = i.value();
179 if value == 0 || val_before_span || !grid_line.line_num.is_zero() {
180 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
181 }
182
183 grid_line.line_num = specified::Integer::new(cmp::max(
184 MIN_GRID_LINE,
185 cmp::min(value, MAX_GRID_LINE),
186 ));
187 } else if let Ok(name) = input.try_parse(|i| CustomIdent::parse(i, &["auto"])) {
188 if val_before_span || grid_line.ident.0 != atom!("") {
189 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
190 }
191 grid_line.ident = name;
194 } else {
195 break;
196 }
197 }
198
199 if grid_line.is_auto() {
200 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
201 }
202
203 if grid_line.is_span {
204 if !grid_line.line_num.is_zero() {
205 if grid_line.line_num.value() <= 0 {
206 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
208 }
209 } else if grid_line.ident.0 == atom!("") {
210 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
212 }
213 }
214
215 Ok(grid_line)
216 }
217}
218
219#[derive(
224 Animate,
225 Clone,
226 Debug,
227 MallocSizeOf,
228 PartialEq,
229 SpecifiedValueInfo,
230 ToAnimatedValue,
231 ToComputedValue,
232 ToCss,
233 ToResolvedValue,
234 ToShmem,
235)]
236#[repr(C, u8)]
237pub enum GenericTrackBreadth<L> {
238 Breadth(L),
240 #[css(dimension)]
242 Fr(CSSFloat),
243 Auto,
245 MinContent,
247 MaxContent,
249}
250
251pub use self::GenericTrackBreadth as TrackBreadth;
252
253impl<L> TrackBreadth<L> {
254 #[inline]
258 pub fn is_fixed(&self) -> bool {
259 matches!(*self, TrackBreadth::Breadth(..))
260 }
261}
262
263#[derive(
268 Clone,
269 Debug,
270 MallocSizeOf,
271 PartialEq,
272 SpecifiedValueInfo,
273 ToAnimatedValue,
274 ToComputedValue,
275 ToResolvedValue,
276 ToShmem,
277)]
278#[repr(C, u8)]
279pub enum GenericTrackSize<L> {
280 Breadth(GenericTrackBreadth<L>),
282 #[css(function)]
287 Minmax(GenericTrackBreadth<L>, GenericTrackBreadth<L>),
288 #[css(function)]
295 FitContent(GenericTrackBreadth<L>),
296}
297
298pub use self::GenericTrackSize as TrackSize;
299
300impl<L> TrackSize<L> {
301 const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto);
303
304 pub const fn initial_value() -> Self {
306 Self::INITIAL_VALUE
307 }
308
309 pub fn is_initial(&self) -> bool {
311 matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) }
313
314 pub fn is_fixed(&self) -> bool {
318 match *self {
319 TrackSize::Breadth(ref breadth) => breadth.is_fixed(),
320 TrackSize::Minmax(ref breadth_1, ref breadth_2) => {
325 if breadth_1.is_fixed() {
326 return true; }
328
329 match *breadth_1 {
330 TrackBreadth::Fr(_) => false, _ => breadth_2.is_fixed(),
332 }
333 },
334 TrackSize::FitContent(_) => false,
335 }
336 }
337}
338
339impl<L> Default for TrackSize<L> {
340 fn default() -> Self {
341 Self::initial_value()
342 }
343}
344
345impl<L: ToCss> ToCss for TrackSize<L> {
346 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
347 where
348 W: Write,
349 {
350 match *self {
351 TrackSize::Breadth(ref breadth) => breadth.to_css(dest),
352 TrackSize::Minmax(ref min, ref max) => {
353 if let TrackBreadth::Auto = *min {
356 if let TrackBreadth::Fr(_) = *max {
357 return max.to_css(dest);
358 }
359 }
360
361 dest.write_str("minmax(")?;
362 min.to_css(dest)?;
363 dest.write_str(", ")?;
364 max.to_css(dest)?;
365 dest.write_char(')')
366 },
367 TrackSize::FitContent(ref lp) => {
368 dest.write_str("fit-content(")?;
369 lp.to_css(dest)?;
370 dest.write_char(')')
371 },
372 }
373 }
374}
375
376#[derive(
380 Clone,
381 Debug,
382 Default,
383 MallocSizeOf,
384 PartialEq,
385 SpecifiedValueInfo,
386 ToComputedValue,
387 ToCss,
388 ToResolvedValue,
389 ToShmem,
390 ToTyped,
391)]
392#[repr(transparent)]
393#[typed(todo_derive_fields)]
394pub struct GenericImplicitGridTracks<T>(
395 #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice<T>,
396);
397
398pub use self::GenericImplicitGridTracks as ImplicitGridTracks;
399
400impl<T: fmt::Debug + Default + PartialEq> ImplicitGridTracks<T> {
401 pub fn is_initial(&self) -> bool {
403 debug_assert_ne!(
404 *self,
405 ImplicitGridTracks(crate::OwnedSlice::from(vec![Default::default()]))
406 );
407 self.0.is_empty()
408 }
409}
410
411pub fn concat_serialize_idents<W>(
414 prefix: &str,
415 suffix: &str,
416 slice: &[CustomIdent],
417 sep: &str,
418 dest: &mut CssWriter<W>,
419) -> fmt::Result
420where
421 W: Write,
422{
423 if let Some((ref first, rest)) = slice.split_first() {
424 dest.write_str(prefix)?;
425 first.to_css(dest)?;
426 for thing in rest {
427 dest.write_str(sep)?;
428 thing.to_css(dest)?;
429 }
430
431 dest.write_str(suffix)?;
432 }
433
434 Ok(())
435}
436
437#[derive(
441 Clone,
442 Copy,
443 Debug,
444 MallocSizeOf,
445 PartialEq,
446 SpecifiedValueInfo,
447 ToAnimatedValue,
448 ToComputedValue,
449 ToCss,
450 ToResolvedValue,
451 ToShmem,
452)]
453#[repr(C, u8)]
454pub enum RepeatCount<Integer> {
455 Number(Integer),
457 AutoFill,
459 AutoFit,
461}
462
463impl Parse for RepeatCount<specified::Integer> {
464 fn parse<'i, 't>(
465 context: &ParserContext,
466 input: &mut Parser<'i, 't>,
467 ) -> Result<Self, ParseError<'i>> {
468 if let Ok(mut i) = input.try_parse(|i| specified::Integer::parse_positive(context, i)) {
469 if i.value() > MAX_GRID_LINE {
470 i = specified::Integer::new(MAX_GRID_LINE);
471 }
472 return Ok(RepeatCount::Number(i));
473 }
474 try_match_ident_ignore_ascii_case! { input,
475 "auto-fill" => Ok(RepeatCount::AutoFill),
476 "auto-fit" => Ok(RepeatCount::AutoFit),
477 }
478 }
479}
480
481#[derive(
483 Clone,
484 Debug,
485 MallocSizeOf,
486 PartialEq,
487 SpecifiedValueInfo,
488 ToAnimatedValue,
489 ToComputedValue,
490 ToResolvedValue,
491 ToShmem,
492)]
493#[css(function = "repeat")]
494#[repr(C)]
495pub struct GenericTrackRepeat<L, I> {
496 pub count: RepeatCount<I>,
498 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
504 pub track_sizes: crate::OwnedSlice<GenericTrackSize<L>>,
506}
507
508pub use self::GenericTrackRepeat as TrackRepeat;
509
510impl<L: ToCss, I: ToCss> ToCss for TrackRepeat<L, I> {
511 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
512 where
513 W: Write,
514 {
515 dest.write_str("repeat(")?;
516 self.count.to_css(dest)?;
517 dest.write_str(", ")?;
518
519 let mut line_names_iter = self.line_names.iter();
520 for (i, (ref size, ref names)) in self
521 .track_sizes
522 .iter()
523 .zip(&mut line_names_iter)
524 .enumerate()
525 {
526 if i > 0 {
527 dest.write_char(' ')?;
528 }
529
530 concat_serialize_idents("[", "] ", names, " ", dest)?;
531 size.to_css(dest)?;
532 }
533
534 if let Some(line_names_last) = line_names_iter.next() {
535 concat_serialize_idents(" [", "]", line_names_last, " ", dest)?;
536 }
537
538 dest.write_char(')')?;
539
540 Ok(())
541 }
542}
543
544#[derive(
546 Animate,
547 Clone,
548 Debug,
549 MallocSizeOf,
550 PartialEq,
551 SpecifiedValueInfo,
552 ToAnimatedValue,
553 ToComputedValue,
554 ToCss,
555 ToResolvedValue,
556 ToShmem,
557)]
558#[repr(C, u8)]
559pub enum GenericTrackListValue<LengthPercentage, Integer> {
560 TrackSize(#[animation(field_bound)] GenericTrackSize<LengthPercentage>),
562 TrackRepeat(#[animation(field_bound)] GenericTrackRepeat<LengthPercentage, Integer>),
564}
565
566pub use self::GenericTrackListValue as TrackListValue;
567
568impl<L, I> TrackListValue<L, I> {
569 const INITIAL_VALUE: Self = TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto));
571
572 fn is_repeat(&self) -> bool {
573 matches!(*self, TrackListValue::TrackRepeat(..))
574 }
575
576 pub fn is_initial(&self) -> bool {
578 matches!(
579 *self,
580 TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto))
581 ) }
583}
584
585impl<L, I> Default for TrackListValue<L, I> {
586 #[inline]
587 fn default() -> Self {
588 Self::INITIAL_VALUE
589 }
590}
591
592#[derive(
596 Clone,
597 Debug,
598 MallocSizeOf,
599 PartialEq,
600 SpecifiedValueInfo,
601 ToAnimatedValue,
602 ToComputedValue,
603 ToResolvedValue,
604 ToShmem,
605)]
606#[repr(C)]
607pub struct GenericTrackList<LengthPercentage, Integer> {
608 #[css(skip)]
610 pub auto_repeat_index: usize,
611 pub values: crate::OwnedSlice<GenericTrackListValue<LengthPercentage, Integer>>,
613 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
619}
620
621pub use self::GenericTrackList as TrackList;
622
623impl<L, I> TrackList<L, I> {
624 pub fn is_explicit(&self) -> bool {
627 !self.values.iter().any(|v| v.is_repeat())
628 }
629
630 pub fn has_auto_repeat(&self) -> bool {
632 self.auto_repeat_index < self.values.len()
633 }
634}
635
636impl<L: ToCss, I: ToCss> ToCss for TrackList<L, I> {
637 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
638 where
639 W: Write,
640 {
641 let mut values_iter = self.values.iter().peekable();
642 let mut line_names_iter = self.line_names.iter().peekable();
643
644 for idx in 0.. {
645 let names = line_names_iter.next().unwrap(); concat_serialize_idents("[", "]", names, " ", dest)?;
647
648 match values_iter.next() {
649 Some(value) => {
650 if !names.is_empty() {
651 dest.write_char(' ')?;
652 }
653
654 value.to_css(dest)?;
655 },
656 None => break,
657 }
658
659 if values_iter.peek().is_some()
660 || line_names_iter.peek().map_or(false, |v| !v.is_empty())
661 || (idx + 1 == self.auto_repeat_index)
662 {
663 dest.write_char(' ')?;
664 }
665 }
666
667 Ok(())
668 }
669}
670
671#[derive(
677 Clone,
678 Debug,
679 MallocSizeOf,
680 PartialEq,
681 SpecifiedValueInfo,
682 ToAnimatedValue,
683 ToComputedValue,
684 ToResolvedValue,
685 ToShmem,
686)]
687#[repr(C)]
688pub struct GenericNameRepeat<I> {
689 pub count: RepeatCount<I>,
692 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
694}
695
696pub use self::GenericNameRepeat as NameRepeat;
697
698impl<I: ToCss> ToCss for NameRepeat<I> {
699 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
700 where
701 W: Write,
702 {
703 dest.write_str("repeat(")?;
704 self.count.to_css(dest)?;
705 dest.write_char(',')?;
706
707 for ref names in self.line_names.iter() {
708 if names.is_empty() {
709 dest.write_str(" []")?;
712 } else {
713 concat_serialize_idents(" [", "]", names, " ", dest)?;
714 }
715 }
716
717 dest.write_char(')')
718 }
719}
720
721impl<I> NameRepeat<I> {
722 #[inline]
724 pub fn is_auto_fill(&self) -> bool {
725 matches!(self.count, RepeatCount::AutoFill)
726 }
727}
728
729#[derive(
731 Clone,
732 Debug,
733 MallocSizeOf,
734 PartialEq,
735 SpecifiedValueInfo,
736 ToAnimatedValue,
737 ToComputedValue,
738 ToResolvedValue,
739 ToShmem,
740)]
741#[repr(C, u8)]
742pub enum GenericLineNameListValue<I> {
743 LineNames(crate::OwnedSlice<CustomIdent>),
745 Repeat(GenericNameRepeat<I>),
747}
748
749pub use self::GenericLineNameListValue as LineNameListValue;
750
751impl<I: ToCss> ToCss for LineNameListValue<I> {
752 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
753 where
754 W: Write,
755 {
756 match *self {
757 Self::Repeat(ref r) => r.to_css(dest),
758 Self::LineNames(ref names) => {
759 dest.write_char('[')?;
760
761 if let Some((ref first, rest)) = names.split_first() {
762 first.to_css(dest)?;
763 for name in rest {
764 dest.write_char(' ')?;
765 name.to_css(dest)?;
766 }
767 }
768
769 dest.write_char(']')
770 },
771 }
772 }
773}
774
775#[derive(
782 Clone,
783 Debug,
784 Default,
785 MallocSizeOf,
786 PartialEq,
787 SpecifiedValueInfo,
788 ToAnimatedValue,
789 ToComputedValue,
790 ToResolvedValue,
791 ToShmem,
792)]
793#[repr(C)]
794pub struct GenericLineNameList<I> {
795 pub expanded_line_names_length: usize,
799 pub line_names: crate::OwnedSlice<GenericLineNameListValue<I>>,
801}
802
803pub use self::GenericLineNameList as LineNameList;
804
805impl<I: ToCss> ToCss for LineNameList<I> {
806 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
807 where
808 W: Write,
809 {
810 dest.write_str("subgrid")?;
811
812 for value in self.line_names.iter() {
813 dest.write_char(' ')?;
814 value.to_css(dest)?;
815 }
816
817 Ok(())
818 }
819}
820
821#[derive(
823 Animate,
824 Clone,
825 Debug,
826 MallocSizeOf,
827 PartialEq,
828 SpecifiedValueInfo,
829 ToAnimatedValue,
830 ToComputedValue,
831 ToCss,
832 ToResolvedValue,
833 ToShmem,
834 ToTyped,
835)]
836#[value_info(other_values = "subgrid")]
837#[repr(C, u8)]
838#[typed(todo_derive_fields)]
839pub enum GenericGridTemplateComponent<L, I> {
840 None,
842 TrackList(
844 #[animation(field_bound)]
845 #[compute(field_bound)]
846 #[resolve(field_bound)]
847 #[shmem(field_bound)]
848 Box<GenericTrackList<L, I>>,
849 ),
850 #[animation(error)]
853 Subgrid(Box<GenericLineNameList<I>>),
854 Masonry,
857}
858
859pub use self::GenericGridTemplateComponent as GridTemplateComponent;
860
861impl<L, I> GridTemplateComponent<L, I> {
862 const INITIAL_VALUE: Self = Self::None;
864
865 pub fn track_list_len(&self) -> usize {
867 match *self {
868 GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(),
869 _ => 0,
870 }
871 }
872
873 pub fn is_initial(&self) -> bool {
875 matches!(*self, Self::None) }
877}
878
879impl<L, I> Default for GridTemplateComponent<L, I> {
880 #[inline]
881 fn default() -> Self {
882 Self::INITIAL_VALUE
883 }
884}