use std::{borrow::Cow, fmt::Debug};
use cssparser::{BasicParseErrorKind, ParseError, Parser};
use crate::{
layout::style::{Color, ColorInput, CssToken, FromCss, Length, MakeComputed, ParseResult},
rendering::Sizing,
};
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct BoxShadow {
pub inset: bool,
pub offset_x: Length,
pub offset_y: Length,
pub blur_radius: Length,
pub spread_radius: Length,
pub color: ColorInput,
}
pub type BoxShadows = Box<[BoxShadow]>;
impl<'i> FromCss<'i> for BoxShadows {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
let mut shadows = Vec::new();
loop {
if input.is_exhausted() {
break;
}
let shadow = BoxShadow::from_css(input)?;
shadows.push(shadow);
if input.expect_comma().is_err() {
break;
}
}
Ok(shadows.into_boxed_slice())
}
fn valid_tokens() -> &'static [CssToken] {
BoxShadow::valid_tokens()
}
}
impl<'i> FromCss<'i> for BoxShadow {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, BoxShadow> {
let mut color = None;
let mut lengths = None;
let mut inset = false;
loop {
if !inset
&& input
.try_parse(|input| input.expect_ident_matching("inset"))
.is_ok()
{
inset = true;
continue;
}
if lengths.is_none() {
let value = input.try_parse::<_, _, ParseError<Cow<'i, str>>>(|input| {
let horizontal = Length::from_css(input)?;
let vertical = Length::from_css(input)?;
let blur = input.try_parse(Length::from_css).unwrap_or(Length::zero());
let spread = input.try_parse(Length::from_css).unwrap_or(Length::zero());
Ok((horizontal, vertical, blur, spread))
});
if let Ok(value) = value {
lengths = Some(value);
continue;
}
}
if color.is_none()
&& let Ok(value) = input.try_parse(ColorInput::from_css)
{
color = Some(value);
continue;
}
break;
}
let lengths = lengths.ok_or(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;
Ok(BoxShadow {
color: color.unwrap_or(ColorInput::Value(Color::transparent())),
offset_x: lengths.0,
offset_y: lengths.1,
blur_radius: lengths.2,
spread_radius: lengths.3,
inset,
})
}
fn valid_tokens() -> &'static [CssToken] {
&[
CssToken::Keyword("inset"),
CssToken::Token("length"),
CssToken::Token("color"),
]
}
}
impl MakeComputed for BoxShadow {
fn make_computed(&mut self, sizing: &Sizing) {
self.offset_x.make_computed(sizing);
self.offset_y.make_computed(sizing);
self.blur_radius.make_computed(sizing);
self.spread_radius.make_computed(sizing);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::style::{
Color,
Length::{self, Px},
};
#[test]
fn test_parse_simple_box_shadow() {
assert_eq!(
BoxShadow::from_str("2px 4px"),
Ok(BoxShadow {
offset_x: Px(2.0),
offset_y: Px(4.0),
blur_radius: Length::zero(),
spread_radius: Length::zero(),
color: ColorInput::Value(Color::transparent()),
inset: false,
})
);
}
#[test]
fn test_parse_box_shadow_with_blur() {
assert_eq!(
BoxShadow::from_str("2px 4px 6px"),
Ok(BoxShadow {
offset_x: Px(2.0),
offset_y: Px(4.0),
blur_radius: Px(6.0),
spread_radius: Length::zero(),
color: ColorInput::Value(Color::transparent()),
inset: false,
})
);
}
#[test]
fn test_parse_box_shadow_with_spread() {
assert_eq!(
BoxShadow::from_str("2px 4px 6px 8px"),
Ok(BoxShadow {
offset_x: Px(2.0),
offset_y: Px(4.0),
blur_radius: Px(6.0),
spread_radius: Px(8.0),
color: ColorInput::Value(Color::transparent()),
inset: false,
})
);
}
#[test]
fn test_parse_box_shadow_with_color() {
assert_eq!(
BoxShadow::from_str("2px 4px red"),
Ok(BoxShadow {
offset_x: Px(2.0),
offset_y: Px(4.0),
blur_radius: Length::zero(),
spread_radius: Length::zero(),
color: ColorInput::Value(Color([255, 0, 0, 255])),
inset: false,
})
);
}
#[test]
fn test_parse_inset_box_shadow() {
assert_eq!(
BoxShadow::from_str("inset 2px 4px"),
Ok(BoxShadow {
offset_x: Px(2.0),
offset_y: Px(4.0),
blur_radius: Length::zero(),
spread_radius: Length::zero(),
color: ColorInput::Value(Color::transparent()),
inset: true,
})
);
}
#[test]
fn test_parse_box_shadow_color_first() {
assert_eq!(
BoxShadow::from_str("red 2px 4px"),
Ok(BoxShadow {
offset_x: Px(2.0),
offset_y: Px(4.0),
blur_radius: Length::zero(),
spread_radius: Length::zero(),
color: ColorInput::Value(Color([255, 0, 0, 255])),
inset: false,
})
);
}
#[test]
fn test_parse_box_shadow_inset_after_offsets() {
assert_eq!(
BoxShadow::from_str("2px 4px inset red"),
Ok(BoxShadow {
offset_x: Px(2.0),
offset_y: Px(4.0),
blur_radius: Length::zero(),
spread_radius: Length::zero(),
color: ColorInput::Value(Color([255, 0, 0, 255])),
inset: true,
})
);
}
#[test]
fn test_parse_box_shadow_hex_color() {
assert_eq!(
BoxShadow::from_str("2px 4px #ff0000"),
Ok(BoxShadow {
offset_x: Px(2.0),
offset_y: Px(4.0),
blur_radius: Length::zero(),
spread_radius: Length::zero(),
color: ColorInput::Value(Color([255, 0, 0, 255])),
inset: false,
})
);
}
#[test]
fn test_parse_box_shadow_rgba_color() {
assert_eq!(
BoxShadow::from_str("2px 4px rgba(255, 0, 0, 0.5)"),
Ok(BoxShadow {
offset_x: Px(2.0),
offset_y: Px(4.0),
blur_radius: Length::zero(),
spread_radius: Length::zero(),
color: ColorInput::Value(Color([255, 0, 0, 128])), inset: false,
})
);
}
#[test]
fn test_parse_box_shadow_invalid() {
assert!(BoxShadow::from_str("2px").is_err());
assert!(BoxShadow::from_str("").is_err());
}
}