use animato_core::{AnimationKind, Easing, PlaybackState};
use animato_driver::{AnimationDriver, AnimationId};
#[derive(Clone, Debug, PartialEq)]
pub struct AnimationSnapshot {
pub id: AnimationId,
pub label: Option<String>,
pub kind: AnimationKind,
pub progress: f32,
pub elapsed: f32,
pub duration: Option<f32>,
pub state: PlaybackState,
pub easing: Option<Easing>,
}
impl AnimationSnapshot {
pub fn color_name(&self) -> &'static str {
match self.kind {
AnimationKind::Tween => "blue",
AnimationKind::Spring => "green",
AnimationKind::Keyframe => "violet",
AnimationKind::Timeline => "amber",
AnimationKind::Group => "cyan",
AnimationKind::Custom => "gray",
}
}
pub fn progress_bar(&self, width: usize) -> String {
let width = width.max(1);
let filled = ((self.progress.clamp(0.0, 1.0) * width as f32).round() as usize).min(width);
let mut out = String::with_capacity(width);
out.extend(core::iter::repeat_n('#', filled));
out.extend(core::iter::repeat_n('-', width - filled));
out
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TimelineInspector {
snapshots: Vec<AnimationSnapshot>,
completed_count: usize,
}
impl TimelineInspector {
pub fn new() -> Self {
Self::default()
}
pub fn capture(&mut self, driver: &AnimationDriver) {
self.snapshots.clear();
self.snapshots
.extend(driver.snapshots().into_iter().map(|snapshot| {
let introspection = snapshot.introspection;
AnimationSnapshot {
id: snapshot.id,
label: snapshot.label,
kind: introspection.kind,
progress: introspection.progress,
elapsed: introspection.elapsed,
duration: introspection.duration,
state: introspection.state,
easing: introspection.easing,
}
}));
self.completed_count = driver.completed_count();
}
pub fn capture_into(&self, out: &mut Vec<AnimationSnapshot>) {
out.clear();
out.extend_from_slice(&self.snapshots);
}
pub fn snapshots(&self) -> &[AnimationSnapshot] {
&self.snapshots
}
pub fn active_count(&self) -> usize {
self.snapshots
.iter()
.filter(|snapshot| snapshot.state != PlaybackState::Complete)
.count()
}
pub fn completed_count(&self) -> usize {
self.completed_count
}
}
#[cfg(test)]
mod tests {
use super::*;
use animato_core::{AnimationKind, PlaybackState};
use animato_driver::AnimationDriver;
use animato_tween::Tween;
#[test]
fn captures_driver_snapshots() {
let mut driver = AnimationDriver::new();
driver.add_inspectable("fade", Tween::new(0.0_f32, 1.0).duration(1.0).build());
driver.tick(0.25);
let mut inspector = TimelineInspector::new();
inspector.capture(&driver);
assert_eq!(inspector.snapshots().len(), 1);
let snapshot = &inspector.snapshots()[0];
assert_eq!(snapshot.label.as_deref(), Some("fade"));
assert_eq!(snapshot.kind, AnimationKind::Tween);
assert_eq!(snapshot.state, PlaybackState::Playing);
assert!((snapshot.progress - 0.25).abs() < 0.001);
assert_eq!(snapshot.progress_bar(4), "#---");
}
#[test]
fn reports_completed_count() {
let mut driver = AnimationDriver::new();
driver.add_inspectable("fade", Tween::new(0.0_f32, 1.0).duration(0.1).build());
driver.tick(1.0);
let mut inspector = TimelineInspector::new();
inspector.capture(&driver);
assert_eq!(inspector.completed_count(), 1);
assert_eq!(inspector.active_count(), 0);
}
}