grid.rs (26570B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Generic types for the handling of 6 //! [grids](https://drafts.csswg.org/css-grid/). 7 8 use crate::derives::*; 9 use crate::parser::{Parse, ParserContext}; 10 use crate::values::specified; 11 use crate::values::{CSSFloat, CustomIdent}; 12 use crate::{One, Zero}; 13 use cssparser::Parser; 14 use std::fmt::{self, Write}; 15 use std::{cmp, usize}; 16 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; 17 18 /// These are the limits that we choose to clamp grid line numbers to. 19 /// http://drafts.csswg.org/css-grid/#overlarge-grids 20 /// line_num is clamped to this range at parse time. 21 pub const MIN_GRID_LINE: i32 = -10000; 22 /// See above. 23 pub const MAX_GRID_LINE: i32 = 10000; 24 25 /// A `<grid-line>` type. 26 /// 27 /// <https://drafts.csswg.org/css-grid/#typedef-grid-row-start-grid-line> 28 #[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 pub struct GenericGridLine<Integer> { 42 /// A custom identifier for named lines, or the empty atom otherwise. 43 /// 44 /// <https://drafts.csswg.org/css-grid/#grid-placement-slot> 45 pub ident: CustomIdent, 46 /// Denotes the nth grid line from grid item's placement. 47 /// 48 /// This is clamped by MIN_GRID_LINE and MAX_GRID_LINE. 49 /// 50 /// NOTE(emilio): If we ever allow animating these we need to either do 51 /// something more complicated for the clamping, or do this clamping at 52 /// used-value time. 53 pub line_num: Integer, 54 /// Flag to check whether it's a `span` keyword. 55 pub is_span: bool, 56 } 57 58 pub use self::GenericGridLine as GridLine; 59 60 impl<Integer> GridLine<Integer> 61 where 62 Integer: PartialEq + Zero, 63 { 64 /// The `auto` value. 65 pub fn auto() -> Self { 66 Self { 67 is_span: false, 68 line_num: Zero::zero(), 69 ident: CustomIdent(atom!("")), 70 } 71 } 72 73 /// Check whether this `<grid-line>` represents an `auto` value. 74 pub fn is_auto(&self) -> bool { 75 self.ident.0 == atom!("") && self.line_num.is_zero() && !self.is_span 76 } 77 78 /// Check whether this `<grid-line>` represents a `<custom-ident>` value. 79 pub fn is_ident_only(&self) -> bool { 80 self.ident.0 != atom!("") && self.line_num.is_zero() && !self.is_span 81 } 82 83 /// Check if `self` makes `other` omittable according to the rules at: 84 /// https://drafts.csswg.org/css-grid/#propdef-grid-column 85 /// https://drafts.csswg.org/css-grid/#propdef-grid-area 86 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 95 impl<Integer> ToCss for GridLine<Integer> 96 where 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 // 1. `auto` 104 if self.is_auto() { 105 return dest.write_str("auto"); 106 } 107 108 // 2. `<custom-ident>` 109 if self.is_ident_only() { 110 return self.ident.to_css(dest); 111 } 112 113 // 3. `[ span && [ <integer [1,∞]> || <custom-ident> ] ]` 114 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 // We omit `line_num` if 120 // 1. we don't specify it, or 121 // 2. it is the default value, i.e. 1.0, and the ident is specified. 122 // https://drafts.csswg.org/css-grid/#grid-placement-span-int 123 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 // 4. `[ <integer [-∞,-1]> | <integer [1,∞]> ] && <custom-ident>? ]` 136 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 146 impl 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 // <custom-ident> | [ <integer> && <custom-ident>? ] | [ span && [ <integer> || <custom-ident> ] ] 157 // This <grid-line> horror is simply, 158 // [ span? && [ <custom-ident> || <integer> ] ] 159 // And, for some magical reason, "span" should be the first or last value and not in-between. 160 let mut val_before_span = false; 161 162 for _ in 0..3 { 163 // Maximum possible entities for <grid-line> 164 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 // FIXME(emilio): Probably shouldn't reject if it's calc()... 177 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 // NOTE(emilio): `span` is consumed above, so we only need to 191 // reject `auto`. 192 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 // disallow negative integers for grid spans 206 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 207 } 208 } else if grid_line.ident.0 == atom!("") { 209 // integer could be omitted 210 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 211 } 212 } 213 214 Ok(grid_line) 215 } 216 } 217 218 /// A track breadth for explicit grid track sizing. It's generic solely to 219 /// avoid re-implementing it for the computed type. 220 /// 221 /// <https://drafts.csswg.org/css-grid/#typedef-track-breadth> 222 #[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)] 236 pub enum GenericTrackBreadth<L> { 237 /// The generic type is almost always a non-negative `<length-percentage>` 238 Breadth(L), 239 /// A flex fraction specified in `fr` units. 240 #[css(dimension)] 241 Fr(CSSFloat), 242 /// `auto` 243 Auto, 244 /// `min-content` 245 MinContent, 246 /// `max-content` 247 MaxContent, 248 } 249 250 pub use self::GenericTrackBreadth as TrackBreadth; 251 252 impl<L> TrackBreadth<L> { 253 /// Check whether this is a `<fixed-breadth>` (i.e., it only has `<length-percentage>`) 254 /// 255 /// <https://drafts.csswg.org/css-grid/#typedef-fixed-breadth> 256 #[inline] 257 pub fn is_fixed(&self) -> bool { 258 matches!(*self, TrackBreadth::Breadth(..)) 259 } 260 } 261 262 /// A `<track-size>` type for explicit grid track sizing. Like `<track-breadth>`, this is 263 /// generic only to avoid code bloat. It only takes `<length-percentage>` 264 /// 265 /// <https://drafts.csswg.org/css-grid/#typedef-track-size> 266 #[derive( 267 Clone, 268 Debug, 269 MallocSizeOf, 270 PartialEq, 271 SpecifiedValueInfo, 272 ToAnimatedValue, 273 ToComputedValue, 274 ToResolvedValue, 275 ToShmem, 276 )] 277 #[repr(C, u8)] 278 pub enum GenericTrackSize<L> { 279 /// A flexible `<track-breadth>` 280 Breadth(GenericTrackBreadth<L>), 281 /// A `minmax` function for a range over an inflexible `<track-breadth>` 282 /// and a flexible `<track-breadth>` 283 /// 284 /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-minmax> 285 #[css(function)] 286 Minmax(GenericTrackBreadth<L>, GenericTrackBreadth<L>), 287 /// A `fit-content` function. 288 /// 289 /// This stores a TrackBreadth<L> for convenience, but it can only be a 290 /// LengthPercentage. 291 /// 292 /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-fit-content> 293 #[css(function)] 294 FitContent(GenericTrackBreadth<L>), 295 } 296 297 pub use self::GenericTrackSize as TrackSize; 298 299 impl<L> TrackSize<L> { 300 /// The initial value. 301 const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto); 302 303 /// Returns the initial value. 304 pub const fn initial_value() -> Self { 305 Self::INITIAL_VALUE 306 } 307 308 /// Returns true if `self` is the initial value. 309 pub fn is_initial(&self) -> bool { 310 matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 311 } 312 313 /// Check whether this is a `<fixed-size>` 314 /// 315 /// <https://drafts.csswg.org/css-grid/#typedef-fixed-size> 316 pub fn is_fixed(&self) -> bool { 317 match *self { 318 TrackSize::Breadth(ref breadth) => breadth.is_fixed(), 319 // For minmax function, it could be either 320 // minmax(<fixed-breadth>, <track-breadth>) or minmax(<inflexible-breadth>, <fixed-breadth>), 321 // and since both variants are a subset of minmax(<inflexible-breadth>, <track-breadth>), we only 322 // need to make sure that they're fixed. So, we don't have to modify the parsing function. 323 TrackSize::Minmax(ref breadth_1, ref breadth_2) => { 324 if breadth_1.is_fixed() { 325 return true; // the second value is always a <track-breadth> 326 } 327 328 match *breadth_1 { 329 TrackBreadth::Fr(_) => false, // should be <inflexible-breadth> at this point 330 _ => breadth_2.is_fixed(), 331 } 332 }, 333 TrackSize::FitContent(_) => false, 334 } 335 } 336 } 337 338 impl<L> Default for TrackSize<L> { 339 fn default() -> Self { 340 Self::initial_value() 341 } 342 } 343 344 impl<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 // According to gecko minmax(auto, <flex>) is equivalent to <flex>, 353 // and both are serialized as <flex>. 354 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 /// A `<track-size>+`. 376 /// We use the empty slice as `auto`, and always parse `auto` as an empty slice. 377 /// This means it's impossible to have a slice containing only one auto item. 378 #[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)] 392 pub struct GenericImplicitGridTracks<T>( 393 #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice<T>, 394 ); 395 396 pub use self::GenericImplicitGridTracks as ImplicitGridTracks; 397 398 impl<T: fmt::Debug + Default + PartialEq> ImplicitGridTracks<T> { 399 /// Returns true if current value is same as its initial value (i.e. auto). 400 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 409 /// Helper function for serializing identifiers with a prefix and suffix, used 410 /// for serializing <line-names> (in grid). 411 pub 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 418 where 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 /// The initial argument of the `repeat` function. 436 /// 437 /// <https://drafts.csswg.org/css-grid/#typedef-track-repeat> 438 #[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)] 452 pub enum RepeatCount<Integer> { 453 /// A positive integer. This is allowed only for `<track-repeat>` and `<fixed-repeat>` 454 Number(Integer), 455 /// An `<auto-fill>` keyword allowed only for `<auto-repeat>` 456 AutoFill, 457 /// An `<auto-fit>` keyword allowed only for `<auto-repeat>` 458 AutoFit, 459 } 460 461 impl 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 /// The structure containing `<line-names>` and `<track-size>` values. 480 #[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)] 493 pub struct GenericTrackRepeat<L, I> { 494 /// The number of times for the value to be repeated (could also be `auto-fit` or `auto-fill`) 495 pub count: RepeatCount<I>, 496 /// `<line-names>` accompanying `<track_size>` values. 497 /// 498 /// If there's no `<line-names>`, then it's represented by an empty vector. 499 /// For N `<track-size>` values, there will be N+1 `<line-names>`, and so this vector's 500 /// length is always one value more than that of the `<track-size>`. 501 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>, 502 /// `<track-size>` values. 503 pub track_sizes: crate::OwnedSlice<GenericTrackSize<L>>, 504 } 505 506 pub use self::GenericTrackRepeat as TrackRepeat; 507 508 impl<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 /// Track list values. Can be <track-size> or <track-repeat> 543 #[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)] 557 pub enum GenericTrackListValue<LengthPercentage, Integer> { 558 /// A <track-size> value. 559 TrackSize(#[animation(field_bound)] GenericTrackSize<LengthPercentage>), 560 /// A <track-repeat> value. 561 TrackRepeat(#[animation(field_bound)] GenericTrackRepeat<LengthPercentage, Integer>), 562 } 563 564 pub use self::GenericTrackListValue as TrackListValue; 565 566 impl<L, I> TrackListValue<L, I> { 567 // FIXME: can't use TrackSize::initial_value() here b/c rustc error "is not yet stable as a const fn" 568 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 /// Returns true if `self` is the initial value. 575 pub fn is_initial(&self) -> bool { 576 matches!( 577 *self, 578 TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto)) 579 ) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 580 } 581 } 582 583 impl<L, I> Default for TrackListValue<L, I> { 584 #[inline] 585 fn default() -> Self { 586 Self::INITIAL_VALUE 587 } 588 } 589 590 /// A grid `<track-list>` type. 591 /// 592 /// <https://drafts.csswg.org/css-grid/#typedef-track-list> 593 #[derive( 594 Clone, 595 Debug, 596 MallocSizeOf, 597 PartialEq, 598 SpecifiedValueInfo, 599 ToAnimatedValue, 600 ToComputedValue, 601 ToResolvedValue, 602 ToShmem, 603 )] 604 #[repr(C)] 605 pub struct GenericTrackList<LengthPercentage, Integer> { 606 /// The index in `values` where our `<auto-repeat>` value is, if in bounds. 607 #[css(skip)] 608 pub auto_repeat_index: usize, 609 /// A vector of `<track-size> | <track-repeat>` values. 610 pub values: crate::OwnedSlice<GenericTrackListValue<LengthPercentage, Integer>>, 611 /// `<line-names>` accompanying `<track-size> | <track-repeat>` values. 612 /// 613 /// If there's no `<line-names>`, then it's represented by an empty vector. 614 /// For N values, there will be N+1 `<line-names>`, and so this vector's 615 /// length is always one value more than that of the `<track-size>`. 616 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>, 617 } 618 619 pub use self::GenericTrackList as TrackList; 620 621 impl<L, I> TrackList<L, I> { 622 /// Whether this track list is an explicit track list (that is, doesn't have 623 /// any repeat values). 624 pub fn is_explicit(&self) -> bool { 625 !self.values.iter().any(|v| v.is_repeat()) 626 } 627 628 /// Whether this track list has an `<auto-repeat>` value. 629 pub fn has_auto_repeat(&self) -> bool { 630 self.auto_repeat_index < self.values.len() 631 } 632 } 633 634 impl<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(); // This should exist! 644 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 /// The `<name-repeat>` for subgrids. 670 /// 671 /// <name-repeat> = repeat( [ <integer [1,∞]> | auto-fill ], <line-names>+) 672 /// 673 /// https://drafts.csswg.org/css-grid/#typedef-name-repeat 674 #[derive( 675 Clone, 676 Debug, 677 MallocSizeOf, 678 PartialEq, 679 SpecifiedValueInfo, 680 ToAnimatedValue, 681 ToComputedValue, 682 ToResolvedValue, 683 ToShmem, 684 )] 685 #[repr(C)] 686 pub struct GenericNameRepeat<I> { 687 /// The number of times for the value to be repeated (could also be `auto-fill`). 688 /// Note: `RepeatCount` accepts `auto-fit`, so we should reject it after parsing it. 689 pub count: RepeatCount<I>, 690 /// This represents `<line-names>+`. The length of the outer vector is at least one. 691 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>, 692 } 693 694 pub use self::GenericNameRepeat as NameRepeat; 695 696 impl<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 // Note: concat_serialize_idents() skip the empty list so we have to handle it 708 // manually for NameRepeat. 709 dest.write_str(" []")?; 710 } else { 711 concat_serialize_idents(" [", "]", names, " ", dest)?; 712 } 713 } 714 715 dest.write_char(')') 716 } 717 } 718 719 impl<I> NameRepeat<I> { 720 /// Returns true if it is auto-fill. 721 #[inline] 722 pub fn is_auto_fill(&self) -> bool { 723 matches!(self.count, RepeatCount::AutoFill) 724 } 725 } 726 727 /// A single value for `<line-names>` or `<name-repeat>`. 728 #[derive( 729 Clone, 730 Debug, 731 MallocSizeOf, 732 PartialEq, 733 SpecifiedValueInfo, 734 ToAnimatedValue, 735 ToComputedValue, 736 ToResolvedValue, 737 ToShmem, 738 )] 739 #[repr(C, u8)] 740 pub enum GenericLineNameListValue<I> { 741 /// `<line-names>`. 742 LineNames(crate::OwnedSlice<CustomIdent>), 743 /// `<name-repeat>`. 744 Repeat(GenericNameRepeat<I>), 745 } 746 747 pub use self::GenericLineNameListValue as LineNameListValue; 748 749 impl<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 /// The `<line-name-list>` for subgrids. 774 /// 775 /// <line-name-list> = [ <line-names> | <name-repeat> ]+ 776 /// <name-repeat> = repeat( [ <integer [1,∞]> | auto-fill ], <line-names>+) 777 /// 778 /// https://drafts.csswg.org/css-grid/#typedef-line-name-list 779 #[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)] 792 pub struct GenericLineNameList<I> { 793 /// The pre-computed length of line_names, without the length of repeat(auto-fill, ...). 794 // We precomputed this at parsing time, so we can avoid an extra loop when expanding 795 // repeat(auto-fill). 796 pub expanded_line_names_length: usize, 797 /// The line name list. 798 pub line_names: crate::OwnedSlice<GenericLineNameListValue<I>>, 799 } 800 801 pub use self::GenericLineNameList as LineNameList; 802 803 impl<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 /// Variants for `<grid-template-rows> | <grid-template-columns>` 820 #[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)] 836 pub enum GenericGridTemplateComponent<L, I> { 837 /// `none` value. 838 None, 839 /// The grid `<track-list>` 840 TrackList( 841 #[animation(field_bound)] 842 #[compute(field_bound)] 843 #[resolve(field_bound)] 844 #[shmem(field_bound)] 845 Box<GenericTrackList<L, I>>, 846 ), 847 /// A `subgrid <line-name-list>?` 848 /// TODO: Support animations for this after subgrid is addressed in [grid-2] spec. 849 #[animation(error)] 850 Subgrid(Box<GenericLineNameList<I>>), 851 /// `masonry` value. 852 /// https://github.com/w3c/csswg-drafts/issues/4650 853 Masonry, 854 } 855 856 pub use self::GenericGridTemplateComponent as GridTemplateComponent; 857 858 impl<L, I> GridTemplateComponent<L, I> { 859 /// The initial value. 860 const INITIAL_VALUE: Self = Self::None; 861 862 /// Returns length of the <track-list>s <track-size> 863 pub fn track_list_len(&self) -> usize { 864 match *self { 865 GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(), 866 _ => 0, 867 } 868 } 869 870 /// Returns true if `self` is the initial value. 871 pub fn is_initial(&self) -> bool { 872 matches!(*self, Self::None) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 873 } 874 } 875 876 impl<L, I> Default for GridTemplateComponent<L, I> { 877 #[inline] 878 fn default() -> Self { 879 Self::INITIAL_VALUE 880 } 881 }