use animato_core::Update;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct AnimationId(u64);
struct Slot {
id: AnimationId,
animation: Box<dyn Update + Send>,
remove: bool,
}
impl std::fmt::Debug for Slot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Slot")
.field("id", &self.id)
.field("remove", &self.remove)
.finish()
}
}
#[derive(Debug, Default)]
pub struct AnimationDriver {
slots: Vec<Slot>,
next_id: u64,
}
impl AnimationDriver {
pub fn new() -> Self {
Self {
slots: Vec::new(),
next_id: 0,
}
}
pub fn add<A: Update + Send + 'static>(&mut self, anim: A) -> AnimationId {
let id = AnimationId(self.next_id);
self.next_id += 1;
self.slots.push(Slot {
id,
animation: Box::new(anim),
remove: false,
});
id
}
pub fn tick(&mut self, dt: f32) {
for slot in self.slots.iter_mut() {
if slot.remove {
continue;
}
let still_running = slot.animation.update(dt);
if !still_running {
slot.remove = true;
}
}
self.slots.retain(|s| !s.remove);
}
pub fn cancel(&mut self, id: AnimationId) {
self.slots.retain(|s| s.id != id);
}
pub fn cancel_all(&mut self) {
self.slots.clear();
}
pub fn active_count(&self) -> usize {
self.slots.len()
}
pub fn is_active(&self, id: AnimationId) -> bool {
self.slots.iter().any(|s| s.id == id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use animato_core::Easing;
use animato_tween::Tween;
#[test]
fn auto_removes_completed() {
let mut driver = AnimationDriver::new();
let id = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
assert!(driver.is_active(id));
driver.tick(2.0); assert!(!driver.is_active(id));
assert_eq!(driver.active_count(), 0);
}
#[test]
fn cancel_removes_mid_animation() {
let mut driver = AnimationDriver::new();
let id = driver.add(Tween::new(0.0_f32, 1.0).duration(10.0).build());
driver.tick(1.0);
assert!(driver.is_active(id));
driver.cancel(id);
assert!(!driver.is_active(id));
}
#[test]
fn cancel_noop_on_missing_id() {
let mut driver = AnimationDriver::new();
let id = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
driver.tick(2.0); driver.cancel(id); assert_eq!(driver.active_count(), 0);
}
#[test]
fn active_count_tracks_correctly() {
let mut driver = AnimationDriver::new();
let _a = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
let _b = driver.add(Tween::new(0.0_f32, 1.0).duration(2.0).build());
let _c = driver.add(Tween::new(0.0_f32, 1.0).duration(3.0).build());
assert_eq!(driver.active_count(), 3);
driver.tick(1.5); assert_eq!(driver.active_count(), 2);
driver.tick(1.0); assert_eq!(driver.active_count(), 1);
}
#[test]
fn cancel_all_clears_everything() {
let mut driver = AnimationDriver::new();
for _ in 0..10 {
driver.add(Tween::new(0.0_f32, 1.0).duration(5.0).build());
}
assert_eq!(driver.active_count(), 10);
driver.cancel_all();
assert_eq!(driver.active_count(), 0);
}
#[test]
fn multiple_concurrent_animations_tick_independently() {
let mut driver = AnimationDriver::new();
let slow = driver.add(Tween::new(0.0_f32, 1.0).duration(2.0).build());
let fast = driver.add(
Tween::new(0.0_f32, 1.0)
.duration(0.5)
.easing(Easing::Linear)
.build(),
);
driver.tick(1.0); assert!(!driver.is_active(fast));
assert!(driver.is_active(slow));
driver.tick(2.0); assert!(!driver.is_active(slow));
}
#[test]
fn animation_id_is_unique() {
let mut driver = AnimationDriver::new();
let ids: Vec<_> = (0..100)
.map(|_| driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build()))
.collect();
let unique: std::collections::HashSet<_> = ids.iter().collect();
assert_eq!(unique.len(), 100);
}
}