1use crate::{
9 css_types::{Color, Length},
10 token_parser::TokenParser,
11 token_types::SimpleToken,
12};
13use std::fmt::{self, Display};
14
15#[derive(Debug, Clone, PartialEq)]
17pub struct BoxShadow {
18 pub offset_x: Length,
19 pub offset_y: Length,
20 pub blur_radius: Length,
21 pub spread_radius: Length,
22 pub color: Color,
23 pub inset: bool,
24}
25
26impl BoxShadow {
27 pub fn new(
29 offset_x: Length,
30 offset_y: Length,
31 blur_radius: Length,
32 spread_radius: Length,
33 color: Color,
34 inset: bool,
35 ) -> Self {
36 Self {
37 offset_x,
38 offset_y,
39 blur_radius,
40 spread_radius,
41 color,
42 inset,
43 }
44 }
45
46 pub fn simple(
48 offset_x: Length,
49 offset_y: Length,
50 blur_radius: Option<Length>,
51 spread_radius: Option<Length>,
52 color: Color,
53 inset: bool,
54 ) -> Self {
55 Self::new(
56 offset_x,
57 offset_y,
58 blur_radius.unwrap_or(Length::new(0.0, "px".to_string())),
59 spread_radius.unwrap_or(Length::new(0.0, "px".to_string())),
60 color,
61 inset,
62 )
63 }
64
65 pub fn parser() -> TokenParser<BoxShadow> {
67 let whitespace = TokenParser::<SimpleToken>::token(SimpleToken::Whitespace, Some("Whitespace"));
68
69 let outer_shadow = {
71 let offset_x = Length::parser();
72 let offset_y = whitespace
73 .clone()
74 .flat_map(|_| Length::parser(), Some("offset_y"));
75 let blur_radius = whitespace
76 .clone()
77 .flat_map(|_| Length::parser(), Some("blur_radius"))
78 .optional();
79 let spread_radius = whitespace
80 .clone()
81 .flat_map(|_| Length::parser(), Some("spread_radius"))
82 .optional();
83 let color = whitespace
84 .clone()
85 .flat_map(|_| Color::parse(), Some("color"));
86
87 offset_x
88 .flat_map(
89 move |x| {
90 let x_clone = x.clone();
91 offset_y
92 .clone()
93 .map(move |y| (x_clone.clone(), y), Some("with_y"))
94 },
95 Some("x_step"),
96 )
97 .flat_map(
98 move |(x, y)| {
99 let x_clone = x.clone();
100 let y_clone = y.clone();
101 blur_radius.clone().map(
102 move |blur| (x_clone.clone(), y_clone.clone(), blur),
103 Some("with_blur"),
104 )
105 },
106 Some("blur_step"),
107 )
108 .flat_map(
109 move |(x, y, blur)| {
110 let x_clone = x.clone();
111 let y_clone = y.clone();
112 let blur_clone = blur.clone();
113 spread_radius.clone().map(
114 move |spread| (x_clone.clone(), y_clone.clone(), blur_clone.clone(), spread),
115 Some("with_spread"),
116 )
117 },
118 Some("spread_step"),
119 )
120 .flat_map(
121 move |(x, y, blur, spread)| {
122 let x_clone = x.clone();
123 let y_clone = y.clone();
124 let blur_clone = blur.clone();
125 let spread_clone = spread.clone();
126 color.clone().map(
127 move |color| {
128 BoxShadow::new(
129 x_clone.clone(),
130 y_clone.clone(),
131 blur_clone
132 .clone()
133 .unwrap_or_else(|| Length::new(0.0, "px".to_string())),
134 spread_clone
135 .clone()
136 .unwrap_or_else(|| Length::new(0.0, "px".to_string())),
137 color,
138 false,
139 )
140 },
141 Some("create_shadow"),
142 )
143 },
144 Some("color_step"),
145 )
146 };
147
148 let inset_shadow = {
149 let inset_keyword =
150 TokenParser::<SimpleToken>::token(SimpleToken::Ident("inset".to_string()), Some("Ident"))
151 .where_fn(
152 |token| {
153 if let SimpleToken::Ident(value) = token {
154 value == "inset"
155 } else {
156 false
157 }
158 },
159 Some("inset_check"),
160 );
161
162 let whitespace_for_inset = whitespace.clone();
163 let inset_keyword_for_inset = inset_keyword.clone();
164
165 let shadow_then_inset = outer_shadow.clone().flat_map(
166 move |shadow| {
167 let shadow_clone = shadow.clone();
168 let whitespace_clone = whitespace_for_inset.clone();
169 let inset_clone = inset_keyword_for_inset.clone();
170
171 whitespace_clone.flat_map(
172 move |_| {
173 let shadow_for_map = shadow_clone.clone();
174 inset_clone.map(
175 move |_| {
176 BoxShadow::new(
177 shadow_for_map.offset_x.clone(),
178 shadow_for_map.offset_y.clone(),
179 shadow_for_map.blur_radius.clone(),
180 shadow_for_map.spread_radius.clone(),
181 shadow_for_map.color.clone(),
182 true,
183 )
184 },
185 Some("to_inset"),
186 )
187 },
188 Some("add_inset"),
189 )
190 },
191 Some("shadow_then_inset"),
192 );
193
194 let whitespace_for_prefix = whitespace.clone();
195 let outer_shadow_for_prefix = outer_shadow.clone();
196
197 let inset_then_shadow = inset_keyword.flat_map(
198 move |_| {
199 let whitespace_clone = whitespace_for_prefix.clone();
200 let shadow_parser = outer_shadow_for_prefix.clone();
201
202 whitespace_clone.flat_map(
203 move |_| {
204 shadow_parser.map(
205 move |shadow| {
206 BoxShadow::new(
207 shadow.offset_x.clone(),
208 shadow.offset_y.clone(),
209 shadow.blur_radius.clone(),
210 shadow.spread_radius.clone(),
211 shadow.color.clone(),
212 true,
213 )
214 },
215 Some("to_inset_prefix"),
216 )
217 },
218 Some("add_inset_prefix"),
219 )
220 },
221 Some("inset_then_shadow"),
222 );
223
224 TokenParser::one_of(vec![shadow_then_inset, inset_then_shadow])
225 };
226
227 TokenParser::one_of(vec![inset_shadow, outer_shadow])
228 }
229}
230
231impl Display for BoxShadow {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 let inset_str = if self.inset { "inset " } else { "" };
234
235 let blur_str = if self.blur_radius.value != 0.0 {
237 format!(" {}", self.blur_radius)
238 } else {
239 String::new()
240 };
241
242 let spread_str = if self.spread_radius.value != 0.0 {
243 format!(" {}", self.spread_radius)
244 } else {
245 String::new()
246 };
247
248 write!(
249 f,
250 "{}{} {}{}{} {}",
251 inset_str, self.offset_x, self.offset_y, blur_str, spread_str, self.color
252 )
253 }
254}
255
256#[derive(Debug, Clone, PartialEq)]
258pub struct BoxShadowList {
259 pub shadows: Vec<BoxShadow>,
260}
261
262impl BoxShadowList {
263 pub fn new(shadows: Vec<BoxShadow>) -> Self {
265 Self { shadows }
266 }
267
268 pub fn parser() -> TokenParser<BoxShadowList> {
270 let comma = TokenParser::<SimpleToken>::token(SimpleToken::Comma, Some("Comma"));
271 let whitespace = TokenParser::<SimpleToken>::token(SimpleToken::Whitespace, Some("Whitespace"));
272
273 let none_parser =
275 TokenParser::<SimpleToken>::token(SimpleToken::Ident("none".to_string()), Some("none_ident"))
276 .where_fn(
277 |token| {
278 if let SimpleToken::Ident(value) = token {
279 value == "none"
280 } else {
281 false
282 }
283 },
284 Some("none_check"),
285 )
286 .map(
287 |_| BoxShadowList::new(Vec::new()),
288 Some("empty_shadow_list"),
289 );
290
291 let comma_separator =
293 comma.surrounded_by(whitespace.clone().optional(), Some(whitespace.optional()));
294
295 let shadow_list_parser =
297 TokenParser::one_or_more_separated_by(BoxShadow::parser(), comma_separator)
298 .map(BoxShadowList::new, Some("shadow_list"));
299
300 TokenParser::one_of(vec![none_parser, shadow_list_parser])
302 }
303}
304
305impl Display for BoxShadowList {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 if self.shadows.is_empty() {
308 write!(f, "none")
309 } else {
310 let shadow_strings: Vec<String> = self
311 .shadows
312 .iter()
313 .map(|shadow| shadow.to_string())
314 .collect();
315 write!(f, "{}", shadow_strings.join(", "))
316 }
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::css_types::{HashColor, NamedColor};
324
325 #[test]
326 fn test_box_shadow_creation() {
327 let offset_x = Length::new(2.0, "px".to_string());
328 let offset_y = Length::new(4.0, "px".to_string());
329 let blur = Length::new(6.0, "px".to_string());
330 let spread = Length::new(0.0, "px".to_string());
331 let color = Color::Named(NamedColor::new("red".to_string()));
332
333 let shadow = BoxShadow::new(
334 offset_x.clone(),
335 offset_y.clone(),
336 blur.clone(),
337 spread.clone(),
338 color.clone(),
339 false,
340 );
341
342 assert_eq!(shadow.offset_x, offset_x);
343 assert_eq!(shadow.offset_y, offset_y);
344 assert_eq!(shadow.blur_radius, blur);
345 assert_eq!(shadow.spread_radius, spread);
346 assert_eq!(shadow.color, color);
347 assert!(!shadow.inset);
348 }
349
350 #[test]
351 fn test_box_shadow_simple_constructor() {
352 let offset_x = Length::new(1.0, "px".to_string());
353 let offset_y = Length::new(2.0, "px".to_string());
354 let color = Color::Named(NamedColor::new("black".to_string()));
355
356 let shadow = BoxShadow::simple(
357 offset_x.clone(),
358 offset_y.clone(),
359 None,
360 None,
361 color.clone(),
362 false,
363 );
364
365 assert_eq!(shadow.offset_x, offset_x);
366 assert_eq!(shadow.offset_y, offset_y);
367 assert_eq!(shadow.blur_radius.value, 0.0);
368 assert_eq!(shadow.spread_radius.value, 0.0);
369 assert_eq!(shadow.color, color);
370 assert!(!shadow.inset);
371 }
372
373 #[test]
374 fn test_box_shadow_inset() {
375 let offset_x = Length::new(1.0, "px".to_string());
376 let offset_y = Length::new(1.0, "px".to_string());
377 let blur = Length::new(3.0, "px".to_string());
378 let spread = Length::new(0.0, "px".to_string());
379 let color = Color::Hash(HashColor::new("#000000".to_string()));
380
381 let inset_shadow = BoxShadow::new(offset_x, offset_y, blur, spread, color, true);
382
383 assert!(inset_shadow.inset);
384 }
385
386 #[test]
387 fn test_box_shadow_display() {
388 let offset_x = Length::new(2.0, "px".to_string());
389 let offset_y = Length::new(4.0, "px".to_string());
390 let blur = Length::new(6.0, "px".to_string());
391 let spread = Length::new(2.0, "px".to_string());
392 let color = Color::Named(NamedColor::new("red".to_string()));
393
394 let shadow = BoxShadow::new(offset_x, offset_y, blur, spread, color, false);
395 assert_eq!(shadow.to_string(), "2px 4px 6px 2px red");
396
397 let inset_shadow = BoxShadow::new(
398 Length::new(1.0, "px".to_string()),
399 Length::new(1.0, "px".to_string()),
400 Length::new(2.0, "px".to_string()),
401 Length::new(0.0, "px".to_string()),
402 Color::Named(NamedColor::new("blue".to_string())),
403 true,
404 );
405 assert_eq!(inset_shadow.to_string(), "inset 1px 1px 2px blue");
406 }
407
408 #[test]
409 fn test_box_shadow_display_zero_values() {
410 let shadow = BoxShadow::new(
411 Length::new(1.0, "px".to_string()),
412 Length::new(2.0, "px".to_string()),
413 Length::new(0.0, "px".to_string()), Length::new(0.0, "px".to_string()), Color::Named(NamedColor::new("black".to_string())),
416 false,
417 );
418
419 assert_eq!(shadow.to_string(), "1px 2px black");
421 }
422
423 #[test]
424 fn test_box_shadow_list_creation() {
425 let shadow1 = BoxShadow::simple(
426 Length::new(1.0, "px".to_string()),
427 Length::new(1.0, "px".to_string()),
428 None,
429 None,
430 Color::Named(NamedColor::new("red".to_string())),
431 false,
432 );
433
434 let shadow2 = BoxShadow::simple(
435 Length::new(2.0, "px".to_string()),
436 Length::new(2.0, "px".to_string()),
437 Some(Length::new(4.0, "px".to_string())),
438 None,
439 Color::Named(NamedColor::new("blue".to_string())),
440 true,
441 );
442
443 let shadow_list = BoxShadowList::new(vec![shadow1, shadow2]);
444 assert_eq!(shadow_list.shadows.len(), 2);
445 }
446
447 #[test]
448 fn test_box_shadow_list_display() {
449 let shadow1 = BoxShadow::new(
450 Length::new(1.0, "px".to_string()),
451 Length::new(1.0, "px".to_string()),
452 Length::new(0.0, "px".to_string()),
453 Length::new(0.0, "px".to_string()),
454 Color::Named(NamedColor::new("red".to_string())),
455 false,
456 );
457
458 let shadow2 = BoxShadow::new(
459 Length::new(2.0, "px".to_string()),
460 Length::new(2.0, "px".to_string()),
461 Length::new(4.0, "px".to_string()),
462 Length::new(0.0, "px".to_string()),
463 Color::Named(NamedColor::new("blue".to_string())),
464 true,
465 );
466
467 let shadow_list = BoxShadowList::new(vec![shadow1, shadow2]);
468 assert_eq!(
469 shadow_list.to_string(),
470 "1px 1px red, inset 2px 2px 4px blue"
471 );
472 }
473
474 #[test]
475 fn test_box_shadow_parser_creation() {
476 let _shadow_parser = BoxShadow::parser();
478 let _list_parser = BoxShadowList::parser();
479 }
480
481 #[test]
482 fn test_box_shadow_equality() {
483 let shadow1 = BoxShadow::simple(
484 Length::new(1.0, "px".to_string()),
485 Length::new(1.0, "px".to_string()),
486 None,
487 None,
488 Color::Named(NamedColor::new("red".to_string())),
489 false,
490 );
491
492 let shadow2 = BoxShadow::simple(
493 Length::new(1.0, "px".to_string()),
494 Length::new(1.0, "px".to_string()),
495 None,
496 None,
497 Color::Named(NamedColor::new("red".to_string())),
498 false,
499 );
500
501 let shadow3 = BoxShadow::simple(
502 Length::new(2.0, "px".to_string()),
503 Length::new(2.0, "px".to_string()),
504 None,
505 None,
506 Color::Named(NamedColor::new("red".to_string())),
507 false,
508 );
509
510 assert_eq!(shadow1, shadow2);
511 assert_ne!(shadow1, shadow3);
512 }
513
514 #[test]
515 fn test_box_shadow_common_values() {
516 let drop_shadow = BoxShadow::simple(
520 Length::new(0.0, "px".to_string()),
521 Length::new(2.0, "px".to_string()),
522 Some(Length::new(4.0, "px".to_string())),
523 None,
524 Color::Hash(HashColor::new("#00000026".to_string())), false,
526 );
527 assert!(!drop_shadow.inset);
528
529 let inner_shadow = BoxShadow::simple(
531 Length::new(0.0, "px".to_string()),
532 Length::new(1.0, "px".to_string()),
533 Some(Length::new(2.0, "px".to_string())),
534 None,
535 Color::Hash(HashColor::new("#0000001a".to_string())), true,
537 );
538 assert!(inner_shadow.inset);
539
540 let no_shadow = BoxShadow::simple(
542 Length::new(0.0, "px".to_string()),
543 Length::new(0.0, "px".to_string()),
544 None,
545 None,
546 Color::Named(NamedColor::new("transparent".to_string())),
547 false,
548 );
549 assert_eq!(no_shadow.offset_x.value, 0.0);
550 assert_eq!(no_shadow.offset_y.value, 0.0);
551 }
552
553 #[test]
554 fn test_box_shadow_with_spread() {
555 let shadow_with_spread = BoxShadow::new(
556 Length::new(0.0, "px".to_string()),
557 Length::new(0.0, "px".to_string()),
558 Length::new(10.0, "px".to_string()),
559 Length::new(5.0, "px".to_string()), Color::Named(NamedColor::new("black".to_string())),
561 false,
562 );
563
564 assert_eq!(shadow_with_spread.spread_radius.value, 5.0);
565 assert_eq!(shadow_with_spread.to_string(), "0px 0px 10px 5px black");
566 }
567}