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 {
244 pub position: AnimatedPoint,
247 pub anchor: Vec2,
249 pub scale: Animated<Vec2>,
251 pub rotation_degrees: Animated<f32>,
253 pub opacity: Animated<f32>,
255}
256
257impl Default for VisualProperties {
258 fn default() -> Self {
259 Self {
260 position: AnimatedPoint::Constant {
261 value: Vec2::new(0.5, 0.5),
262 },
263 anchor: Vec2::new(0.5, 0.5),
264 scale: Animated::Constant {
265 value: Vec2::new(1.0, 1.0),
266 },
267 rotation_degrees: Animated::Constant { value: 0.0 },
268 opacity: Animated::Constant { value: 1.0 },
269 }
270 }
271}
272
273impl VisualProperties {
274 pub(crate) fn validate(&self) -> bool {
275 self.anchor.is_finite()
276 && valid_point_animation(&self.position)
277 && valid_vec2_animation(&self.scale)
278 && valid_scalar_animation(&self.rotation_degrees, false)
279 && valid_scalar_animation(&self.opacity, true)
280 }
281}
282
283fn valid_vec2_animation(animation: &Animated<Vec2>) -> bool {
284 match animation {
285 Animated::Constant { value } => value.is_finite(),
286 Animated::Keyframes { keyframes } => {
287 valid_keyframes(keyframes)
288 && keyframes.iter().all(|keyframe| keyframe.value.is_finite())
289 }
290 }
291}
292
293pub(crate) fn valid_point_animation(animation: &AnimatedPoint) -> bool {
295 match animation {
296 AnimatedPoint::Constant { value } => value.is_finite(),
297 AnimatedPoint::Keyframes { keyframes } => {
298 !keyframes.is_empty()
299 && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
300 && keyframes.iter().all(|keyframe| {
301 keyframe.interpolation.is_valid()
302 && keyframe.value.is_finite()
303 && keyframe.out_tangent.is_finite()
304 && keyframe.in_tangent.is_finite()
305 })
306 }
307 }
308}
309
310fn valid_scalar_animation(animation: &Animated<f32>, unit_interval: bool) -> bool {
311 let valid_value =
312 |value: f32| value.is_finite() && (!unit_interval || (0.0..=1.0).contains(&value));
313 match animation {
314 Animated::Constant { value } => valid_value(*value),
315 Animated::Keyframes { keyframes } => {
316 valid_keyframes(keyframes)
317 && keyframes.iter().all(|keyframe| valid_value(keyframe.value))
318 }
319 }
320}
321
322pub(crate) fn valid_scalar_values(animation: &Animated<f32>, check: impl Fn(f32) -> bool) -> bool {
324 let valid = |value: f32| value.is_finite() && check(value);
325 match animation {
326 Animated::Constant { value } => valid(*value),
327 Animated::Keyframes { keyframes } => {
328 valid_keyframes(keyframes) && keyframes.iter().all(|keyframe| valid(keyframe.value))
329 }
330 }
331}
332
333pub(crate) fn valid_keyframes<T>(keyframes: &[Keyframe<T>]) -> bool {
335 !keyframes.is_empty()
336 && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
337 && keyframes
338 .iter()
339 .all(|keyframe| keyframe.interpolation.is_valid())
340}