#![allow(clippy::type_complexity)]
use std::sync::{Arc, Mutex, Weak};
use crate::signal::{Signal, NEEDS_REDRAW};
use crate::view::Lerp;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Easing {
Linear,
EaseIn,
EaseOut,
EaseInOut,
CubicBezier(f32, f32, f32, f32),
}
impl Easing {
pub fn apply(self, t: f32) -> f32 {
let t = t.clamp(0.0, 1.0);
match self {
Easing::Linear => t,
Easing::EaseIn => t * t * t,
Easing::EaseOut => {
let u = 1.0 - t;
1.0 - u * u * u
}
Easing::EaseInOut => {
if t < 0.5 {
4.0 * t * t * t
} else {
let u = -2.0 * t + 2.0;
1.0 - u * u * u / 2.0
}
}
Easing::CubicBezier(x1, y1, x2, y2) => {
cubic_bezier(t, x1, y1, x2, y2)
}
}
}
}
fn cubic_bezier(x: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
let mut t = x;
for _ in 0..8 {
let cx = 3.0 * x1;
let bx = 3.0 * (x2 - x1) - cx;
let ax = 1.0 - cx - bx;
let sample = ax * t * t * t + bx * t * t + cx * t;
let dx = 3.0 * ax * t * t + 2.0 * bx * t + cx;
if dx.abs() < 1e-6 { break; }
t -= (sample - x) / dx;
}
let cy = 3.0 * y1;
let by_ = 3.0 * (y2 - y1) - cy;
let ay = 1.0 - cy - by_;
ay * t * t * t + by_ * t * t + cy * t
}
static REGISTRY: std::sync::OnceLock<Arc<Mutex<Vec<Weak<dyn AnyTween>>>>> =
std::sync::OnceLock::new();
fn registry() -> &'static Arc<Mutex<Vec<Weak<dyn AnyTween>>>> {
REGISTRY.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
pub fn tick_tweens(dt: f32) -> bool {
let mut reg = registry().lock().unwrap();
let mut still_running = false;
reg.retain(|weak| {
if let Some(t) = weak.upgrade() {
if t.tick(dt) { still_running = true; }
true
} else {
false
}
});
still_running
}
trait AnyTween: Send + Sync {
fn tick(&self, dt: f32) -> bool;
}
struct TweenInner<T: Lerp> {
signal: Signal<T>,
easing: Easing,
duration: f32,
from: Option<T>,
to: Option<T>,
elapsed: f32,
running: bool,
}
impl<T: Lerp> TweenInner<T> {
fn tick(&mut self, dt: f32) -> bool {
if !self.running { return false; }
let (from, to) = match (&self.from, &self.to) {
(Some(f), Some(t)) => (f.clone(), t.clone()),
_ => { self.running = false; return false; }
};
self.elapsed = (self.elapsed + dt).min(self.duration);
let raw_t = if self.duration > 0.0 { self.elapsed / self.duration } else { 1.0 };
let t = self.easing.apply(raw_t);
self.signal.set(T::lerp(&from, &to, t));
if raw_t >= 1.0 {
self.signal.set(to);
self.running = false;
return false;
}
true
}
}
pub struct Tween<T: Lerp>(Arc<Mutex<TweenInner<T>>>);
impl<T: Lerp + 'static> Tween<T> {
pub fn new(signal: Signal<T>, easing: Easing, duration: f32) -> Self {
let inner = Arc::new(Mutex::new(TweenInner {
signal,
easing,
duration,
from: None,
to: None,
elapsed: 0.0,
running: false,
}));
let weak: Weak<dyn AnyTween> = Arc::downgrade(&(Arc::clone(&inner) as Arc<dyn AnyTween>));
registry().lock().unwrap().push(weak);
Self(inner)
}
pub fn animate(&self, from: T, to: T) {
let mut g = self.0.lock().unwrap();
g.from = Some(from);
g.to = Some(to);
g.elapsed = 0.0;
g.running = true;
std::sync::atomic::AtomicBool::store(
&NEEDS_REDRAW,
true,
std::sync::atomic::Ordering::Relaxed,
);
}
pub fn start(&self, to: T) {
let current = self.0.lock().unwrap().signal.get();
self.animate(current, to);
}
pub fn stop(&self) {
self.0.lock().unwrap().running = false;
}
pub fn is_running(&self) -> bool {
self.0.lock().unwrap().running
}
}
impl<T: Lerp> Clone for Tween<T> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<T: Lerp + 'static> AnyTween for Mutex<TweenInner<T>> {
fn tick(&self, dt: f32) -> bool {
self.lock().unwrap().tick(dt)
}
}