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/// Font weight — named ("normal"/"bold") or numeric (100-900)
65#[derive(Debug, Clone, JsonSchema, Default)]
66pub enum FontWeight {
67    #[default]
68    Normal,
69    Bold,
70    Weight(u16),
71}
72
73#[allow(dead_code)]
74impl FontWeight {
75    pub fn to_skia_weight(&self) -> i32 {
76        match self {
77            FontWeight::Normal => 400,
78            FontWeight::Bold => 700,
79            FontWeight::Weight(w) => *w as i32,
80        }
81    }
82}
83
84impl Serialize for FontWeight {
85    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
86        match self {
87            FontWeight::Normal => serializer.serialize_str("normal"),
88            FontWeight::Bold => serializer.serialize_str("bold"),
89            FontWeight::Weight(w) => serializer.serialize_u16(*w),
90        }
91    }
92}
93
94impl<'de> Deserialize<'de> for FontWeight {
95    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
96        struct FontWeightVisitor;
97        impl<'de> serde::de::Visitor<'de> for FontWeightVisitor {
98            type Value = FontWeight;
99            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
100                write!(f, "\"normal\", \"bold\", or a number 100-900")
101            }
102            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<FontWeight, E> {
103                match v {
104                    "normal" => Ok(FontWeight::Normal),
105                    "bold" => Ok(FontWeight::Bold),
106                    _ => Err(E::custom(format!("unknown font weight: {}", v))),
107                }
108            }
109            fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<FontWeight, E> {
110                Ok(FontWeight::Weight(v as u16))
111            }
112            fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<FontWeight, E> {
113                Ok(FontWeight::Weight(v as u16))
114            }
115            fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<FontWeight, E> {
116                Ok(FontWeight::Weight(v as u16))
117            }
118        }
119        deserializer.deserialize_any(FontWeightVisitor)
120    }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
124#[serde(rename_all = "snake_case")]
125#[derive(Default)]
126pub enum FontStyleType {
127    #[default]
128    Normal,
129    Italic,
130    Oblique,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
134#[serde(rename_all = "snake_case")]
135#[derive(Default)]
136pub enum TextAlign {
137    #[default]
138    Left,
139    Center,
140    Right,
141}
142
143#[derive(Debug, Serialize, Deserialize, JsonSchema)]
144#[serde(rename_all = "snake_case")]
145#[derive(Default)]
146pub enum VerticalAlign {
147    Top,
148    #[default]
149    Middle,
150    Bottom,
151}
152
153/// Size dimension: fixed px, "auto", or "50%" (percent of parent)
154#[derive(Debug, Clone, JsonSchema)]
155pub enum SizeDimension {
156    Fixed(f32),
157    Percent(f32),
158    Auto,
159}
160
161impl Serialize for SizeDimension {
162    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
163        match self {
164            SizeDimension::Fixed(v) => serializer.serialize_f32(*v),
165            SizeDimension::Percent(p) => serializer.serialize_str(&format!("{}%", p)),
166            SizeDimension::Auto => serializer.serialize_str("auto"),
167        }
168    }
169}
170
171impl<'de> Deserialize<'de> for SizeDimension {
172    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
173        struct SizeDimensionVisitor;
174        impl<'de> serde::de::Visitor<'de> for SizeDimensionVisitor {
175            type Value = SizeDimension;
176            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
177                write!(f, "a number, \"auto\", or \"50%\"")
178            }
179            fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<SizeDimension, E> {
180                Ok(SizeDimension::Fixed(v as f32))
181            }
182            fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<SizeDimension, E> {
183                Ok(SizeDimension::Fixed(v as f32))
184            }
185            fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<SizeDimension, E> {
186                Ok(SizeDimension::Fixed(v as f32))
187            }
188            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<SizeDimension, E> {
189                if v == "auto" {
190                    Ok(SizeDimension::Auto)
191                } else if let Some(pct) = v.strip_suffix('%') {
192                    pct.trim()
193                        .parse::<f32>()
194                        .map(SizeDimension::Percent)
195                        .map_err(|_| E::custom(format!("invalid percentage: {}", v)))
196                } else {
197                    Err(E::custom(format!(
198                        "expected number, \"auto\", or \"N%\", got: {}",
199                        v
200                    )))
201                }
202            }
203        }
204        deserializer.deserialize_any(SizeDimensionVisitor)
205    }
206}
207
208pub fn deserialize_animation_effects<'de, D>(
209    deserializer: D,
210) -> Result<Vec<AnimationEffect>, D::Error>
211where
212    D: serde::Deserializer<'de>,
213{
214    use serde::de;
215
216    struct OneOrMany;
217
218    impl<'de> de::Visitor<'de> for OneOrMany {
219        type Value = Vec<AnimationEffect>;
220
221        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
222            f.write_str("a single animation effect or an array of animation effects")
223        }
224
225        fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
226        where
227            A: de::SeqAccess<'de>,
228        {
229            Vec::deserialize(de::value::SeqAccessDeserializer::new(seq))
230        }
231
232        fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
233        where
234            M: de::MapAccess<'de>,
235        {
236            let effect = AnimationEffect::deserialize(de::value::MapAccessDeserializer::new(map))?;
237            Ok(vec![effect])
238        }
239    }
240
241    deserializer.deserialize_any(OneOrMany)
242}