Skip to main content

rustmotion_core/schema/
style.rs

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