#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, vec::Vec};
use crate::traits::Update;
#[cfg(feature = "std")]
use std::sync::{Arc, Mutex};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct AnimationId(u64);
impl AnimationId {
pub fn new(raw: u64) -> Self {
Self(raw)
}
}
pub struct AnimationDriver {
animations: Vec<(AnimationId, Box<dyn Update>)>,
next_id: u64,
}
impl AnimationDriver {
pub fn new() -> Self {
Self {
animations: Vec::new(),
next_id: 0,
}
}
pub fn add<A: Update + 'static>(&mut self, animation: A) -> AnimationId {
let id = AnimationId(self.next_id);
self.next_id += 1;
self.animations.push((id, Box::new(animation)));
id
}
pub fn tick(&mut self, dt: f32) {
self.animations.retain_mut(|(_, anim)| anim.update(dt));
}
pub fn cancel(&mut self, id: AnimationId) {
self.animations.retain(|(aid, _)| *aid != id);
}
pub fn cancel_all(&mut self) {
self.animations.clear();
}
pub fn active_count(&self) -> usize {
self.animations.len()
}
}
impl Default for AnimationDriver {
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Debug for AnimationDriver {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AnimationDriver")
.field("active_count", &self.animations.len())
.field("next_id", &self.next_id)
.finish()
}
}
#[cfg(feature = "std")]
#[derive(Clone, Debug)]
pub struct AnimationDriverArc(Arc<Mutex<AnimationDriver>>);
#[cfg(feature = "std")]
impl AnimationDriverArc {
#[allow(clippy::arc_with_non_send_sync)]
pub fn new() -> Self {
Self(Arc::new(Mutex::new(AnimationDriver::new())))
}
pub fn add<A: Update + 'static>(&self, animation: A) -> AnimationId {
self.0.lock().unwrap().add(animation)
}
pub fn tick(&self, dt: f32) {
self.0.lock().unwrap().tick(dt);
}
pub fn cancel(&self, id: AnimationId) {
self.0.lock().unwrap().cancel(id);
}
pub fn cancel_all(&self) {
self.0.lock().unwrap().cancel_all();
}
pub fn active_count(&self) -> usize {
self.0.lock().unwrap().active_count()
}
}
#[cfg(feature = "std")]
impl Default for AnimationDriverArc {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tween::Tween;
#[test]
fn completed_animations_are_removed() {
let mut driver = AnimationDriver::new();
driver.add(Tween::new(0.0_f32, 1.0).duration(0.5).build());
assert_eq!(driver.active_count(), 1);
driver.tick(0.5);
assert_eq!(driver.active_count(), 0);
}
#[test]
fn cancel_removes_animation() {
let mut driver = AnimationDriver::new();
let id = driver.add(Tween::new(0.0_f32, 1.0).duration(10.0).build());
assert_eq!(driver.active_count(), 1);
driver.cancel(id);
assert_eq!(driver.active_count(), 0);
}
#[test]
fn cancel_all_clears_everything() {
let mut driver = AnimationDriver::new();
driver.add(Tween::new(0.0_f32, 1.0).duration(10.0).build());
driver.add(Tween::new(0.0_f32, 1.0).duration(10.0).build());
driver.add(Tween::new(0.0_f32, 1.0).duration(10.0).build());
assert_eq!(driver.active_count(), 3);
driver.cancel_all();
assert_eq!(driver.active_count(), 0);
}
#[test]
fn multiple_animations_tick_independently() {
let mut driver = AnimationDriver::new();
driver.add(Tween::new(0.0_f32, 1.0).duration(0.5).build());
driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
driver.tick(0.5);
assert_eq!(driver.active_count(), 1);
driver.tick(0.5);
assert_eq!(driver.active_count(), 0);
}
#[test]
fn cancel_nonexistent_id_is_noop() {
let mut driver = AnimationDriver::new();
driver.add(Tween::new(0.0_f32, 1.0).duration(10.0).build());
driver.cancel(AnimationId(999));
assert_eq!(driver.active_count(), 1);
}
}