animato_devtools/
inspector.rs1use animato_core::{AnimationKind, Easing, PlaybackState};
4use animato_driver::{AnimationDriver, AnimationId};
5
6#[derive(Clone, Debug, PartialEq)]
8pub struct AnimationSnapshot {
9 pub id: AnimationId,
11 pub label: Option<String>,
13 pub kind: AnimationKind,
15 pub progress: f32,
17 pub elapsed: f32,
19 pub duration: Option<f32>,
21 pub state: PlaybackState,
23 pub easing: Option<Easing>,
25}
26
27impl AnimationSnapshot {
28 pub fn color_name(&self) -> &'static str {
30 match self.kind {
31 AnimationKind::Tween => "blue",
32 AnimationKind::Spring => "green",
33 AnimationKind::Keyframe => "violet",
34 AnimationKind::Timeline => "amber",
35 AnimationKind::Group => "cyan",
36 AnimationKind::Custom => "gray",
37 }
38 }
39
40 pub fn progress_bar(&self, width: usize) -> String {
42 let width = width.max(1);
43 let filled = ((self.progress.clamp(0.0, 1.0) * width as f32).round() as usize).min(width);
44 let mut out = String::with_capacity(width);
45 out.extend(core::iter::repeat_n('#', filled));
46 out.extend(core::iter::repeat_n('-', width - filled));
47 out
48 }
49}
50
51#[derive(Clone, Debug, Default, PartialEq)]
53pub struct TimelineInspector {
54 snapshots: Vec<AnimationSnapshot>,
55 completed_count: usize,
56}
57
58impl TimelineInspector {
59 pub fn new() -> Self {
61 Self::default()
62 }
63
64 pub fn capture(&mut self, driver: &AnimationDriver) {
66 self.snapshots.clear();
67 self.snapshots
68 .extend(driver.snapshots().into_iter().map(|snapshot| {
69 let introspection = snapshot.introspection;
70 AnimationSnapshot {
71 id: snapshot.id,
72 label: snapshot.label,
73 kind: introspection.kind,
74 progress: introspection.progress,
75 elapsed: introspection.elapsed,
76 duration: introspection.duration,
77 state: introspection.state,
78 easing: introspection.easing,
79 }
80 }));
81 self.completed_count = driver.completed_count();
82 }
83
84 pub fn capture_into(&self, out: &mut Vec<AnimationSnapshot>) {
86 out.clear();
87 out.extend_from_slice(&self.snapshots);
88 }
89
90 pub fn snapshots(&self) -> &[AnimationSnapshot] {
92 &self.snapshots
93 }
94
95 pub fn active_count(&self) -> usize {
97 self.snapshots
98 .iter()
99 .filter(|snapshot| snapshot.state != PlaybackState::Complete)
100 .count()
101 }
102
103 pub fn completed_count(&self) -> usize {
105 self.completed_count
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use animato_core::{AnimationKind, PlaybackState};
113 use animato_driver::AnimationDriver;
114 use animato_tween::Tween;
115
116 #[test]
117 fn captures_driver_snapshots() {
118 let mut driver = AnimationDriver::new();
119 driver.add_inspectable("fade", Tween::new(0.0_f32, 1.0).duration(1.0).build());
120 driver.tick(0.25);
121
122 let mut inspector = TimelineInspector::new();
123 inspector.capture(&driver);
124
125 assert_eq!(inspector.snapshots().len(), 1);
126 let snapshot = &inspector.snapshots()[0];
127 assert_eq!(snapshot.label.as_deref(), Some("fade"));
128 assert_eq!(snapshot.kind, AnimationKind::Tween);
129 assert_eq!(snapshot.state, PlaybackState::Playing);
130 assert!((snapshot.progress - 0.25).abs() < 0.001);
131 assert_eq!(snapshot.progress_bar(4), "#---");
132 }
133
134 #[test]
135 fn reports_completed_count() {
136 let mut driver = AnimationDriver::new();
137 driver.add_inspectable("fade", Tween::new(0.0_f32, 1.0).duration(0.1).build());
138 driver.tick(1.0);
139
140 let mut inspector = TimelineInspector::new();
141 inspector.capture(&driver);
142 assert_eq!(inspector.completed_count(), 1);
143 assert_eq!(inspector.active_count(), 0);
144 }
145}