use super::{
animation::{Animation, BaseAnimation, Boxed, IsFinished},
Animatable, Options, DURATION_ZERO,
};
use std::{
fmt::Debug,
sync::atomic::AtomicUsize,
time::{Duration, Instant},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TimelineId(usize);
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Status {
Idle,
Animating,
Paused,
Completed,
}
impl Status {
#[inline]
pub fn is_idle(&self) -> bool {
self == &Status::Idle
}
#[inline]
pub fn is_animating(&self) -> bool {
self == &Status::Animating
}
#[inline]
pub fn is_paused(&self) -> bool {
self == &Status::Paused
}
#[inline]
pub fn is_completed(&self) -> bool {
self == &Status::Completed
}
}
#[derive(Debug)]
enum State {
Idle,
Animating {
time: Instant,
elapsed: Option<Duration>,
},
Paused { elapsed: Option<Duration> },
Completed { elapsed: Option<Duration> },
}
static ID_GEN: AtomicUsize = AtomicUsize::new(1);
#[derive(Debug)]
pub struct Timeline<T> {
id: usize,
animation: Boxed<T>, state: State,
}
impl<T> Timeline<T> {
#[inline]
pub fn new<F>(animation: F) -> Self
where
F: Animation<Item = T> + 'static,
{
Self {
id: ID_GEN.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
animation: Boxed::new(animation),
state: State::Idle,
}
}
#[inline]
pub fn id(&self) -> TimelineId {
TimelineId(self.id)
}
#[inline]
pub fn begin(&mut self) {
let now = Instant::now();
self.state = State::Animating {
time: now,
elapsed: None,
}
}
#[inline]
pub fn stop(&mut self) {
match self.state {
State::Idle | State::Completed { .. } => {}
State::Animating { time, elapsed } => {
let elapsed = if let Some(elapsed) = elapsed {
elapsed + time.elapsed()
} else {
time.elapsed()
};
self.state = State::Completed {
elapsed: Some(elapsed),
}
}
State::Paused { elapsed } => self.state = State::Completed { elapsed },
}
}
#[inline]
pub fn pause(&mut self) {
if let State::Animating { time, elapsed } = self.state {
self.state = State::Paused {
elapsed: Some(elapsed.unwrap_or_default() + time.elapsed()),
};
}
}
#[inline]
pub fn resume(&mut self) {
match self.state {
State::Paused { elapsed } => {
self.state = State::Animating {
time: Instant::now(),
elapsed,
};
}
_ => self.begin(),
}
}
#[inline]
pub fn reset(&mut self) {
if let State::Completed { .. } = self.state {
self.state = State::Completed { elapsed: None };
}
}
#[inline]
pub fn status(&self) -> Status {
match self.state {
State::Idle => Status::Idle,
State::Animating { .. } => Status::Animating,
State::Paused { .. } => Status::Paused,
State::Completed { .. } => Status::Completed,
}
}
#[inline]
pub fn value(&self) -> T {
match self.state {
State::Idle => self.animation.animate(DURATION_ZERO),
State::Animating { time, elapsed } => {
let elapsed = if let Some(elapsed) = elapsed {
elapsed + time.elapsed()
} else {
time.elapsed()
};
self.animation.animate(elapsed)
}
State::Paused { elapsed } => self.animation.animate(elapsed.unwrap_or(DURATION_ZERO)),
State::Completed { elapsed, .. } => {
if let Some(elapsed) = elapsed {
self.animation.animate(elapsed)
} else {
self.animation.animate(DURATION_ZERO)
}
}
}
}
#[inline]
pub fn update(&mut self) -> Status {
self.update_with_time(Instant::now())
}
#[inline]
pub fn update_with_time(&mut self, now: Instant) -> Status {
match self.state {
State::Idle => Status::Idle,
State::Animating { time, elapsed } => {
let elapsed = if let Some(elapsed) = elapsed {
elapsed + (now - time)
} else {
now - time
};
if self.animation.is_finished(elapsed) {
self.state = State::Completed {
elapsed: Some(elapsed),
};
return Status::Completed;
}
Status::Animating
}
State::Paused { .. } => Status::Paused,
State::Completed { .. } => Status::Completed,
}
}
}
impl<T: Animation + 'static> From<T> for Timeline<T::Item> {
#[inline]
fn from(src: T) -> Self {
let src = Boxed::new(src);
Timeline::new(src)
}
}
impl<T: Animatable + 'static> From<Options<T>> for Timeline<T> {
#[inline]
fn from(opt: Options<T>) -> Self {
Timeline::new(opt.build())
}
}