use crate::{CubicBezier, Easing};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Vec2 {
#[must_use]
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite()
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.x == 0.0 && self.y == 0.0
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(tag = "mode", rename_all = "snake_case")]
#[cfg_attr(feature = "schema", schemars(rename = "Animated_{T}"))]
pub enum Animated<T> {
Constant {
value: T,
},
Keyframes {
keyframes: Vec<Keyframe<T>>,
},
}
impl<T> Animated<T> {
#[must_use]
pub fn map<U>(&self, map: impl Fn(&T) -> U) -> Animated<U> {
match self {
Self::Constant { value } => Animated::Constant { value: map(value) },
Self::Keyframes { keyframes } => Animated::Keyframes {
keyframes: keyframes
.iter()
.map(|keyframe| Keyframe {
time: keyframe.time,
value: map(&keyframe.value),
interpolation: keyframe.interpolation,
})
.collect(),
},
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(tag = "mode", rename_all = "snake_case")]
pub enum AnimatedPoint {
Constant {
value: Vec2,
},
Keyframes {
keyframes: Vec<PointKeyframe>,
},
}
impl Default for AnimatedPoint {
fn default() -> Self {
Self::Constant {
value: Vec2::default(),
}
}
}
impl From<Animated<Vec2>> for AnimatedPoint {
fn from(animation: Animated<Vec2>) -> Self {
match animation {
Animated::Constant { value } => Self::Constant { value },
Animated::Keyframes { keyframes } => Self::Keyframes {
keyframes: keyframes
.into_iter()
.map(|keyframe| PointKeyframe {
time: keyframe.time,
value: keyframe.value,
interpolation: keyframe.interpolation,
out_tangent: Vec2::default(),
in_tangent: Vec2::default(),
})
.collect(),
},
}
}
}
impl AnimatedPoint {
#[must_use]
pub const fn is_animated(&self) -> bool {
matches!(self, Self::Keyframes { .. })
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct PointKeyframe {
pub time: alpha2_time::MediaTime,
pub value: Vec2,
pub interpolation: Interpolation,
#[serde(default, skip_serializing_if = "Vec2::is_zero")]
pub out_tangent: Vec2,
#[serde(default, skip_serializing_if = "Vec2::is_zero")]
pub in_tangent: Vec2,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "schema", schemars(rename = "Keyframe_{T}"))]
pub struct Keyframe<T> {
pub time: alpha2_time::MediaTime,
pub value: T,
pub interpolation: Interpolation,
}
#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "type", rename_all = "snake_case", from = "InterpolationWire")]
pub enum Interpolation {
Hold,
Linear,
CubicBezier(CubicBezier),
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename = "Interpolation"))]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub(crate) enum InterpolationWire {
Hold {},
Linear {},
CubicBezier(CubicBezier),
}
impl From<InterpolationWire> for Interpolation {
fn from(wire: InterpolationWire) -> Self {
match wire {
InterpolationWire::Hold {} => Self::Hold,
InterpolationWire::Linear {} => Self::Linear,
InterpolationWire::CubicBezier(curve) => Self::CubicBezier(curve),
}
}
}
impl Interpolation {
#[must_use]
pub fn amount(self, progress: f64) -> Option<f64> {
match self {
Self::Hold => None,
Self::Linear => Some(Easing::Linear.apply(progress)),
Self::CubicBezier(curve) => Some(curve.apply(progress)),
}
}
#[must_use]
pub fn is_valid(self) -> bool {
match self {
Self::Hold | Self::Linear => true,
Self::CubicBezier(curve) => curve.is_valid(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct VisualProperties {
pub position: AnimatedPoint,
pub anchor: Vec2,
pub scale: Animated<Vec2>,
pub rotation_degrees: Animated<f32>,
pub opacity: Animated<f32>,
}
impl Default for VisualProperties {
fn default() -> Self {
Self {
position: AnimatedPoint::Constant {
value: Vec2::new(0.5, 0.5),
},
anchor: Vec2::new(0.5, 0.5),
scale: Animated::Constant {
value: Vec2::new(1.0, 1.0),
},
rotation_degrees: Animated::Constant { value: 0.0 },
opacity: Animated::Constant { value: 1.0 },
}
}
}
impl VisualProperties {
pub(crate) fn validate(&self) -> bool {
self.anchor.is_finite()
&& valid_point_animation(&self.position)
&& valid_vec2_animation(&self.scale)
&& valid_scalar_animation(&self.rotation_degrees, false)
&& valid_scalar_animation(&self.opacity, true)
}
}
fn valid_vec2_animation(animation: &Animated<Vec2>) -> bool {
match animation {
Animated::Constant { value } => value.is_finite(),
Animated::Keyframes { keyframes } => {
valid_keyframes(keyframes)
&& keyframes.iter().all(|keyframe| keyframe.value.is_finite())
}
}
}
pub(crate) fn valid_point_animation(animation: &AnimatedPoint) -> bool {
match animation {
AnimatedPoint::Constant { value } => value.is_finite(),
AnimatedPoint::Keyframes { keyframes } => {
!keyframes.is_empty()
&& keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
&& keyframes.iter().all(|keyframe| {
keyframe.interpolation.is_valid()
&& keyframe.value.is_finite()
&& keyframe.out_tangent.is_finite()
&& keyframe.in_tangent.is_finite()
})
}
}
}
fn valid_scalar_animation(animation: &Animated<f32>, unit_interval: bool) -> bool {
let valid_value =
|value: f32| value.is_finite() && (!unit_interval || (0.0..=1.0).contains(&value));
match animation {
Animated::Constant { value } => valid_value(*value),
Animated::Keyframes { keyframes } => {
valid_keyframes(keyframes)
&& keyframes.iter().all(|keyframe| valid_value(keyframe.value))
}
}
}
pub(crate) fn valid_scalar_values(animation: &Animated<f32>, check: impl Fn(f32) -> bool) -> bool {
let valid = |value: f32| value.is_finite() && check(value);
match animation {
Animated::Constant { value } => valid(*value),
Animated::Keyframes { keyframes } => {
valid_keyframes(keyframes) && keyframes.iter().all(|keyframe| valid(keyframe.value))
}
}
}
pub(crate) fn valid_keyframes<T>(keyframes: &[Keyframe<T>]) -> bool {
!keyframes.is_empty()
&& keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
&& keyframes
.iter()
.all(|keyframe| keyframe.interpolation.is_valid())
}