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