1use crate::{CubicBezier, Easing};
2use serde::{Deserialize, Serialize};
3
4#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
6#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
7#[serde(deny_unknown_fields)]
8pub struct Vec2 {
9 pub x: f32,
11 pub y: f32,
13}
14
15impl Vec2 {
16 #[must_use]
18 pub const fn new(x: f32, y: f32) -> Self {
19 Self { x, y }
20 }
21
22 fn is_finite(self) -> bool {
23 self.x.is_finite() && self.y.is_finite()
24 }
25
26 #[must_use]
28 pub fn is_zero(&self) -> bool {
29 self.x == 0.0 && self.y == 0.0
30 }
31}
32
33#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
35#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
36#[serde(deny_unknown_fields)]
37#[serde(tag = "mode", rename_all = "snake_case")]
38#[cfg_attr(feature = "schema", schemars(rename = "Animated_{T}"))]
39pub enum Animated<T> {
40 Constant {
42 value: T,
44 },
45 Keyframes {
47 keyframes: Vec<Keyframe<T>>,
49 },
50}
51
52#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
61#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
62#[serde(deny_unknown_fields)]
63#[serde(tag = "mode", rename_all = "snake_case")]
64pub enum AnimatedPoint {
65 Constant {
67 value: Vec2,
69 },
70 Keyframes {
72 keyframes: Vec<PointKeyframe>,
74 },
75}
76
77impl Default for AnimatedPoint {
78 fn default() -> Self {
79 Self::Constant {
80 value: Vec2::default(),
81 }
82 }
83}
84
85impl From<Animated<Vec2>> for AnimatedPoint {
86 fn from(animation: Animated<Vec2>) -> Self {
87 match animation {
88 Animated::Constant { value } => Self::Constant { value },
89 Animated::Keyframes { keyframes } => Self::Keyframes {
90 keyframes: keyframes
91 .into_iter()
92 .map(|keyframe| PointKeyframe {
93 time: keyframe.time,
94 value: keyframe.value,
95 interpolation: keyframe.interpolation,
96 out_tangent: Vec2::default(),
97 in_tangent: Vec2::default(),
98 })
99 .collect(),
100 },
101 }
102 }
103}
104
105impl AnimatedPoint {
106 #[must_use]
108 pub const fn is_animated(&self) -> bool {
109 matches!(self, Self::Keyframes { .. })
110 }
111}
112
113#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
115#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
116#[serde(deny_unknown_fields)]
117pub struct PointKeyframe {
118 pub time: alpha2_time::MediaTime,
120 pub value: Vec2,
122 pub interpolation: Interpolation,
125 #[serde(default, skip_serializing_if = "Vec2::is_zero")]
128 pub out_tangent: Vec2,
129 #[serde(default, skip_serializing_if = "Vec2::is_zero")]
132 pub in_tangent: Vec2,
133}
134
135#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
137#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
138#[serde(deny_unknown_fields)]
139#[cfg_attr(feature = "schema", schemars(rename = "Keyframe_{T}"))]
140pub struct Keyframe<T> {
141 pub time: alpha2_time::MediaTime,
144 pub value: T,
146 pub interpolation: Interpolation,
148}
149
150#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
156#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
157#[serde(tag = "type", rename_all = "snake_case", from = "InterpolationWire")]
158pub enum Interpolation {
159 Hold,
161 Linear,
163 CubicBezier(CubicBezier),
165}
166
167#[derive(Deserialize)]
170#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
171#[cfg_attr(feature = "schema", schemars(rename = "Interpolation"))]
172#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
173pub(crate) enum InterpolationWire {
174 Hold {},
176 Linear {},
178 CubicBezier(CubicBezier),
180}
181
182impl From<InterpolationWire> for Interpolation {
183 fn from(wire: InterpolationWire) -> Self {
184 match wire {
185 InterpolationWire::Hold {} => Self::Hold,
186 InterpolationWire::Linear {} => Self::Linear,
187 InterpolationWire::CubicBezier(curve) => Self::CubicBezier(curve),
188 }
189 }
190}
191
192impl Interpolation {
193 #[must_use]
198 pub fn amount(self, progress: f64) -> Option<f64> {
199 match self {
200 Self::Hold => None,
201 Self::Linear => Some(Easing::Linear.apply(progress)),
202 Self::CubicBezier(curve) => Some(curve.apply(progress)),
203 }
204 }
205
206 #[must_use]
208 pub fn is_valid(self) -> bool {
209 match self {
210 Self::Hold | Self::Linear => true,
211 Self::CubicBezier(curve) => curve.is_valid(),
212 }
213 }
214}
215
216#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
218#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
219#[serde(deny_unknown_fields)]
220pub struct VisualProperties {
221 pub position: AnimatedPoint,
224 pub anchor: Vec2,
226 pub scale: Animated<Vec2>,
228 pub rotation_degrees: Animated<f32>,
230 pub opacity: Animated<f32>,
232}
233
234impl Default for VisualProperties {
235 fn default() -> Self {
236 Self {
237 position: AnimatedPoint::Constant {
238 value: Vec2::new(0.5, 0.5),
239 },
240 anchor: Vec2::new(0.5, 0.5),
241 scale: Animated::Constant {
242 value: Vec2::new(1.0, 1.0),
243 },
244 rotation_degrees: Animated::Constant { value: 0.0 },
245 opacity: Animated::Constant { value: 1.0 },
246 }
247 }
248}
249
250impl VisualProperties {
251 pub(crate) fn validate(&self) -> bool {
252 self.anchor.is_finite()
253 && valid_point_animation(&self.position)
254 && valid_vec2_animation(&self.scale)
255 && valid_scalar_animation(&self.rotation_degrees, false)
256 && valid_scalar_animation(&self.opacity, true)
257 }
258}
259
260fn valid_vec2_animation(animation: &Animated<Vec2>) -> bool {
261 match animation {
262 Animated::Constant { value } => value.is_finite(),
263 Animated::Keyframes { keyframes } => {
264 valid_keyframes(keyframes)
265 && keyframes.iter().all(|keyframe| keyframe.value.is_finite())
266 }
267 }
268}
269
270pub(crate) fn valid_point_animation(animation: &AnimatedPoint) -> bool {
272 match animation {
273 AnimatedPoint::Constant { value } => value.is_finite(),
274 AnimatedPoint::Keyframes { keyframes } => {
275 !keyframes.is_empty()
276 && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
277 && keyframes.iter().all(|keyframe| {
278 keyframe.interpolation.is_valid()
279 && keyframe.value.is_finite()
280 && keyframe.out_tangent.is_finite()
281 && keyframe.in_tangent.is_finite()
282 })
283 }
284 }
285}
286
287fn valid_scalar_animation(animation: &Animated<f32>, unit_interval: bool) -> bool {
288 let valid_value =
289 |value: f32| value.is_finite() && (!unit_interval || (0.0..=1.0).contains(&value));
290 match animation {
291 Animated::Constant { value } => valid_value(*value),
292 Animated::Keyframes { keyframes } => {
293 valid_keyframes(keyframes)
294 && keyframes.iter().all(|keyframe| valid_value(keyframe.value))
295 }
296 }
297}
298
299pub(crate) fn valid_scalar_values(animation: &Animated<f32>, check: impl Fn(f32) -> bool) -> bool {
301 let valid = |value: f32| value.is_finite() && check(value);
302 match animation {
303 Animated::Constant { value } => valid(*value),
304 Animated::Keyframes { keyframes } => {
305 valid_keyframes(keyframes) && keyframes.iter().all(|keyframe| valid(keyframe.value))
306 }
307 }
308}
309
310pub(crate) fn valid_keyframes<T>(keyframes: &[Keyframe<T>]) -> bool {
312 !keyframes.is_empty()
313 && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
314 && keyframes
315 .iter()
316 .all(|keyframe| keyframe.interpolation.is_valid())
317}