use crate::{
ActiveStateTransition, AnimationHandle, AnimationRuntime, AnimationTargetId, Duration,
StateTransition, StateTransitionProgress, StateTransitionRegistration, StateTransitionSet,
Timeline, runtime::AnimationClock,
};
use std::{hash::Hash, sync::Arc};
#[derive(Debug, Clone, PartialEq)]
pub struct StateAnimator<S>
where
S: Copy + Eq + Hash,
{
target: AnimationTargetId,
current: S,
active: Option<ActiveStateTransition<S>>,
}
impl<S> StateAnimator<S>
where
S: Copy + Eq + Hash,
{
#[must_use]
pub const fn new(target: AnimationTargetId, initial: S) -> Self {
Self {
target,
current: initial,
active: None,
}
}
#[must_use]
pub const fn target(&self) -> AnimationTargetId {
self.target
}
#[must_use]
pub const fn current(&self) -> S {
self.current
}
#[must_use]
pub const fn active_handle(&self) -> Option<AnimationHandle> {
match self.active {
Some(active) => Some(active.handle()),
None => None,
}
}
#[must_use]
pub fn is_active<C: AnimationClock>(&self, runtime: &AnimationRuntime<C>) -> bool {
match self.active {
Some(active) => runtime.contains(self.target, active.handle()),
None => false,
}
}
#[must_use]
pub const fn active_transition(&self) -> Option<&ActiveStateTransition<S>> {
self.active.as_ref()
}
#[must_use]
pub fn active_progress_at(&self, timestamp: Duration) -> Option<StateTransitionProgress<S>> {
self.active
.as_ref()
.map(|active| active.progress_at(timestamp))
}
pub fn handle_completion<C: AnimationClock>(&mut self, runtime: &AnimationRuntime<C>) -> bool {
let Some(active) = self.active else {
return false;
};
if runtime.contains(self.target, active.handle()) {
return false;
}
self.active = None;
true
}
pub fn transition_with<C: AnimationClock>(
&mut self,
runtime: &mut AnimationRuntime<C>,
transition: &StateTransition<S>,
) -> Option<StateTransitionRegistration<S>> {
self.invalidate_if_stale(runtime);
if self.current != transition.from() || transition.from() == transition.to() {
return None;
}
Some(self.register_timeline(
runtime,
transition.from(),
transition.to(),
transition.timeline_arc(),
))
}
pub fn transition_to<C: AnimationClock>(
&mut self,
runtime: &mut AnimationRuntime<C>,
to: S,
transitions: &StateTransitionSet<S>,
) -> Option<StateTransitionRegistration<S>> {
self.invalidate_if_stale(runtime);
if self.current == to {
return None;
}
if let Some(transition) = transitions.find(self.current, to) {
return self.transition_with(runtime, transition);
}
let fallback = transitions.fallback_arc()?;
Some(self.register_timeline(runtime, self.current, to, fallback.clone()))
}
fn register_timeline<C: AnimationClock>(
&mut self,
runtime: &mut AnimationRuntime<C>,
from: S,
to: S,
timeline: Arc<Timeline>,
) -> StateTransitionRegistration<S> {
let replaced = self.active.take();
let started_at = runtime.clock().now();
let duration = timeline.total_duration();
self.current = to;
let registration = runtime.register_timeline_arc(self.target, timeline);
self.active = Some(ActiveStateTransition::new(
registration.handle(),
from,
to,
started_at,
duration,
));
self.cleanup_replaced(runtime, replaced);
StateTransitionRegistration::new(registration, replaced)
}
fn cleanup_replaced<C: AnimationClock>(
&self,
runtime: &mut AnimationRuntime<C>,
replaced: Option<ActiveStateTransition<S>>,
) {
if let Some(active) = replaced {
runtime.cancel(self.target, active.handle());
}
}
fn invalidate_if_stale<C: AnimationClock>(&mut self, runtime: &AnimationRuntime<C>) {
if let Some(active) = self.active
&& !runtime.contains(self.target, active.handle())
{
self.active = None;
}
}
}