1use glam::{Vec2, Vec3, Vec4};
15use std::collections::HashMap;
16
17use super::easing::Easing;
18use super::{Tween, TweenState, Lerp};
19use crate::glyph::GlyphId;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub struct TweenId(pub u64);
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub struct BarId(pub u32);
32
33pub enum TweenTarget {
37 GlyphPositionX(GlyphId),
39 GlyphPositionY(GlyphId),
40 GlyphPositionZ(GlyphId),
41 GlyphScale(GlyphId),
42 GlyphScaleX(GlyphId),
43 GlyphScaleY(GlyphId),
44 GlyphAlpha(GlyphId),
45 GlyphEmission(GlyphId),
46 GlyphRotation(GlyphId),
47 GlyphColorR(GlyphId),
48 GlyphColorG(GlyphId),
49 GlyphColorB(GlyphId),
50 GlyphGlowRadius(GlyphId),
51 GlyphTemperature(GlyphId),
52 GlyphEntropy(GlyphId),
53
54 CameraFov,
56 CameraPositionX,
57 CameraPositionY,
58 CameraPositionZ,
59 CameraTargetX,
60 CameraTargetY,
61 CameraTargetZ,
62 CameraTrauma,
63
64 ScreenFade,
66 ScreenShake,
67 ScreenBloom,
68 ScreenChromaticAberration,
69 ScreenVignette,
70 ScreenSaturation,
71 ScreenHueShift,
72
73 BarFillPercent(BarId),
75 BarGhostPercent(BarId),
76
77 Custom(Box<dyn FnMut(f32) + Send>),
81
82 Named(String),
84}
85
86pub struct ActiveTween {
90 pub id: TweenId,
91 pub target: TweenTarget,
92 pub state: TweenState<f32>,
93 pub delay_remaining: f32,
95 pub tag: Option<String>,
97 pub on_complete: Option<Box<dyn FnOnce(&mut TweenManager) + Send>>,
99 pub cancelled: bool,
101}
102
103pub struct TweenManager {
107 tweens: Vec<ActiveTween>,
109 next_id: u64,
111 pub named_values: HashMap<String, f32>,
113 pub screen_fade: f32,
115 pub screen_shake: f32,
116 pub screen_bloom_override: Option<f32>,
117 pub screen_chromatic_override: Option<f32>,
118 pub screen_vignette_override: Option<f32>,
119 pub screen_saturation_override: Option<f32>,
120 pub screen_hue_shift: f32,
121 pub bar_values: HashMap<BarId, f32>,
123 pub bar_ghost_values: HashMap<BarId, f32>,
124 pub camera_fov_override: Option<f32>,
126 pub camera_position_override: [Option<f32>; 3],
127 pub camera_target_override: [Option<f32>; 3],
128 pub camera_trauma_add: f32,
129}
130
131impl TweenManager {
132 pub fn new() -> Self {
133 Self {
134 tweens: Vec::with_capacity(256),
135 next_id: 1,
136 named_values: HashMap::new(),
137 screen_fade: 0.0,
138 screen_shake: 0.0,
139 screen_bloom_override: None,
140 screen_chromatic_override: None,
141 screen_vignette_override: None,
142 screen_saturation_override: None,
143 screen_hue_shift: 0.0,
144 bar_values: HashMap::new(),
145 bar_ghost_values: HashMap::new(),
146 camera_fov_override: None,
147 camera_position_override: [None; 3],
148 camera_target_override: [None; 3],
149 camera_trauma_add: 0.0,
150 }
151 }
152
153 pub fn start(
157 &mut self,
158 target: TweenTarget,
159 from: f32,
160 to: f32,
161 duration: f32,
162 easing: Easing,
163 ) -> TweenId {
164 self.start_inner(target, from, to, duration, easing, 0.0, None, None)
165 }
166
167 pub fn start_delayed(
169 &mut self,
170 target: TweenTarget,
171 from: f32,
172 to: f32,
173 duration: f32,
174 delay: f32,
175 easing: Easing,
176 ) -> TweenId {
177 self.start_inner(target, from, to, duration, easing, delay, None, None)
178 }
179
180 pub fn start_with_callback(
182 &mut self,
183 target: TweenTarget,
184 from: f32,
185 to: f32,
186 duration: f32,
187 easing: Easing,
188 on_complete: impl FnOnce(&mut TweenManager) + Send + 'static,
189 ) -> TweenId {
190 self.start_inner(target, from, to, duration, easing, 0.0, None, Some(Box::new(on_complete)))
191 }
192
193 pub fn start_tagged(
195 &mut self,
196 target: TweenTarget,
197 from: f32,
198 to: f32,
199 duration: f32,
200 easing: Easing,
201 tag: &str,
202 ) -> TweenId {
203 self.start_inner(target, from, to, duration, easing, 0.0, Some(tag.to_string()), None)
204 }
205
206 fn start_inner(
208 &mut self,
209 target: TweenTarget,
210 from: f32,
211 to: f32,
212 duration: f32,
213 easing: Easing,
214 delay: f32,
215 tag: Option<String>,
216 on_complete: Option<Box<dyn FnOnce(&mut TweenManager) + Send>>,
217 ) -> TweenId {
218 let id = TweenId(self.next_id);
219 self.next_id += 1;
220
221 let tween = Tween::new(from, to, duration, easing);
222 let state = TweenState::new(tween);
223
224 self.tweens.push(ActiveTween {
225 id,
226 target,
227 state,
228 delay_remaining: delay,
229 tag,
230 on_complete,
231 cancelled: false,
232 });
233
234 id
235 }
236
237 pub fn cancel(&mut self, id: TweenId) {
241 for t in &mut self.tweens {
242 if t.id == id {
243 t.cancelled = true;
244 break;
245 }
246 }
247 }
248
249 pub fn cancel_tag(&mut self, tag: &str) {
251 for t in &mut self.tweens {
252 if t.tag.as_deref() == Some(tag) {
253 t.cancelled = true;
254 }
255 }
256 }
257
258 pub fn cancel_glyph(&mut self, glyph_id: GlyphId) {
260 for t in &mut self.tweens {
261 let targets_glyph = matches!(
262 &t.target,
263 TweenTarget::GlyphPositionX(id)
264 | TweenTarget::GlyphPositionY(id)
265 | TweenTarget::GlyphPositionZ(id)
266 | TweenTarget::GlyphScale(id)
267 | TweenTarget::GlyphScaleX(id)
268 | TweenTarget::GlyphScaleY(id)
269 | TweenTarget::GlyphAlpha(id)
270 | TweenTarget::GlyphEmission(id)
271 | TweenTarget::GlyphRotation(id)
272 | TweenTarget::GlyphColorR(id)
273 | TweenTarget::GlyphColorG(id)
274 | TweenTarget::GlyphColorB(id)
275 | TweenTarget::GlyphGlowRadius(id)
276 | TweenTarget::GlyphTemperature(id)
277 | TweenTarget::GlyphEntropy(id)
278 if *id == glyph_id
279 );
280 if targets_glyph {
281 t.cancelled = true;
282 }
283 }
284 }
285
286 pub fn cancel_all(&mut self) {
288 for t in &mut self.tweens {
289 t.cancelled = true;
290 }
291 }
292
293 pub fn push_raw(&mut self, target: TweenTarget, state: super::TweenState<f32>, delay: f32, tag: Option<String>, on_complete: Option<Box<dyn FnOnce(&mut TweenManager) + Send>>) -> TweenId {
297 let id = TweenId(self.next_id);
298 self.next_id += 1;
299 self.tweens.push(ActiveTween {
300 id, target, state,
301 delay_remaining: delay,
302 tag,
303 on_complete,
304 cancelled: false,
305 });
306 id
307 }
308
309 pub fn is_active(&self, id: TweenId) -> bool {
311 self.tweens.iter().any(|t| t.id == id && !t.cancelled && !t.state.done)
312 }
313
314 pub fn active_count(&self) -> usize {
316 self.tweens.iter().filter(|t| !t.cancelled && !t.state.done).count()
317 }
318
319 pub fn get_named(&self, name: &str) -> f32 {
321 self.named_values.get(name).copied().unwrap_or(0.0)
322 }
323
324 pub fn get_bar(&self, bar: BarId) -> f32 {
326 self.bar_values.get(&bar).copied().unwrap_or(0.0)
327 }
328
329 pub fn get_bar_ghost(&self, bar: BarId) -> f32 {
331 self.bar_ghost_values.get(&bar).copied().unwrap_or(0.0)
332 }
333
334 pub fn tick(&mut self, dt: f32) {
349 self.camera_trauma_add = 0.0;
351
352 let mut completed_callbacks: Vec<Box<dyn FnOnce(&mut TweenManager) + Send>> = Vec::new();
354
355 for tween in &mut self.tweens {
356 if tween.cancelled {
357 continue;
358 }
359
360 if tween.delay_remaining > 0.0 {
362 tween.delay_remaining -= dt;
363 if tween.delay_remaining > 0.0 {
364 continue;
365 }
366 let overflow = -tween.delay_remaining;
368 tween.delay_remaining = 0.0;
369 tween.state.tick(overflow);
370 } else {
371 tween.state.tick(dt);
372 }
373
374 let value = tween.state.value();
375
376 match &mut tween.target {
378 TweenTarget::GlyphPositionX(_)
379 | TweenTarget::GlyphPositionY(_)
380 | TweenTarget::GlyphPositionZ(_)
381 | TweenTarget::GlyphScale(_)
382 | TweenTarget::GlyphScaleX(_)
383 | TweenTarget::GlyphScaleY(_)
384 | TweenTarget::GlyphAlpha(_)
385 | TweenTarget::GlyphEmission(_)
386 | TweenTarget::GlyphRotation(_)
387 | TweenTarget::GlyphColorR(_)
388 | TweenTarget::GlyphColorG(_)
389 | TweenTarget::GlyphColorB(_)
390 | TweenTarget::GlyphGlowRadius(_)
391 | TweenTarget::GlyphTemperature(_)
392 | TweenTarget::GlyphEntropy(_) => {
393 }
395
396 TweenTarget::CameraFov => {
397 self.camera_fov_override = Some(value);
398 }
399 TweenTarget::CameraPositionX => {
400 self.camera_position_override[0] = Some(value);
401 }
402 TweenTarget::CameraPositionY => {
403 self.camera_position_override[1] = Some(value);
404 }
405 TweenTarget::CameraPositionZ => {
406 self.camera_position_override[2] = Some(value);
407 }
408 TweenTarget::CameraTargetX => {
409 self.camera_target_override[0] = Some(value);
410 }
411 TweenTarget::CameraTargetY => {
412 self.camera_target_override[1] = Some(value);
413 }
414 TweenTarget::CameraTargetZ => {
415 self.camera_target_override[2] = Some(value);
416 }
417 TweenTarget::CameraTrauma => {
418 self.camera_trauma_add = value;
419 }
420
421 TweenTarget::ScreenFade => {
422 self.screen_fade = value;
423 }
424 TweenTarget::ScreenShake => {
425 self.screen_shake = value;
426 }
427 TweenTarget::ScreenBloom => {
428 self.screen_bloom_override = Some(value);
429 }
430 TweenTarget::ScreenChromaticAberration => {
431 self.screen_chromatic_override = Some(value);
432 }
433 TweenTarget::ScreenVignette => {
434 self.screen_vignette_override = Some(value);
435 }
436 TweenTarget::ScreenSaturation => {
437 self.screen_saturation_override = Some(value);
438 }
439 TweenTarget::ScreenHueShift => {
440 self.screen_hue_shift = value;
441 }
442
443 TweenTarget::BarFillPercent(bar_id) => {
444 self.bar_values.insert(*bar_id, value);
445 }
446 TweenTarget::BarGhostPercent(bar_id) => {
447 self.bar_ghost_values.insert(*bar_id, value);
448 }
449
450 TweenTarget::Custom(ref mut f) => {
451 f(value);
452 }
453
454 TweenTarget::Named(ref name) => {
455 self.named_values.insert(name.clone(), value);
456 }
457 }
458
459 if tween.state.done {
461 if let Some(cb) = tween.on_complete.take() {
462 completed_callbacks.push(cb);
463 }
464 }
465 }
466
467 self.tweens.retain(|t| !t.cancelled && !t.state.done);
469
470 for cb in completed_callbacks {
472 cb(self);
473 }
474 }
475
476 pub fn apply_to_glyphs<F>(&self, mut apply: F)
481 where
482 F: FnMut(GlyphId, &str, f32),
483 {
484 for tween in &self.tweens {
485 if tween.cancelled || tween.delay_remaining > 0.0 {
486 continue;
487 }
488 let value = tween.state.value();
489 match &tween.target {
490 TweenTarget::GlyphPositionX(id) => apply(*id, "position_x", value),
491 TweenTarget::GlyphPositionY(id) => apply(*id, "position_y", value),
492 TweenTarget::GlyphPositionZ(id) => apply(*id, "position_z", value),
493 TweenTarget::GlyphScale(id) => apply(*id, "scale", value),
494 TweenTarget::GlyphScaleX(id) => apply(*id, "scale_x", value),
495 TweenTarget::GlyphScaleY(id) => apply(*id, "scale_y", value),
496 TweenTarget::GlyphAlpha(id) => apply(*id, "alpha", value),
497 TweenTarget::GlyphEmission(id) => apply(*id, "emission", value),
498 TweenTarget::GlyphRotation(id) => apply(*id, "rotation", value),
499 TweenTarget::GlyphColorR(id) => apply(*id, "color_r", value),
500 TweenTarget::GlyphColorG(id) => apply(*id, "color_g", value),
501 TweenTarget::GlyphColorB(id) => apply(*id, "color_b", value),
502 TweenTarget::GlyphGlowRadius(id) => apply(*id, "glow_radius", value),
503 TweenTarget::GlyphTemperature(id) => apply(*id, "temperature", value),
504 TweenTarget::GlyphEntropy(id) => apply(*id, "entropy", value),
505 _ => {}
506 }
507 }
508 }
509
510 pub fn reset_overrides(&mut self) {
513 self.screen_bloom_override = None;
514 self.screen_chromatic_override = None;
515 self.screen_vignette_override = None;
516 self.screen_saturation_override = None;
517 self.screen_hue_shift = 0.0;
518 self.camera_fov_override = None;
519 self.camera_position_override = [None; 3];
520 self.camera_target_override = [None; 3];
521 }
522}
523
524impl Default for TweenManager {
525 fn default() -> Self {
526 Self::new()
527 }
528}
529
530#[cfg(test)]
533mod tests {
534 use super::*;
535
536 #[test]
537 fn start_and_tick() {
538 let mut mgr = TweenManager::new();
539 let id = mgr.start(TweenTarget::ScreenFade, 0.0, 1.0, 1.0, Easing::Linear);
540 assert!(mgr.is_active(id));
541 mgr.tick(0.5);
542 assert!((mgr.screen_fade - 0.5).abs() < 0.05);
543 mgr.tick(0.6);
544 assert!(!mgr.is_active(id));
545 assert!((mgr.screen_fade - 1.0).abs() < 0.05);
546 }
547
548 #[test]
549 fn delayed_start() {
550 let mut mgr = TweenManager::new();
551 mgr.start_delayed(TweenTarget::ScreenFade, 0.0, 1.0, 1.0, 0.5, Easing::Linear);
552 mgr.tick(0.3);
553 assert!((mgr.screen_fade - 0.0).abs() < 0.01, "Should still be in delay");
554 mgr.tick(0.3); assert!(mgr.screen_fade > 0.0, "Should have started");
556 }
557
558 #[test]
559 fn cancel_by_id() {
560 let mut mgr = TweenManager::new();
561 let id = mgr.start(TweenTarget::ScreenFade, 0.0, 1.0, 1.0, Easing::Linear);
562 mgr.cancel(id);
563 mgr.tick(0.5);
564 assert!(!mgr.is_active(id));
565 }
566
567 #[test]
568 fn cancel_by_tag() {
569 let mut mgr = TweenManager::new();
570 mgr.start_tagged(TweenTarget::ScreenFade, 0.0, 1.0, 1.0, Easing::Linear, "combat");
571 mgr.start_tagged(TweenTarget::ScreenShake, 0.0, 1.0, 1.0, Easing::Linear, "combat");
572 mgr.start_tagged(TweenTarget::ScreenBloom, 0.0, 1.0, 1.0, Easing::Linear, "menu");
573 assert_eq!(mgr.active_count(), 3);
574 mgr.cancel_tag("combat");
575 mgr.tick(0.0);
576 assert_eq!(mgr.active_count(), 1);
577 }
578
579 #[test]
580 fn bar_values() {
581 let mut mgr = TweenManager::new();
582 let bar = BarId(0);
583 mgr.start(TweenTarget::BarFillPercent(bar), 1.0, 0.5, 0.5, Easing::Linear);
584 mgr.tick(0.25);
585 let val = mgr.get_bar(bar);
586 assert!((val - 0.75).abs() < 0.05);
587 }
588
589 #[test]
590 fn named_values() {
591 let mut mgr = TweenManager::new();
592 mgr.start(TweenTarget::Named("test".to_string()), 0.0, 10.0, 1.0, Easing::Linear);
593 mgr.tick(0.5);
594 assert!((mgr.get_named("test") - 5.0).abs() < 0.5);
595 }
596
597 #[test]
598 fn callback_fires_on_complete() {
599 use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
600 let fired = Arc::new(AtomicBool::new(false));
601 let fired_clone = fired.clone();
602 let mut mgr = TweenManager::new();
603 mgr.start_with_callback(
604 TweenTarget::ScreenFade, 0.0, 1.0, 0.1, Easing::Linear,
605 move |_mgr| { fired_clone.store(true, Ordering::SeqCst); },
606 );
607 mgr.tick(0.2);
608 assert!(fired.load(Ordering::SeqCst));
609 }
610
611 #[test]
612 fn callback_can_chain_tweens() {
613 let mut mgr = TweenManager::new();
614 mgr.start_with_callback(
615 TweenTarget::ScreenFade, 0.0, 1.0, 0.1, Easing::Linear,
616 |mgr| {
617 mgr.start(TweenTarget::ScreenFade, 1.0, 0.0, 0.1, Easing::Linear);
618 },
619 );
620 mgr.tick(0.15); assert!(mgr.active_count() >= 1);
622 }
623}