use crate::math::Lerp;
use std::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Ease {
Linear,
QuadIn,
QuadOut,
QuadInOut,
CubicIn,
CubicOut,
CubicInOut,
QuartIn,
QuartOut,
QuartInOut,
QuintIn,
QuintOut,
QuintInOut,
SineIn,
SineOut,
SineInOut,
ExpoIn,
ExpoOut,
ExpoInOut,
CircIn,
CircOut,
CircInOut,
ElasticIn,
ElasticOut,
ElasticInOut,
BackIn,
BackOut,
BackInOut,
BounceIn,
BounceOut,
BounceInOut,
}
impl Ease {
pub fn apply(self, t: f32) -> f32 {
let t = t.clamp(0.0, 1.0);
match self {
Ease::Linear => t,
Ease::QuadIn => t * t,
Ease::QuadOut => 1.0 - (1.0 - t) * (1.0 - t),
Ease::QuadInOut => {
if t < 0.5 { 2.0 * t * t } else { 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0 }
}
Ease::CubicIn => t * t * t,
Ease::CubicOut => 1.0 - (1.0 - t).powi(3),
Ease::CubicInOut => {
if t < 0.5 { 4.0 * t * t * t } else { 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0 }
}
Ease::QuartIn => t * t * t * t,
Ease::QuartOut => 1.0 - (1.0 - t).powi(4),
Ease::QuartInOut => {
if t < 0.5 { 8.0 * t * t * t * t } else { 1.0 - (-2.0 * t + 2.0).powi(4) / 2.0 }
}
Ease::QuintIn => t * t * t * t * t,
Ease::QuintOut => 1.0 - (1.0 - t).powi(5),
Ease::QuintInOut => {
if t < 0.5 { 16.0 * t.powi(5) } else { 1.0 - (-2.0 * t + 2.0).powi(5) / 2.0 }
}
Ease::SineIn => 1.0 - (t * std::f32::consts::FRAC_PI_2).cos(),
Ease::SineOut => (t * std::f32::consts::FRAC_PI_2).sin(),
Ease::SineInOut => -(std::f32::consts::PI * t).cos() / 2.0 + 0.5,
Ease::ExpoIn => if t == 0.0 { 0.0 } else { 2.0_f32.powf(10.0 * t - 10.0) },
Ease::ExpoOut => if t == 1.0 { 1.0 } else { 1.0 - 2.0_f32.powf(-10.0 * t) },
Ease::ExpoInOut => {
if t == 0.0 { 0.0 }
else if t == 1.0 { 1.0 }
else if t < 0.5 { 2.0_f32.powf(20.0 * t - 10.0) / 2.0 }
else { (2.0 - 2.0_f32.powf(-20.0 * t + 10.0)) / 2.0 }
}
Ease::CircIn => 1.0 - (1.0 - t * t).sqrt(),
Ease::CircOut => (1.0 - (t - 1.0).powi(2)).sqrt(),
Ease::CircInOut => {
if t < 0.5 { (1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) / 2.0 }
else { ((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) / 2.0 }
}
Ease::ElasticIn => {
if t == 0.0 { 0.0 }
else if t == 1.0 { 1.0 }
else { -(2.0_f32).powf(10.0 * t - 10.0) * ((t * 10.0 - 10.75) * (2.0 * std::f32::consts::PI) / 3.0).sin() }
}
Ease::ElasticOut => {
if t == 0.0 { 0.0 }
else if t == 1.0 { 1.0 }
else { 2.0_f32.powf(-10.0 * t) * ((t * 10.0 - 0.75) * (2.0 * std::f32::consts::PI) / 3.0).sin() + 1.0 }
}
Ease::ElasticInOut => {
const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
if t == 0.0 { 0.0 }
else if t == 1.0 { 1.0 }
else if t < 0.5 {
-(2.0_f32.powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * C4).sin()) / 2.0
} else {
2.0_f32.powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * C4).sin() / 2.0 + 1.0
}
}
Ease::BackIn => {
const C1: f32 = 1.70158;
const C3: f32 = C1 + 1.0;
C3 * t * t * t - C1 * t * t
}
Ease::BackOut => {
const C1: f32 = 1.70158;
const C3: f32 = C1 + 1.0;
1.0 + C3 * (t - 1.0).powi(3) + C1 * (t - 1.0).powi(2)
}
Ease::BackInOut => {
const C1: f32 = 1.70158;
const C2: f32 = C1 * 1.525;
if t < 0.5 {
(2.0 * t).powi(2) * ((C2 + 1.0) * 2.0 * t - C2) / 2.0
} else {
((2.0 * t - 2.0).powi(2) * ((C2 + 1.0) * (t * 2.0 - 2.0) + C2) + 2.0) / 2.0
}
}
Ease::BounceIn => 1.0 - Ease::BounceOut.apply(1.0 - t),
Ease::BounceOut => {
const N1: f32 = 7.5625;
const D1: f32 = 2.75;
if t < 1.0 / D1 {
N1 * t * t
} else if t < 2.0 / D1 {
let t = t - 1.5 / D1;
N1 * t * t + 0.75
} else if t < 2.5 / D1 {
let t = t - 2.25 / D1;
N1 * t * t + 0.9375
} else {
let t = t - 2.625 / D1;
N1 * t * t + 0.984375
}
}
Ease::BounceInOut => {
if t < 0.5 {
(1.0 - Ease::BounceOut.apply(1.0 - 2.0 * t)) / 2.0
} else {
(1.0 + Ease::BounceOut.apply(2.0 * t - 1.0)) / 2.0
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TweenStatus {
Running,
Completed,
Stopped,
Delayed,
}
#[derive(Debug, Clone)]
pub struct Tween<T: Lerp + Copy> {
current: T,
start: T,
end: T,
duration: f32,
elapsed: f32,
delay: f32,
ease: Ease,
status: TweenStatus,
ping_pong: bool,
forward: bool,
loops: u32,
loops_done: u32,
speed: f32,
on_complete_tag: Option<String>,
_marker: PhantomData<T>,
}
impl<T: Lerp + Copy> Tween<T> {
pub fn new(start: T, end: T, duration: f32, ease: Ease) -> Self {
Self {
current: start,
start,
end,
duration,
elapsed: 0.0,
delay: 0.0,
ease,
status: TweenStatus::Running,
ping_pong: false,
forward: true,
loops: 1,
loops_done: 0,
speed: 1.0,
on_complete_tag: None,
_marker: PhantomData,
}
}
pub fn with_delay(mut self, delay: f32) -> Self {
self.delay = delay;
if delay > 0.0 {
self.status = TweenStatus::Delayed;
}
self
}
pub fn with_ping_pong(mut self) -> Self {
self.ping_pong = true;
self
}
pub fn with_loops(mut self, loops: u32) -> Self {
self.loops = loops;
self
}
pub fn with_speed(mut self, speed: f32) -> Self {
self.speed = speed;
self
}
pub fn with_tag(mut self, tag: &str) -> Self {
self.on_complete_tag = Some(tag.to_string());
self
}
pub fn value(&self) -> T {
self.current
}
pub fn status(&self) -> TweenStatus {
self.status
}
pub fn progress(&self) -> f32 {
if self.duration <= 0.0 { return 1.0; }
(self.elapsed / self.duration).clamp(0.0, 1.0)
}
pub fn reverse(&mut self) {
self.forward = !self.forward;
}
pub fn stop(&mut self) {
self.status = TweenStatus::Stopped;
}
pub fn restart(&mut self) {
self.elapsed = 0.0;
self.loops_done = 0;
self.forward = true;
self.status = if self.delay > 0.0 { TweenStatus::Delayed } else { TweenStatus::Running };
}
pub fn update(&mut self, dt: f32) -> bool {
match self.status {
TweenStatus::Completed | TweenStatus::Stopped => return false,
TweenStatus::Delayed => {
self.delay -= dt * self.speed;
if self.delay <= 0.0 {
self.status = TweenStatus::Running;
}
return false;
}
TweenStatus::Running => {}
}
self.elapsed += dt * self.speed;
let raw_t = if self.duration > 0.0 {
(self.elapsed / self.duration).clamp(0.0, 1.0)
} else {
1.0
};
if raw_t >= 1.0 {
if self.ping_pong {
if self.forward {
self.forward = false;
self.elapsed = 0.0;
self.current = self.end;
return false;
} else {
self.forward = true;
self.loops_done += 1;
}
} else {
self.current = self.end;
self.loops_done += 1;
}
if self.loops_done >= self.loops {
self.status = TweenStatus::Completed;
self.current = if self.forward { self.end } else { self.start };
return true;
}
self.elapsed = 0.0;
return false;
}
let eased_t = self.ease.apply(raw_t);
if self.forward {
self.current = self.start.lerp(self.end, eased_t);
} else {
self.current = self.end.lerp(self.start, eased_t);
}
false
}
}
#[derive(Default)]
pub struct TweenManager {
tweens: Vec<Box<dyn TweenTrait>>,
completed_tags: Vec<String>,
}
trait TweenTrait: AsAny {
fn update_box(&mut self, dt: f32) -> bool;
fn is_done(&self) -> bool;
fn tag(&self) -> Option<&str>;
}
impl<T: Lerp + Copy + 'static> TweenTrait for Tween<T> {
fn update_box(&mut self, dt: f32) -> bool {
self.update(dt)
}
fn is_done(&self) -> bool {
self.status == TweenStatus::Completed || self.status == TweenStatus::Stopped
}
fn tag(&self) -> Option<&str> {
self.on_complete_tag.as_deref()
}
}
impl TweenManager {
pub fn new() -> Self {
Self::default()
}
pub fn add_tween<T: Lerp + Copy + 'static>(&mut self, tween: Tween<T>) -> usize {
let id = self.tweens.len();
self.tweens.push(Box::new(tween));
id
}
pub fn get_tween<T: Lerp + Copy + 'static>(&self, id: usize) -> Option<&Tween<T>> {
self.tweens.get(id)?.as_any().downcast_ref::<Tween<T>>()
}
pub fn get_tween_mut<T: Lerp + Copy + 'static>(&mut self, id: usize) -> Option<&mut Tween<T>> {
self.tweens.get_mut(id)?.as_any_mut().downcast_mut::<Tween<T>>()
}
pub fn update(&mut self, dt: f32) -> &[String] {
self.completed_tags.clear();
for tween in &mut self.tweens {
if tween.update_box(dt) {
if let Some(tag) = tween.tag() {
self.completed_tags.push(tag.to_string());
}
}
}
self.tweens.retain(|t| !t.is_done());
&self.completed_tags
}
pub fn stop_all(&mut self) {
for _tween in &mut self.tweens {
}
self.tweens.clear();
}
pub fn count(&self) -> usize {
self.tweens.len()
}
pub fn is_empty(&self) -> bool {
self.tweens.is_empty()
}
}
trait AsAny {
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
}
impl<T: Lerp + Copy + 'static> AsAny for Tween<T> {
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}