Skip to main content

takumi_css/style/properties/grid/
grid_template_component.rs

1use std::mem::take;
2
3use cssparser::Parser;
4
5use super::write_space_separated;
6use crate::style::{
7  CssDescriptorKind, CssSyntaxKind, CssToken, FromCss, GridRepeatTrack, GridRepetitionCount,
8  GridTrackSize, MakeComputed, ParseResult, SizingContext, ToCss,
9};
10
11pub type GridTemplateComponents = Vec<GridTemplateComponent>;
12
13/// Parses a `[name1 name2 ...]` line-name block's body; caller consumes the opening bracket.
14pub(crate) fn parse_line_names<'i>(input: &mut Parser<'i, '_>) -> ParseResult<'i, Vec<String>> {
15  input.parse_nested_block(|i| {
16    let mut names = Vec::new();
17    while let Ok(name) = i.try_parse(Parser::expect_ident_cloned) {
18      names.push(name.as_ref().to_owned());
19    }
20    Ok(names)
21  })
22}
23
24/// Represents a track sizing function or a list of line names between tracks
25#[derive(Debug, Clone, PartialEq)]
26#[non_exhaustive]
27pub enum GridTemplateComponent {
28  /// A list of line names that apply to the current grid line (e.g., [a b])
29  LineNames(Vec<String>),
30  /// A single non-repeated track
31  Single(GridTrackSize),
32  /// Automatically generate grid tracks to fit the available space using the specified definite track lengths
33  /// Only valid if every track in template (not just the repetition) has a fixed size.
34  Repeat(GridRepetitionCount, Vec<GridRepeatTrack>),
35}
36
37impl MakeComputed for GridTemplateComponent {
38  fn make_computed(&mut self, sizing: &SizingContext) {
39    match self {
40      GridTemplateComponent::Single(size) => size.make_computed(sizing),
41      GridTemplateComponent::Repeat(_, tracks) => {
42        for track in tracks.iter_mut() {
43          track.make_computed(sizing);
44        }
45      }
46      _ => {}
47    }
48  }
49}
50
51impl<'i> FromCss<'i> for GridTemplateComponent {
52  fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
53    // Line name block: [name1 name2 ...]
54    if input.try_parse(Parser::expect_square_bracket_block).is_ok() {
55      return Ok(GridTemplateComponent::LineNames(parse_line_names(input)?));
56    }
57
58    if input
59      .try_parse(|i| i.expect_function_matching("repeat"))
60      .is_ok()
61    {
62      return input.parse_nested_block(|input| {
63        let repetition = GridRepetitionCount::from_css(input)?;
64        input.expect_comma()?;
65
66        let mut tracks: Vec<GridRepeatTrack> = Vec::new();
67        // Names encountered after a size belong to the NEXT track in repeat() context
68        let mut pending_leading_names: Vec<String> = Vec::new();
69        loop {
70          // Start with any pending names from the previous track's trailing names
71          let mut names: Vec<String> = take(&mut pending_leading_names);
72
73          // Capture any additional leading square-bracketed names before the size
74          while input.try_parse(Parser::expect_square_bracket_block).is_ok() {
75            names.extend(parse_line_names(input)?);
76          }
77
78          // If we cannot parse a size, stop the loop
79          let size = if let Ok(size) = input.try_parse(GridTrackSize::from_css) {
80            size
81          } else {
82            break;
83          };
84
85          // Collect trailing names, but assign them to the next track
86          while input.try_parse(Parser::expect_square_bracket_block).is_ok() {
87            pending_leading_names.extend(parse_line_names(input)?);
88          }
89
90          tracks.push(GridRepeatTrack {
91            size,
92            names,
93            end_names: None,
94          });
95        }
96
97        if tracks.is_empty() {
98          return Err(input.new_error_for_next_token());
99        }
100
101        // Any remaining pending names after the final size are the trailing names of the repeat fragment
102        if !pending_leading_names.is_empty()
103          && let Some(last) = tracks.last_mut()
104        {
105          last.end_names = Some(take(&mut pending_leading_names));
106        }
107
108        Ok(GridTemplateComponent::Repeat(repetition, tracks))
109      });
110    }
111
112    // Single track-size
113    let size = GridTrackSize::from_css(input)?;
114    Ok(GridTemplateComponent::Single(size))
115  }
116
117  const VALID_TOKENS: &'static [CssToken] = &[
118    CssToken::Syntax(CssSyntaxKind::LineNames),
119    CssToken::Descriptor(CssDescriptorKind::RepeatFn),
120    CssToken::Descriptor(CssDescriptorKind::MinmaxFn),
121    CssToken::Syntax(CssSyntaxKind::Length),
122  ];
123}
124
125impl<'i> FromCss<'i> for GridTemplateComponents {
126  fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
127    let mut components = Vec::new();
128    while let Ok(component) = GridTemplateComponent::from_css(input) {
129      components.push(component);
130    }
131    Ok(components)
132  }
133
134  const VALID_TOKENS: &'static [CssToken] = GridTemplateComponent::VALID_TOKENS;
135}
136
137pub(crate) fn collect_components_and_names(
138  components: &[GridTemplateComponent],
139  sizing: &SizingContext,
140) -> (Vec<taffy::GridTemplateComponent<String>>, Vec<Vec<String>>) {
141  let mut track_components = Vec::new();
142  let mut line_name_sets = Vec::new();
143  let mut pending_line_names = Vec::new();
144
145  for component in components {
146    match component {
147      GridTemplateComponent::LineNames(names) => {
148        if !names.is_empty() {
149          pending_line_names.extend_from_slice(names);
150        }
151      }
152      GridTemplateComponent::Single(track_size) => {
153        line_name_sets.push(take(&mut pending_line_names));
154        track_components.push(taffy::GridTemplateComponent::Single(
155          track_size.to_min_max(sizing),
156        ));
157      }
158      GridTemplateComponent::Repeat(repetition, tracks) => {
159        line_name_sets.push(take(&mut pending_line_names));
160
161        let track_sizes = tracks
162          .iter()
163          .map(|track| track.size.to_min_max(sizing))
164          .collect();
165        let mut inner_line_names = tracks
166          .iter()
167          .map(|track| track.names.to_owned())
168          .collect::<Vec<_>>();
169        inner_line_names.push(
170          tracks
171            .last()
172            .and_then(|track| track.end_names.clone())
173            .unwrap_or_default(),
174        );
175
176        track_components.push(taffy::GridTemplateComponent::Repeat(
177          taffy::GridTemplateRepetition {
178            count: (*repetition).into(),
179            tracks: track_sizes,
180            line_names: inner_line_names,
181          },
182        ));
183      }
184    }
185  }
186
187  line_name_sets.push(pending_line_names);
188
189  (track_components, line_name_sets)
190}
191
192impl ToCss for GridTemplateComponent {
193  fn to_css<W: std::fmt::Write>(&self, dest: &mut W) -> std::fmt::Result {
194    match self {
195      Self::LineNames(names) => {
196        dest.write_str("[")?;
197        write_space_separated(dest, names)?;
198        dest.write_str("]")
199      }
200      Self::Single(size) => size.to_css(dest),
201      Self::Repeat(count, tracks) => {
202        dest.write_str("repeat(")?;
203        count.to_css(dest)?;
204        dest.write_str(", ")?;
205        let mut first = true;
206        for track in tracks {
207          if !first {
208            dest.write_str(" ")?;
209          }
210          first = false;
211          track.to_css(dest)?;
212        }
213        dest.write_str(")")
214      }
215    }
216  }
217}
218
219#[cfg(test)]
220mod tests {
221  use crate::style::{GridLength, GridRepetitionKeyword};
222
223  use super::*;
224
225  #[test]
226  fn test_parse_template_component_repeat() {
227    assert_eq!(
228      GridTemplateComponent::from_str("repeat(auto-fill, [a] 1fr [b] 2fr)"),
229      Ok(GridTemplateComponent::Repeat(
230        GridRepetitionCount::Keyword(GridRepetitionKeyword::AutoFill),
231        vec![
232          GridRepeatTrack {
233            names: vec!["a".to_string()],
234            size: GridTrackSize::Fixed(GridLength::Fr(1.0)),
235            end_names: None
236          },
237          GridRepeatTrack {
238            names: vec!["b".to_string()],
239            size: GridTrackSize::Fixed(GridLength::Fr(2.0)),
240            end_names: None
241          }
242        ]
243      ))
244    );
245  }
246
247  #[test]
248  fn test_parse_repeat_without_track_size_is_error() {
249    assert!(GridTemplateComponent::from_str("repeat(2, [a])").is_err());
250  }
251}