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
27#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
29#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
30#[serde(deny_unknown_fields)]
31#[serde(tag = "mode", rename_all = "snake_case")]
32#[cfg_attr(feature = "schema", schemars(rename = "Animated_{T}"))]
33pub enum Animated<T> {
34 Constant {
36 value: T,
38 },
39 Keyframes {
41 keyframes: Vec<Keyframe<T>>,
43 },
44}
45
46#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
48#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
49#[serde(deny_unknown_fields)]
50#[cfg_attr(feature = "schema", schemars(rename = "Keyframe_{T}"))]
51pub struct Keyframe<T> {
52 pub time: alpha2_time::MediaTime,
55 pub value: T,
57 pub interpolation: Interpolation,
59}
60
61#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
67#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
68#[serde(tag = "type", rename_all = "snake_case", from = "InterpolationWire")]
69pub enum Interpolation {
70 Hold,
72 Linear,
74 CubicBezier(CubicBezier),
76}
77
78#[derive(Deserialize)]
81#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
82#[cfg_attr(feature = "schema", schemars(rename = "Interpolation"))]
83#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
84pub(crate) enum InterpolationWire {
85 Hold {},
87 Linear {},
89 CubicBezier(CubicBezier),
91}
92
93impl From<InterpolationWire> for Interpolation {
94 fn from(wire: InterpolationWire) -> Self {
95 match wire {
96 InterpolationWire::Hold {} => Self::Hold,
97 InterpolationWire::Linear {} => Self::Linear,
98 InterpolationWire::CubicBezier(curve) => Self::CubicBezier(curve),
99 }
100 }
101}
102
103impl Interpolation {
104 #[must_use]
109 pub fn amount(self, progress: f64) -> Option<f64> {
110 match self {
111 Self::Hold => None,
112 Self::Linear => Some(Easing::Linear.apply(progress)),
113 Self::CubicBezier(curve) => Some(curve.apply(progress)),
114 }
115 }
116
117 #[must_use]
119 pub fn is_valid(self) -> bool {
120 match self {
121 Self::Hold | Self::Linear => true,
122 Self::CubicBezier(curve) => curve.is_valid(),
123 }
124 }
125}
126
127#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
129#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
130#[serde(deny_unknown_fields)]
131pub struct VisualProperties {
132 pub position: Animated<Vec2>,
134 pub anchor: Vec2,
136 pub scale: Animated<Vec2>,
138 pub rotation_degrees: Animated<f32>,
140 pub opacity: Animated<f32>,
142}
143
144impl Default for VisualProperties {
145 fn default() -> Self {
146 Self {
147 position: Animated::Constant {
148 value: Vec2::new(0.5, 0.5),
149 },
150 anchor: Vec2::new(0.5, 0.5),
151 scale: Animated::Constant {
152 value: Vec2::new(1.0, 1.0),
153 },
154 rotation_degrees: Animated::Constant { value: 0.0 },
155 opacity: Animated::Constant { value: 1.0 },
156 }
157 }
158}
159
160impl VisualProperties {
161 pub(crate) fn validate(&self) -> bool {
162 self.anchor.is_finite()
163 && valid_vec2_animation(&self.position)
164 && valid_vec2_animation(&self.scale)
165 && valid_scalar_animation(&self.rotation_degrees, false)
166 && valid_scalar_animation(&self.opacity, true)
167 }
168}
169
170fn valid_vec2_animation(animation: &Animated<Vec2>) -> bool {
171 match animation {
172 Animated::Constant { value } => value.is_finite(),
173 Animated::Keyframes { keyframes } => {
174 valid_keyframes(keyframes)
175 && keyframes.iter().all(|keyframe| keyframe.value.is_finite())
176 }
177 }
178}
179
180fn valid_scalar_animation(animation: &Animated<f32>, unit_interval: bool) -> bool {
181 let valid_value =
182 |value: f32| value.is_finite() && (!unit_interval || (0.0..=1.0).contains(&value));
183 match animation {
184 Animated::Constant { value } => valid_value(*value),
185 Animated::Keyframes { keyframes } => {
186 valid_keyframes(keyframes)
187 && keyframes.iter().all(|keyframe| valid_value(keyframe.value))
188 }
189 }
190}
191
192pub(crate) fn valid_scalar_values(animation: &Animated<f32>, check: impl Fn(f32) -> bool) -> bool {
194 let valid = |value: f32| value.is_finite() && check(value);
195 match animation {
196 Animated::Constant { value } => valid(*value),
197 Animated::Keyframes { keyframes } => {
198 valid_keyframes(keyframes) && keyframes.iter().all(|keyframe| valid(keyframe.value))
199 }
200 }
201}
202
203pub(crate) fn valid_vec2_values(animation: &Animated<Vec2>, check: impl Fn(Vec2) -> bool) -> bool {
205 let valid = |value: Vec2| value.is_finite() && check(value);
206 match animation {
207 Animated::Constant { value } => valid(*value),
208 Animated::Keyframes { keyframes } => {
209 valid_keyframes(keyframes) && keyframes.iter().all(|keyframe| valid(keyframe.value))
210 }
211 }
212}
213
214pub(crate) fn valid_keyframes<T>(keyframes: &[Keyframe<T>]) -> bool {
216 !keyframes.is_empty()
217 && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
218 && keyframes
219 .iter()
220 .all(|keyframe| keyframe.interpolation.is_valid())
221}