use crate::easing::parse_easing;
use crate::tween::lock;
use crate::types::{f32_array, parse_loop_mode};
use animato_core::{Playable, Update};
use animato_tween::KeyframeTrack as CoreKeyframeTrack;
use js_sys::Float32Array;
use std::sync::{Arc, Mutex};
use wasm_bindgen::prelude::*;
type Shared<T> = Arc<Mutex<CoreKeyframeTrack<T>>>;
macro_rules! shared_keyframes {
($name:ident, $value_ty:ty) => {
#[derive(Clone, Debug)]
pub(crate) struct $name {
inner: Shared<$value_ty>,
}
impl $name {
pub(crate) fn new(inner: Shared<$value_ty>) -> Self {
Self { inner }
}
}
impl Update for $name {
fn update(&mut self, dt: f32) -> bool {
lock(&self.inner).update(dt)
}
}
impl Playable for $name {
fn duration(&self) -> f32 {
lock(&self.inner).duration()
}
fn reset(&mut self) {
lock(&self.inner).reset();
}
fn seek_to(&mut self, progress: f32) {
let mut track = lock(&self.inner);
Playable::seek_to(&mut *track, progress);
}
fn is_complete(&self) -> bool {
lock(&self.inner).is_complete()
}
fn as_any(&self) -> &dyn core::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
self
}
}
};
}
shared_keyframes!(SharedKeyframeTrack, f32);
shared_keyframes!(SharedKeyframeTrack2D, [f32; 2]);
shared_keyframes!(SharedKeyframeTrack3D, [f32; 3]);
shared_keyframes!(SharedKeyframeTrack4D, [f32; 4]);
#[wasm_bindgen(js_name = KeyframeTrack)]
#[derive(Clone, Debug)]
pub struct KeyframeTrack {
inner: Shared<f32>,
}
#[wasm_bindgen(js_class = KeyframeTrack)]
impl KeyframeTrack {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(CoreKeyframeTrack::new())),
}
}
pub fn push(&self, time: f32, value: f32) {
let mut track = lock(&self.inner);
let next = core::mem::take(&mut *track).push(time, value);
*track = next;
}
#[wasm_bindgen(js_name = pushEased)]
pub fn push_eased(&self, time: f32, value: f32, easing: &str) -> Result<(), JsValue> {
let easing = parse_easing(easing)?;
let mut track = lock(&self.inner);
let next = core::mem::take(&mut *track).push_eased(time, value, easing);
*track = next;
Ok(())
}
pub fn update(&self, dt: f32) -> bool {
lock(&self.inner).update(dt)
}
pub fn value(&self) -> f32 {
lock(&self.inner).value().unwrap_or(f32::NAN)
}
#[wasm_bindgen(js_name = valueAt)]
pub fn value_at(&self, seconds: f32) -> f32 {
lock(&self.inner).value_at(seconds).unwrap_or(f32::NAN)
}
pub fn duration(&self) -> f32 {
lock(&self.inner).duration()
}
pub fn progress(&self) -> f32 {
lock(&self.inner).progress()
}
#[wasm_bindgen(js_name = isComplete)]
pub fn is_complete(&self) -> bool {
lock(&self.inner).is_complete()
}
pub fn reset(&self) {
lock(&self.inner).reset();
}
#[wasm_bindgen(js_name = setLoopMode)]
pub fn set_loop_mode(&self, mode: &str) -> Result<(), JsValue> {
let mut track = lock(&self.inner);
let next = core::mem::take(&mut *track).looping(parse_loop_mode(mode)?);
*track = next;
Ok(())
}
pub(crate) fn shared(&self) -> SharedKeyframeTrack {
SharedKeyframeTrack::new(Arc::clone(&self.inner))
}
}
impl Default for KeyframeTrack {
fn default() -> Self {
Self::new()
}
}
macro_rules! vector_track {
(
$class:ident,
$js_name:ident,
$shared:ident,
$value_ty:ty,
[$($value:ident),+]
) => {
#[wasm_bindgen(js_name = $js_name)]
#[derive(Clone, Debug)]
pub struct $class {
inner: Shared<$value_ty>,
}
#[wasm_bindgen(js_class = $js_name)]
impl $class {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(CoreKeyframeTrack::new())),
}
}
pub fn push(&self, time: f32, $($value: f32),+) {
let mut track = lock(&self.inner);
let next = core::mem::take(&mut *track).push(time, [$($value),+]);
*track = next;
}
#[wasm_bindgen(js_name = pushEased)]
pub fn push_eased(&self, time: f32, $($value: f32,)+ easing: &str) -> Result<(), JsValue> {
let easing = parse_easing(easing)?;
let mut track = lock(&self.inner);
let next = core::mem::take(&mut *track).push_eased(time, [$($value),+], easing);
*track = next;
Ok(())
}
pub fn update(&self, dt: f32) -> bool {
lock(&self.inner).update(dt)
}
#[wasm_bindgen(js_name = toArray)]
pub fn to_array(&self) -> Float32Array {
let values = lock(&self.inner).value().unwrap_or_default();
f32_array(&values)
}
#[wasm_bindgen(js_name = valueAt)]
pub fn value_at(&self, seconds: f32) -> Float32Array {
let values = lock(&self.inner).value_at(seconds).unwrap_or_default();
f32_array(&values)
}
pub fn duration(&self) -> f32 {
lock(&self.inner).duration()
}
pub fn progress(&self) -> f32 {
lock(&self.inner).progress()
}
#[wasm_bindgen(js_name = isComplete)]
pub fn is_complete(&self) -> bool {
lock(&self.inner).is_complete()
}
pub fn reset(&self) {
lock(&self.inner).reset();
}
#[wasm_bindgen(js_name = setLoopMode)]
pub fn set_loop_mode(&self, mode: &str) -> Result<(), JsValue> {
let mut track = lock(&self.inner);
let next = core::mem::take(&mut *track).looping(parse_loop_mode(mode)?);
*track = next;
Ok(())
}
pub(crate) fn shared(&self) -> $shared {
$shared::new(Arc::clone(&self.inner))
}
}
impl Default for $class {
fn default() -> Self {
Self::new()
}
}
};
}
vector_track!(
KeyframeTrack2D,
KeyframeTrack2D,
SharedKeyframeTrack2D,
[f32; 2],
[x, y]
);
vector_track!(
KeyframeTrack3D,
KeyframeTrack3D,
SharedKeyframeTrack3D,
[f32; 3],
[x, y, z]
);
vector_track!(
KeyframeTrack4D,
KeyframeTrack4D,
SharedKeyframeTrack4D,
[f32; 4],
[x, y, z, w]
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keyframes_interpolate() {
let track = KeyframeTrack::new();
track.push(0.0, 0.0);
track.push_eased(1.0, 100.0, "linear").unwrap();
track.update(0.5);
assert_eq!(track.value(), 50.0);
}
}