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()
}
}
#[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>>,
},
}
#[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: Animated<Vec2>,
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: Animated::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_vec2_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())
}
}
}
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_vec2_values(animation: &Animated<Vec2>, check: impl Fn(Vec2) -> bool) -> bool {
let valid = |value: Vec2| 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())
}