use std::fmt;
use serde::Deserialize;
use serde::de::{self, Deserializer, MapAccess, Visitor};
use super::decode_warn;
use super::units::{Angle, Length};
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OutlineSpec {
#[serde(default)]
pub width: Option<Length>,
#[serde(default)]
pub offset: Option<Length>,
#[serde(default)]
pub color: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BoxShadowSpec {
#[serde(default)]
pub color: Option<String>,
#[serde(default)]
pub x_offset: Option<Length>,
#[serde(default)]
pub y_offset: Option<Length>,
#[serde(default)]
pub spread_radius: Option<Length>,
#[serde(default)]
pub blur_radius: Option<Length>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum BoxShadowList {
One(BoxShadowSpec),
Many(Vec<BoxShadowSpec>),
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum LineHeightSpec {
Relative(f32),
Px { px: f32 },
Str(String),
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum LetterSpacingSpec {
Px(f32),
Rem { rem: f32 },
Str(String),
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TextShadowSpec {
#[serde(default)]
pub color: Option<String>,
#[serde(default)]
pub offset_x: Option<f32>,
#[serde(default)]
pub offset_y: Option<f32>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GradientStop {
pub color: String,
#[serde(default)]
pub position: Option<Length>,
#[serde(default)]
pub hint: Option<f32>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AngularStop {
pub color: String,
#[serde(default)]
pub angle: Option<Angle>,
#[serde(default)]
pub hint: Option<f32>,
}
pub type GradientPosition = String;
pub type ColorSpace = String;
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum RadialShapeSpec {
Keyword(String),
Circle { circle: Length },
Ellipse { ellipse: [Length; 2] },
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LinearGradientSpec {
#[serde(default)]
pub angle: Option<Angle>,
#[serde(default)]
pub stops: Vec<GradientStop>,
#[serde(default)]
pub color_space: Option<ColorSpace>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RadialGradientSpec {
#[serde(default)]
pub position: Option<GradientPosition>,
#[serde(default)]
pub shape: Option<RadialShapeSpec>,
#[serde(default)]
pub stops: Vec<GradientStop>,
#[serde(default)]
pub color_space: Option<ColorSpace>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConicGradientSpec {
#[serde(default)]
pub start: Option<Angle>,
#[serde(default)]
pub position: Option<GradientPosition>,
#[serde(default)]
pub stops: Vec<AngularStop>,
#[serde(default)]
pub color_space: Option<ColorSpace>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum GradientSpec {
Linear(LinearGradientSpec),
Radial(RadialGradientSpec),
Conic(ConicGradientSpec),
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum GradientList {
One(GradientSpec),
Many(Vec<GradientSpec>),
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct BorderColorSpec {
pub top: Option<String>,
pub right: Option<String>,
pub bottom: Option<String>,
pub left: Option<String>,
}
impl BorderColorSpec {
fn uniform(s: String) -> Self {
BorderColorSpec {
top: Some(s.clone()),
right: Some(s.clone()),
bottom: Some(s.clone()),
left: Some(s),
}
}
}
impl<'de> Deserialize<'de> for BorderColorSpec {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct BorderColorVisitor;
impl<'de> Visitor<'de> for BorderColorVisitor {
type Value = BorderColorSpec;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a CSS color string or a {top,right,bottom,left} object of colors")
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<BorderColorSpec, E> {
Ok(BorderColorSpec::uniform(s.to_owned()))
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<BorderColorSpec, A::Error> {
let mut spec = BorderColorSpec::default();
while let Some(key) = map.next_key::<String>()? {
let v = map.next_value::<String>()?;
match key.as_str() {
"top" => spec.top = Some(v),
"right" => spec.right = Some(v),
"bottom" => spec.bottom = Some(v),
"left" => spec.left = Some(v),
_ => decode_warn(
"borderColor",
&key,
&format!(
"unknown borderColor side {key:?}; ignoring (expected top/right/bottom/left)"
),
),
}
}
Ok(spec)
}
}
d.deserialize_any(BorderColorVisitor)
}
}
#[cfg(test)]
mod tests {
use crate::protocol::animatable::AnimatableField;
use crate::protocol::style::Style;
#[test]
fn border_color_scalar_and_per_side() {
let uniform: Style =
serde_json::from_str(r#"{ "borderColor": "white" }"#).expect("scalar decodes");
let bc = uniform
.border_color
.static_ref()
.expect("border_color present");
assert_eq!(bc.top.as_deref(), Some("white"));
assert_eq!(bc.right.as_deref(), Some("white"));
assert_eq!(bc.bottom.as_deref(), Some("white"));
assert_eq!(bc.left.as_deref(), Some("white"));
let sided: Style =
serde_json::from_str(r##"{ "borderColor": { "top": "#f00", "left": "blue" } }"##)
.expect("object decodes");
let bc = sided
.border_color
.static_ref()
.expect("border_color present");
assert_eq!(bc.top.as_deref(), Some("#f00"));
assert_eq!(bc.left.as_deref(), Some("blue"));
assert_eq!(bc.right, None);
assert_eq!(bc.bottom, None);
let bogus: Style =
serde_json::from_str(r#"{ "borderColor": { "middle": "red", "top": "blue" } }"#)
.expect("unknown side key must not abort deserialization");
let bc = bogus
.border_color
.static_ref()
.expect("border_color present");
assert_eq!(bc.top.as_deref(), Some("blue"));
assert_eq!(bc.right, None);
assert_eq!(bc.bottom, None);
assert_eq!(bc.left, None);
}
}