use crate::easing::{easing_name, parse_easing};
use crate::error::non_negative;
use crate::types::{f32_array, loop_from_count, parse_loop_mode};
use animato_core::{Playable, Update};
use animato_tween::{Loop, Tween as CoreTween, TweenState};
use js_sys::Float32Array;
use std::sync::{Arc, Mutex};
use wasm_bindgen::prelude::*;
type Shared<T> = Arc<Mutex<CoreTween<T>>>;
pub(crate) fn lock<T>(shared: &Arc<Mutex<T>>) -> std::sync::MutexGuard<'_, T> {
shared
.lock()
.expect("animato-js shared animation lock poisoned")
}
pub(crate) fn state_name(state: &TweenState) -> &'static str {
match state {
TweenState::Idle => "idle",
TweenState::Running => "running",
TweenState::Paused => "paused",
TweenState::Completed => "completed",
}
}
macro_rules! shared_update {
($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) {
lock(&self.inner).seek(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_update!(SharedTween, f32);
shared_update!(SharedTween2D, [f32; 2]);
shared_update!(SharedTween3D, [f32; 3]);
shared_update!(SharedTween4D, [f32; 4]);
#[wasm_bindgen(js_name = Tween)]
#[derive(Clone, Debug)]
pub struct Tween {
inner: Shared<f32>,
}
#[wasm_bindgen(js_class = Tween)]
impl Tween {
#[wasm_bindgen(constructor)]
pub fn new(from: f32, to: f32, duration: f32) -> Self {
Self {
inner: Arc::new(Mutex::new(
CoreTween::new(from, to)
.duration(non_negative(duration, 1.0))
.build(),
)),
}
}
pub fn update(&self, dt: f32) -> bool {
lock(&self.inner).update(dt)
}
pub fn value(&self) -> f32 {
lock(&self.inner).value()
}
pub fn progress(&self) -> f32 {
lock(&self.inner).progress()
}
#[wasm_bindgen(js_name = easedProgress)]
pub fn eased_progress(&self) -> f32 {
lock(&self.inner).eased_progress()
}
pub fn state(&self) -> String {
state_name(lock(&self.inner).state()).to_owned()
}
pub fn easing(&self) -> String {
easing_name(&lock(&self.inner).easing).to_owned()
}
#[wasm_bindgen(js_name = isComplete)]
pub fn is_complete(&self) -> bool {
lock(&self.inner).is_complete()
}
pub fn pause(&self) {
lock(&self.inner).pause();
}
pub fn resume(&self) {
lock(&self.inner).resume();
}
pub fn reset(&self) {
lock(&self.inner).reset();
}
pub fn reverse(&self) {
lock(&self.inner).reverse();
}
pub fn seek(&self, progress: f32) {
lock(&self.inner).seek(progress);
}
#[wasm_bindgen(js_name = setEasing)]
pub fn set_easing(&self, name: &str) -> Result<(), JsValue> {
lock(&self.inner).easing = parse_easing(name)?;
Ok(())
}
#[wasm_bindgen(js_name = setCubicBezier)]
pub fn set_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) {
lock(&self.inner).easing = animato_core::Easing::CubicBezier(x1, y1, x2, y2);
}
#[wasm_bindgen(js_name = setTimeScale)]
pub fn set_time_scale(&self, scale: f32) {
lock(&self.inner).time_scale = non_negative(scale, 1.0);
}
#[wasm_bindgen(js_name = setDelay)]
pub fn set_delay(&self, delay: f32) {
lock(&self.inner).delay = non_negative(delay, 0.0);
}
#[wasm_bindgen(js_name = setLoopCount)]
pub fn set_loop_count(&self, count: u32) {
lock(&self.inner).looping = loop_from_count(count);
}
#[wasm_bindgen(js_name = setLoopMode)]
pub fn set_loop_mode(&self, mode: &str) -> Result<(), JsValue> {
lock(&self.inner).looping = parse_loop_mode(mode)?;
Ok(())
}
#[wasm_bindgen(js_name = setPingPong)]
pub fn set_ping_pong(&self) {
lock(&self.inner).looping = Loop::PingPong;
}
#[wasm_bindgen(js_name = setForever)]
pub fn set_forever(&self) {
lock(&self.inner).looping = Loop::Forever;
}
pub(crate) fn shared(&self) -> SharedTween {
SharedTween::new(Arc::clone(&self.inner))
}
}
macro_rules! vector_tween {
(
$class:ident,
$js_name:ident,
$shared:ident,
$value_ty:ty,
[$($from:ident),+],
[$($to:ident),+],
$array_fn:ident,
[$($component:ident : $index:tt),+]
) => {
#[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)]
#[allow(clippy::too_many_arguments)]
pub fn new($($from: f32,)+ $($to: f32,)+ duration: f32) -> Self {
Self {
inner: Arc::new(Mutex::new(
CoreTween::new([$($from),+], [$($to),+])
.duration(non_negative(duration, 1.0))
.build(),
)),
}
}
pub fn update(&self, dt: f32) -> bool {
lock(&self.inner).update(dt)
}
#[wasm_bindgen(js_name = toArray)]
pub fn to_array(&self) -> Float32Array {
let value = lock(&self.inner).value();
f32_array(&value)
}
$(
pub fn $component(&self) -> f32 {
lock(&self.inner).value()[$index]
}
)+
pub fn progress(&self) -> f32 {
lock(&self.inner).progress()
}
#[wasm_bindgen(js_name = easedProgress)]
pub fn eased_progress(&self) -> f32 {
lock(&self.inner).eased_progress()
}
#[wasm_bindgen(js_name = isComplete)]
pub fn is_complete(&self) -> bool {
lock(&self.inner).is_complete()
}
pub fn pause(&self) {
lock(&self.inner).pause();
}
pub fn resume(&self) {
lock(&self.inner).resume();
}
pub fn reset(&self) {
lock(&self.inner).reset();
}
pub fn reverse(&self) {
lock(&self.inner).reverse();
}
pub fn seek(&self, progress: f32) {
lock(&self.inner).seek(progress);
}
#[wasm_bindgen(js_name = setEasing)]
pub fn set_easing(&self, name: &str) -> Result<(), JsValue> {
lock(&self.inner).easing = parse_easing(name)?;
Ok(())
}
#[wasm_bindgen(js_name = setTimeScale)]
pub fn set_time_scale(&self, scale: f32) {
lock(&self.inner).time_scale = non_negative(scale, 1.0);
}
#[wasm_bindgen(js_name = setLoopMode)]
pub fn set_loop_mode(&self, mode: &str) -> Result<(), JsValue> {
lock(&self.inner).looping = parse_loop_mode(mode)?;
Ok(())
}
pub(crate) fn shared(&self) -> $shared {
$shared::new(Arc::clone(&self.inner))
}
}
};
}
vector_tween!(
Tween2D,
Tween2D,
SharedTween2D,
[f32; 2],
[from_x, from_y],
[to_x, to_y],
vec2,
[x: 0, y: 1]
);
vector_tween!(
Tween3D,
Tween3D,
SharedTween3D,
[f32; 3],
[from_x, from_y, from_z],
[to_x, to_y, to_z],
vec3,
[x: 0, y: 1, z: 2]
);
vector_tween!(
Tween4D,
Tween4D,
SharedTween4D,
[f32; 4],
[from_x, from_y, from_z, from_w],
[to_x, to_y, to_z, to_w],
vec4,
[x: 0, y: 1, z: 2, w: 3]
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scalar_tween_updates() {
let tween = Tween::new(0.0, 10.0, 1.0);
tween.set_easing("linear").unwrap();
assert!(tween.update(0.5));
assert_eq!(tween.value(), 5.0);
}
#[test]
fn vector_tween_updates() {
let tween = Tween2D::new(0.0, 0.0, 10.0, 20.0, 1.0);
tween.update(0.5);
assert_eq!(tween.x(), 5.0);
assert_eq!(tween.y(), 10.0);
}
}