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