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
52impl<T> Animated<T> {
53 #[must_use]
55 pub fn map<U>(&self, map: impl Fn(&T) -> U) -> Animated<U> {
56 match self {
57 Self::Constant { value } => Animated::Constant { value: map(value) },
58 Self::Keyframes { keyframes } => Animated::Keyframes {
59 keyframes: keyframes
60 .iter()
61 .map(|keyframe| Keyframe {
62 time: keyframe.time,
63 value: map(&keyframe.value),
64 interpolation: keyframe.interpolation,
65 })
66 .collect(),
67 },
68 }
69 }
70}
71
72#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
79#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
80#[serde(deny_unknown_fields)]
81#[serde(tag = "mode", rename_all = "snake_case")]
82pub enum AnimatedPoint {
83 Constant {
85 value: Vec2,
87 },
88 Keyframes {
90 keyframes: Vec<PointKeyframe>,
92 },
93}
94
95impl Default for AnimatedPoint {
96 fn default() -> Self {
97 Self::Constant {
98 value: Vec2::default(),
99 }
100 }
101}
102
103impl From<Animated<Vec2>> for AnimatedPoint {
104 fn from(animation: Animated<Vec2>) -> Self {
105 match animation {
106 Animated::Constant { value } => Self::Constant { value },
107 Animated::Keyframes { keyframes } => Self::Keyframes {
108 keyframes: keyframes
109 .into_iter()
110 .map(|keyframe| PointKeyframe {
111 time: keyframe.time,
112 value: keyframe.value,
113 interpolation: keyframe.interpolation,
114 out_tangent: Vec2::default(),
115 in_tangent: Vec2::default(),
116 })
117 .collect(),
118 },
119 }
120 }
121}
122
123impl AnimatedPoint {
124 #[must_use]
126 pub const fn is_animated(&self) -> bool {
127 matches!(self, Self::Keyframes { .. })
128 }
129}
130
131#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
133#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
134#[serde(deny_unknown_fields)]
135pub struct PointKeyframe {
136 pub time: alpha2_time::MediaTime,
138 pub value: Vec2,
140 pub interpolation: Interpolation,
143 #[serde(default, skip_serializing_if = "Vec2::is_zero")]
145 pub out_tangent: Vec2,
146 #[serde(default, skip_serializing_if = "Vec2::is_zero")]
148 pub in_tangent: Vec2,
149}
150
151#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
153#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
154#[serde(deny_unknown_fields)]
155#[cfg_attr(feature = "schema", schemars(rename = "Keyframe_{T}"))]
156pub struct Keyframe<T> {
157 pub time: alpha2_time::MediaTime,
160 pub value: T,
162 pub interpolation: Interpolation,
164}
165
166#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
171#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
172#[serde(tag = "type", rename_all = "snake_case", from = "InterpolationWire")]
173pub enum Interpolation {
174 Hold,
176 Linear,
178 CubicBezier(CubicBezier),
180}
181
182#[derive(Deserialize)]
185#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
186#[cfg_attr(feature = "schema", schemars(rename = "Interpolation"))]
187#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
188pub(crate) enum InterpolationWire {
189 Hold {},
191 Linear {},
193 CubicBezier(CubicBezier),
195}
196
197impl From<InterpolationWire> for Interpolation {
198 fn from(wire: InterpolationWire) -> Self {
199 match wire {
200 InterpolationWire::Hold {} => Self::Hold,
201 InterpolationWire::Linear {} => Self::Linear,
202 InterpolationWire::CubicBezier(curve) => Self::CubicBezier(curve),
203 }
204 }
205}
206
207impl Interpolation {
208 #[must_use]
211 pub fn amount(self, progress: f64) -> Option<f64> {
212 match self {
213 Self::Hold => None,
214 Self::Linear => Some(Easing::Linear.apply(progress)),
215 Self::CubicBezier(curve) => Some(curve.apply(progress)),
216 }
217 }
218
219 #[must_use]
221 pub fn is_valid(self) -> bool {
222 match self {
223 Self::Hold | Self::Linear => true,
224 Self::CubicBezier(curve) => curve.is_valid(),
225 }
226 }
227}
228
229#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
231#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
232#[serde(deny_unknown_fields)]
233pub struct VisualProperties {
236 #[serde(default = "centre_point")]
239 pub position: AnimatedPoint,
240 #[serde(default = "centre")]
242 pub anchor: Vec2,
243 #[serde(default = "unit_scale")]
245 pub scale: Animated<Vec2>,
246 #[serde(default = "no_rotation")]
248 pub rotation_degrees: Animated<f32>,
249 #[serde(default = "opaque")]
251 pub opacity: Animated<f32>,
252}
253
254impl Default for VisualProperties {
255 fn default() -> Self {
256 Self {
257 position: centre_point(),
258 anchor: centre(),
259 scale: unit_scale(),
260 rotation_degrees: no_rotation(),
261 opacity: opaque(),
262 }
263 }
264}
265
266const fn centre() -> Vec2 {
267 Vec2::new(0.5, 0.5)
268}
269
270const fn centre_point() -> AnimatedPoint {
271 AnimatedPoint::Constant { value: centre() }
272}
273
274pub(crate) const fn unit_scale() -> Animated<Vec2> {
275 Animated::Constant {
276 value: Vec2::new(1.0, 1.0),
277 }
278}
279
280const fn no_rotation() -> Animated<f32> {
281 Animated::Constant { value: 0.0 }
282}
283
284pub(crate) const fn opaque() -> Animated<f32> {
285 Animated::Constant { value: 1.0 }
286}
287
288pub(crate) fn visit_keyed<T>(
290 at: &str,
291 animated: &Animated<T>,
292 visit: &mut impl FnMut(&str, &[alpha2_time::MediaTime]),
293) {
294 if let Animated::Keyframes { keyframes } = animated {
295 let times = keyframes
296 .iter()
297 .map(|keyframe| keyframe.time)
298 .collect::<Vec<_>>();
299 visit(at, ×);
300 }
301}
302
303pub(crate) fn visit_keyed_point(
305 at: &str,
306 animated: &AnimatedPoint,
307 visit: &mut impl FnMut(&str, &[alpha2_time::MediaTime]),
308) {
309 if let AnimatedPoint::Keyframes { keyframes } = animated {
310 let times = keyframes
311 .iter()
312 .map(|keyframe| keyframe.time)
313 .collect::<Vec<_>>();
314 visit(at, ×);
315 }
316}
317
318impl VisualProperties {
319 pub(crate) fn validate(&self) -> bool {
320 self.anchor.is_finite()
321 && valid_point_animation(&self.position)
322 && valid_vec2_animation(&self.scale)
323 && valid_scalar_animation(&self.rotation_degrees, false)
324 && valid_scalar_animation(&self.opacity, true)
325 }
326}
327
328fn valid_vec2_animation(animation: &Animated<Vec2>) -> bool {
329 match animation {
330 Animated::Constant { value } => value.is_finite(),
331 Animated::Keyframes { keyframes } => {
332 valid_keyframes(keyframes)
333 && keyframes.iter().all(|keyframe| keyframe.value.is_finite())
334 }
335 }
336}
337
338pub(crate) fn valid_point_animation(animation: &AnimatedPoint) -> bool {
340 match animation {
341 AnimatedPoint::Constant { value } => value.is_finite(),
342 AnimatedPoint::Keyframes { keyframes } => {
343 !keyframes.is_empty()
344 && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
345 && keyframes.iter().all(|keyframe| {
346 keyframe.interpolation.is_valid()
347 && keyframe.value.is_finite()
348 && keyframe.out_tangent.is_finite()
349 && keyframe.in_tangent.is_finite()
350 })
351 }
352 }
353}
354
355fn valid_scalar_animation(animation: &Animated<f32>, unit_interval: bool) -> bool {
356 let valid_value =
357 |value: f32| value.is_finite() && (!unit_interval || (0.0..=1.0).contains(&value));
358 match animation {
359 Animated::Constant { value } => valid_value(*value),
360 Animated::Keyframes { keyframes } => {
361 valid_keyframes(keyframes)
362 && keyframes.iter().all(|keyframe| valid_value(keyframe.value))
363 }
364 }
365}
366
367pub(crate) fn valid_scalar_values(animation: &Animated<f32>, check: impl Fn(f32) -> bool) -> bool {
369 let valid = |value: f32| value.is_finite() && check(value);
370 match animation {
371 Animated::Constant { value } => valid(*value),
372 Animated::Keyframes { keyframes } => {
373 valid_keyframes(keyframes) && keyframes.iter().all(|keyframe| valid(keyframe.value))
374 }
375 }
376}
377
378pub(crate) fn valid_keyframes<T>(keyframes: &[Keyframe<T>]) -> bool {
380 !keyframes.is_empty()
381 && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
382 && keyframes
383 .iter()
384 .all(|keyframe| keyframe.interpolation.is_valid())
385}