1use animato_core::Update;
4
5#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
9pub struct AnimationId(u64);
10
11struct Slot {
12 id: AnimationId,
13 animation: Box<dyn Update + Send>,
14 remove: bool,
15}
16
17impl std::fmt::Debug for Slot {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 f.debug_struct("Slot")
20 .field("id", &self.id)
21 .field("remove", &self.remove)
22 .finish()
23 }
24}
25
26#[derive(Debug, Default)]
50pub struct AnimationDriver {
51 slots: Vec<Slot>,
52 next_id: u64,
53}
54
55impl AnimationDriver {
56 pub fn new() -> Self {
58 Self {
59 slots: Vec::new(),
60 next_id: 0,
61 }
62 }
63
64 pub fn add<A: Update + Send + 'static>(&mut self, anim: A) -> AnimationId {
69 let id = AnimationId(self.next_id);
70 self.next_id += 1;
71 self.slots.push(Slot {
72 id,
73 animation: Box::new(anim),
74 remove: false,
75 });
76 id
77 }
78
79 pub fn tick(&mut self, dt: f32) {
84 for slot in self.slots.iter_mut() {
85 if slot.remove {
86 continue;
87 }
88 let still_running = slot.animation.update(dt);
89 if !still_running {
90 slot.remove = true;
91 }
92 }
93 self.slots.retain(|s| !s.remove);
95 }
96
97 pub fn cancel(&mut self, id: AnimationId) {
102 self.slots.retain(|s| s.id != id);
103 }
104
105 pub fn cancel_all(&mut self) {
107 self.slots.clear();
108 }
109
110 pub fn active_count(&self) -> usize {
112 self.slots.len()
113 }
114
115 pub fn is_active(&self, id: AnimationId) -> bool {
117 self.slots.iter().any(|s| s.id == id)
118 }
119}
120
121#[cfg(test)]
126mod tests {
127 use super::*;
128 use animato_core::Easing;
129 use animato_tween::Tween;
130
131 #[test]
132 fn auto_removes_completed() {
133 let mut driver = AnimationDriver::new();
134 let id = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
135 assert!(driver.is_active(id));
136 driver.tick(2.0); assert!(!driver.is_active(id));
138 assert_eq!(driver.active_count(), 0);
139 }
140
141 #[test]
142 fn cancel_removes_mid_animation() {
143 let mut driver = AnimationDriver::new();
144 let id = driver.add(Tween::new(0.0_f32, 1.0).duration(10.0).build());
145 driver.tick(1.0);
146 assert!(driver.is_active(id));
147 driver.cancel(id);
148 assert!(!driver.is_active(id));
149 }
150
151 #[test]
152 fn cancel_noop_on_missing_id() {
153 let mut driver = AnimationDriver::new();
154 let id = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
155 driver.tick(2.0); driver.cancel(id); assert_eq!(driver.active_count(), 0);
158 }
159
160 #[test]
161 fn active_count_tracks_correctly() {
162 let mut driver = AnimationDriver::new();
163 let _a = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
164 let _b = driver.add(Tween::new(0.0_f32, 1.0).duration(2.0).build());
165 let _c = driver.add(Tween::new(0.0_f32, 1.0).duration(3.0).build());
166 assert_eq!(driver.active_count(), 3);
167
168 driver.tick(1.5); assert_eq!(driver.active_count(), 2);
170
171 driver.tick(1.0); assert_eq!(driver.active_count(), 1);
173 }
174
175 #[test]
176 fn cancel_all_clears_everything() {
177 let mut driver = AnimationDriver::new();
178 for _ in 0..10 {
179 driver.add(Tween::new(0.0_f32, 1.0).duration(5.0).build());
180 }
181 assert_eq!(driver.active_count(), 10);
182 driver.cancel_all();
183 assert_eq!(driver.active_count(), 0);
184 }
185
186 #[test]
187 fn multiple_concurrent_animations_tick_independently() {
188 let mut driver = AnimationDriver::new();
189 let slow = driver.add(Tween::new(0.0_f32, 1.0).duration(2.0).build());
190 let fast = driver.add(
191 Tween::new(0.0_f32, 1.0)
192 .duration(0.5)
193 .easing(Easing::Linear)
194 .build(),
195 );
196
197 driver.tick(1.0); assert!(!driver.is_active(fast));
199 assert!(driver.is_active(slow));
200
201 driver.tick(2.0); assert!(!driver.is_active(slow));
203 }
204
205 #[test]
206 fn animation_id_is_unique() {
207 let mut driver = AnimationDriver::new();
208 let ids: Vec<_> = (0..100)
209 .map(|_| driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build()))
210 .collect();
211 let unique: std::collections::HashSet<_> = ids.iter().collect();
212 assert_eq!(unique.len(), 100);
213 }
214}