Skip to main content

rustmotion_core/schema/
style.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use super::video::AnimationEffect;
5
6#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
7#[serde(rename_all = "snake_case")]
8#[derive(Default)]
9pub enum CardDirection {
10    #[default]
11    Column,
12    Row,
13    ColumnReverse,
14    RowReverse,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
18#[serde(rename_all = "snake_case")]
19#[derive(Default)]
20pub enum CardAlign {
21    #[serde(alias = "flex-start", alias = "flex_start")]
22    #[default]
23    Start,
24    Center,
25    #[serde(alias = "flex-end", alias = "flex_end")]
26    End,
27    Stretch,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
31#[serde(rename_all = "snake_case")]
32#[derive(Default)]
33pub enum CardJustify {
34    #[serde(alias = "flex-start", alias = "flex_start")]
35    #[default]
36    Start,
37    Center,
38    #[serde(alias = "flex-end", alias = "flex_end")]
39    End,
40    #[serde(alias = "space-between")]
41    SpaceBetween,
42    #[serde(alias = "space-around")]
43    SpaceAround,
44    #[serde(alias = "space-evenly")]
45    SpaceEvenly,
46}
47
48/// A single step in a component's animation timeline.
49/// Triggers a set of animations at a specific time within the scene.
50#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
51pub struct TimelineStep {
52    /// Time (in seconds, relative to component start) when this step begins.
53    pub at: f64,
54    /// Animation effects to apply during this step.
55    #[serde(default, deserialize_with = "deserialize_animation_effects")]
56    pub animation: Vec<AnimationEffect>,
57    /// Style state applied from this step's `at` onwards. Properties snap at
58    /// `at`, except the ones the component's `style.transition` smooths
59    /// (opacity; color on text/counter).
60    #[serde(default)]
61    pub style: Option<Box<crate::css::CssStyle>>,
62}
63
64#[derive(Debug, Clone, Default)]
65pub enum FontWeight {
66    #[default]
67    Normal,
68    Bold,
69    Weight(u16),
70}
71
72#[allow(dead_code)]
73impl FontWeight {
74    pub fn to_skia_weight(&self) -> i32 {
75        match self {
76            FontWeight::Normal => 400,
77            FontWeight::Bold => 700,
78            FontWeight::Weight(w) => *w as i32,
79        }
80    }
81}
82
83impl Serialize for FontWeight {
84    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
85        match self {
86            FontWeight::Normal => serializer.serialize_str("normal"),
87            FontWeight::Bold => serializer.serialize_str("bold"),
88            FontWeight::Weight(w) => serializer.serialize_u16(*w),
89        }
90    }
91}
92
93impl schemars::JsonSchema for FontWeight {
94    fn schema_name() -> String {
95        "FontWeight".to_string()
96    }
97
98    fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
99        serde_json::from_value(serde_json::json!({
100            "description": "Font weight — \"normal\", \"bold\", or a number.",
101            "anyOf": [
102                { "type": "string", "enum": ["normal", "bold"] },
103                { "type": "number" }
104            ]
105        }))
106        .expect("FontWeight's schema is a literal written here")
107    }
108}
109
110impl<'de> Deserialize<'de> for FontWeight {
111    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
112        struct FontWeightVisitor;
113        impl<'de> serde::de::Visitor<'de> for FontWeightVisitor {
114            type Value = FontWeight;
115            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
116                write!(f, "\"normal\", \"bold\", or a number 100-900")
117            }
118            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<FontWeight, E> {
119                match v {
120                    "normal" => Ok(FontWeight::Normal),
121                    "bold" => Ok(FontWeight::Bold),
122                    _ => Err(E::custom(format!("unknown font weight: {}", v))),
123                }
124            }
125            fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<FontWeight, E> {
126                Ok(FontWeight::Weight(v as u16))
127            }
128            fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<FontWeight, E> {
129                Ok(FontWeight::Weight(v as u16))
130            }
131            fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<FontWeight, E> {
132                Ok(FontWeight::Weight(v as u16))
133            }
134        }
135        deserializer.deserialize_any(FontWeightVisitor)
136    }
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
140#[serde(rename_all = "snake_case")]
141#[derive(Default)]
142pub enum FontStyleType {
143    #[default]
144    Normal,
145    Italic,
146    Oblique,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
150#[serde(rename_all = "snake_case")]
151#[derive(Default)]
152pub enum TextAlign {
153    #[default]
154    Left,
155    Center,
156    Right,
157}
158
159#[derive(Debug, Serialize, Deserialize, JsonSchema)]
160#[serde(rename_all = "snake_case")]
161#[derive(Default)]
162pub enum VerticalAlign {
163    Top,
164    #[default]
165    Middle,
166    Bottom,
167}
168
169/// Size dimension: fixed px, "auto", or "50%" (percent of parent)
170#[derive(Debug, Clone, JsonSchema)]
171pub enum SizeDimension {
172    Fixed(f32),
173    Percent(f32),
174    Auto,
175}
176
177impl Serialize for SizeDimension {
178    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
179        match self {
180            SizeDimension::Fixed(v) => serializer.serialize_f32(*v),
181            SizeDimension::Percent(p) => serializer.serialize_str(&format!("{}%", p)),
182            SizeDimension::Auto => serializer.serialize_str("auto"),
183        }
184    }
185}
186
187impl<'de> Deserialize<'de> for SizeDimension {
188    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
189        struct SizeDimensionVisitor;
190        impl<'de> serde::de::Visitor<'de> for SizeDimensionVisitor {
191            type Value = SizeDimension;
192            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
193                write!(f, "a number, \"auto\", or \"50%\"")
194            }
195            fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<SizeDimension, E> {
196                Ok(SizeDimension::Fixed(v as f32))
197            }
198            fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<SizeDimension, E> {
199                Ok(SizeDimension::Fixed(v as f32))
200            }
201            fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<SizeDimension, E> {
202                Ok(SizeDimension::Fixed(v as f32))
203            }
204            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<SizeDimension, E> {
205                if v == "auto" {
206                    Ok(SizeDimension::Auto)
207                } else if let Some(pct) = v.strip_suffix('%') {
208                    pct.trim()
209                        .parse::<f32>()
210                        .map(SizeDimension::Percent)
211                        .map_err(|_| E::custom(format!("invalid percentage: {}", v)))
212                } else {
213                    Err(E::custom(format!(
214                        "expected number, \"auto\", or \"N%\", got: {}",
215                        v
216                    )))
217                }
218            }
219        }
220        deserializer.deserialize_any(SizeDimensionVisitor)
221    }
222}
223
224pub fn deserialize_animation_effects<'de, D>(
225    deserializer: D,
226) -> Result<Vec<AnimationEffect>, D::Error>
227where
228    D: serde::Deserializer<'de>,
229{
230    use serde::de;
231
232    struct OneOrMany;
233
234    impl<'de> de::Visitor<'de> for OneOrMany {
235        type Value = Vec<AnimationEffect>;
236
237        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
238            f.write_str("a single animation effect or an array of animation effects")
239        }
240
241        fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
242        where
243            A: de::SeqAccess<'de>,
244        {
245            Vec::deserialize(de::value::SeqAccessDeserializer::new(seq))
246        }
247
248        fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
249        where
250            M: de::MapAccess<'de>,
251        {
252            let effect = AnimationEffect::deserialize(de::value::MapAccessDeserializer::new(map))?;
253            Ok(vec![effect])
254        }
255    }
256
257    deserializer.deserialize_any(OneOrMany)
258}