1use std::fmt;
5
6use bevy::ui::{GridPlacement, GridTrack, RepeatedGridTrack};
7use serde::de::{self, Deserializer, Visitor};
8
9use super::decode_warn;
10
11fn split_tracks(s: &str) -> Vec<String> {
14 let mut out = Vec::new();
15 let mut depth = 0usize;
16 let mut cur = String::new();
17 for ch in s.chars() {
18 match ch {
19 '(' => {
20 depth += 1;
21 cur.push(ch);
22 }
23 ')' => {
24 depth = depth.saturating_sub(1);
25 cur.push(ch);
26 }
27 c if c.is_whitespace() && depth == 0 => {
28 if !cur.is_empty() {
29 out.push(std::mem::take(&mut cur));
30 }
31 }
32 c => cur.push(c),
33 }
34 }
35 if !cur.is_empty() {
36 out.push(cur);
37 }
38 out
39}
40
41fn single_track(token: &str) -> Option<GridTrack> {
44 let t = token.trim();
45 match t {
46 "auto" => return Some(GridTrack::auto()),
47 "min-content" => return Some(GridTrack::min_content()),
48 "max-content" => return Some(GridTrack::max_content()),
49 _ => {}
50 }
51 let parse = |num: &str| num.trim().parse::<f32>().ok();
52 if let Some(v) = t.strip_suffix("fr").and_then(parse) {
53 Some(GridTrack::fr(v))
54 } else if let Some(v) = t.strip_suffix("flex").and_then(parse) {
55 Some(GridTrack::flex(v))
56 } else if let Some(v) = t.strip_suffix("px").and_then(parse) {
57 Some(GridTrack::px(v))
58 } else {
59 t.strip_suffix('%').and_then(parse).map(GridTrack::percent)
60 }
61}
62
63fn repeated_track(count: u16, token: &str) -> Option<RepeatedGridTrack> {
65 let t = token.trim();
66 match t {
67 "auto" => return Some(RepeatedGridTrack::auto(count)),
68 "min-content" => return Some(RepeatedGridTrack::min_content(count)),
69 "max-content" => return Some(RepeatedGridTrack::max_content(count)),
70 _ => {}
71 }
72 let parse = |num: &str| num.trim().parse::<f32>().ok();
73 if let Some(v) = t.strip_suffix("fr").and_then(parse) {
74 Some(RepeatedGridTrack::fr(count, v))
75 } else if let Some(v) = t.strip_suffix("flex").and_then(parse) {
76 Some(RepeatedGridTrack::flex(count, v))
77 } else if let Some(v) = t.strip_suffix("px").and_then(parse) {
78 Some(RepeatedGridTrack::px(count as usize, v))
79 } else {
80 t.strip_suffix('%')
81 .and_then(parse)
82 .map(|v| RepeatedGridTrack::percent(count as usize, v))
83 }
84}
85
86fn parse_template(s: &str) -> Vec<RepeatedGridTrack> {
89 split_tracks(s)
90 .into_iter()
91 .filter_map(|tok| {
92 let parse_one = || {
93 if let Some(inner) = tok
94 .strip_prefix("repeat(")
95 .and_then(|t| t.strip_suffix(')'))
96 {
97 let (count, track) = inner.split_once(',')?;
98 repeated_track(count.trim().parse().ok()?, track)
99 } else {
100 single_track(&tok).map(Into::into)
101 }
102 };
103 let parsed = parse_one();
104 if parsed.is_none() {
105 decode_warn(
106 "gridTrack",
107 &tok,
108 &format!("ignoring unparsable grid track {tok:?}"),
109 );
110 }
111 parsed
112 })
113 .collect()
114}
115
116fn parse_auto_tracks(s: &str) -> Vec<GridTrack> {
118 split_tracks(s)
119 .iter()
120 .filter_map(|t| {
121 let parsed = single_track(t);
122 if parsed.is_none() {
123 decode_warn(
124 "gridTrack",
125 t,
126 &format!("ignoring unparsable grid track {t:?}"),
127 );
128 }
129 parsed
130 })
131 .collect()
132}
133
134fn try_grid_placement(s: &str) -> Option<GridPlacement> {
139 enum Token {
140 Num(i16), Span(u16), Auto,
143 Invalid, }
145 fn token(t: &str) -> Token {
146 let t = t.trim();
147 if t == "auto" {
148 return Token::Auto;
149 }
150 if let Some(n) = t.strip_prefix("span") {
151 return match n.trim().parse::<u16>() {
152 Ok(0) | Err(_) => Token::Invalid,
153 Ok(n) => Token::Span(n),
154 };
155 }
156 match t.parse::<i16>() {
157 Ok(0) | Err(_) => Token::Invalid,
158 Ok(n) => Token::Num(n),
159 }
160 }
161 use Token::*;
162 if let Some((a, b)) = s.split_once('/') {
163 return Some(match (token(a), token(b)) {
164 (Num(start), Span(span)) => GridPlacement::start_span(start, span),
165 (Auto, Span(span)) => GridPlacement::span(span),
166 (Num(start), Num(end)) => GridPlacement::start_end(start, end),
167 (Num(start), Auto) => GridPlacement::start(start),
168 (Auto, Num(end)) => GridPlacement::end(end),
169 (Auto, Auto) => GridPlacement::auto(),
170 _ => return None,
171 });
172 }
173 match token(s) {
174 Auto => Some(GridPlacement::auto()),
175 Span(span) => Some(GridPlacement::span(span)),
176 Num(line) => Some(GridPlacement::start(line)),
177 Invalid => None,
178 }
179}
180
181macro_rules! grid_fields {
184 ( $(
185 $(#[$meta:meta])*
186 fn $fn_name:ident($expect:literal) -> $ty:ty { $parse:expr }
187 )+ ) => { $(
188 $(#[$meta])*
189 pub(crate) fn $fn_name<'de, D: Deserializer<'de>>(d: D) -> Result<Option<$ty>, D::Error> {
190 struct V;
191 impl<'de> Visitor<'de> for V {
192 type Value = Option<$ty>;
193 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
194 f.write_str($expect)
195 }
196 fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
197 let parse: fn(&str) -> $ty = $parse;
198 Ok(Some(parse(s)))
199 }
200 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
201 Ok(None)
202 }
203 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
204 Ok(None)
205 }
206 }
207 d.deserialize_any(V)
208 }
209 )+ };
210}
211
212grid_fields! {
213 fn de_grid_template("a CSS grid template string") -> Vec<RepeatedGridTrack> {
214 parse_template
215 }
216 fn de_grid_auto_tracks("a grid auto-track list string") -> Vec<GridTrack> {
217 parse_auto_tracks
218 }
219 fn de_grid_placement("a grid line placement string") -> GridPlacement {
223 |s| {
224 try_grid_placement(s).unwrap_or_else(|| {
225 decode_warn(
226 "gridPlacement",
227 s,
228 &format!("unrecognized grid placement {s:?}"),
229 );
230 GridPlacement::default()
231 })
232 }
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use crate::protocol::style::Style;
240
241 #[test]
243 fn grid_templates_and_placement_decode() {
244 let s: Style = serde_json::from_value(serde_json::json!({
245 "gridTemplateColumns": "1fr 2fr 100px",
246 "gridTemplateRows": "repeat(3, 1fr)",
247 "gridAutoRows": "auto 40px",
248 }))
249 .expect("grid template decodes");
250 assert_eq!(s.grid_template_columns.map(|t| t.len()), Some(3));
251 assert_eq!(s.grid_template_rows.map(|t| t.len()), Some(1));
252 assert_eq!(s.grid_auto_rows.map(|t| t.len()), Some(2));
253
254 let s: Style =
256 serde_json::from_value(serde_json::json!({ "gridTemplateRows": "1fr bogus 2fr" }))
257 .expect("bad track must not abort");
258 assert_eq!(s.grid_template_rows.map(|t| t.len()), Some(2));
259
260 let placed = |v: &str| {
261 let s: Style = serde_json::from_value(serde_json::json!({ "gridRow": v }))
262 .expect("grid placement decodes");
263 format!("{:?}", s.grid_row.unwrap())
264 };
265 let expect = |p: GridPlacement| format!("{p:?}");
266 assert_eq!(placed("1 / 3"), expect(GridPlacement::start_end(1, 3)));
267 assert_eq!(placed("span 2"), expect(GridPlacement::span(2)));
268 assert_eq!(
269 placed("2 / span 3"),
270 expect(GridPlacement::start_span(2, 3))
271 );
272 assert_eq!(placed("2 / 2"), expect(GridPlacement::start_end(2, 2)));
273 assert_eq!(placed("-1"), expect(GridPlacement::start(-1)));
274 assert_eq!(placed("2 / auto"), expect(GridPlacement::start(2)));
275 assert_eq!(placed("auto / 3"), expect(GridPlacement::end(3)));
276 }
277
278 #[test]
282 fn grid_placement_zero_falls_back_to_auto() {
283 let placed = |v: &str| {
284 let s: Style = serde_json::from_value(serde_json::json!({ "gridRow": v }))
285 .expect("zero placement must not abort");
286 format!("{:?}", s.grid_row.unwrap())
287 };
288 let auto = format!("{:?}", GridPlacement::auto());
289 for s in ["0", "span 0", "0 / 2", "2 / 0", "0 / span 2", "2 / span 0"] {
290 assert_eq!(placed(s), auto, "input {s:?}");
291 }
292 assert_eq!(placed("garbage"), auto);
294 }
295}