use std::sync::Arc;
use super::{Animatable, Tween, Vector};
pub trait Animation {
type Value: Animatable;
fn sample(&self, t: f64) -> Self::Value;
fn velocity(&self, t: f64) -> <Self::Value as Animatable>::Vector {
let h = 1e-4;
let (a, b) = ((t - h).max(0.0), t + h);
self.sample(b)
.to_vector()
.minus(self.sample(a).to_vector())
.times(1.0 / (b - a))
}
fn duration(&self) -> Option<f64>;
fn is_finished(&self, t: f64) -> bool {
self.duration().is_some_and(|d| t >= d)
}
}
pub type BoxedAnimation<T> = Arc<dyn Animation<Value = T> + Send + Sync>;
impl<A: Animation + ?Sized> Animation for Arc<A> {
type Value = A::Value;
fn sample(&self, t: f64) -> A::Value {
(**self).sample(t)
}
fn velocity(&self, t: f64) -> <A::Value as Animatable>::Vector {
(**self).velocity(t)
}
fn duration(&self) -> Option<f64> {
(**self).duration()
}
fn is_finished(&self, t: f64) -> bool {
(**self).is_finished(t)
}
}
impl<A: Animation + ?Sized> Animation for Box<A> {
type Value = A::Value;
fn sample(&self, t: f64) -> A::Value {
(**self).sample(t)
}
fn velocity(&self, t: f64) -> <A::Value as Animatable>::Vector {
(**self).velocity(t)
}
fn duration(&self) -> Option<f64> {
(**self).duration()
}
fn is_finished(&self, t: f64) -> bool {
(**self).is_finished(t)
}
}
pub trait AnimationExt: Animation + Sized {
fn delay(self, seconds: f64) -> Delay<Self> {
Delay {
inner: self,
delay: seconds.max(0.0),
}
}
fn repeat(self, count: f64) -> Repeat<Self> {
Repeat {
inner: self,
count: Some(count.max(0.0)),
alternate: false,
}
}
fn repeat_forever(self) -> Repeat<Self> {
Repeat {
inner: self,
count: None,
alternate: false,
}
}
fn then<B>(self, next: B) -> Sequence<Self::Value>
where
Self: Send + Sync + 'static,
B: Animation<Value = Self::Value> + Send + Sync + 'static,
{
Sequence::new(self).then(next)
}
fn boxed(self) -> BoxedAnimation<Self::Value>
where
Self: Send + Sync + 'static,
{
Arc::new(self)
}
}
impl<A: Animation> AnimationExt for A {}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Still<T>(pub T);
impl<T: Animatable> Animation for Still<T> {
type Value = T;
fn sample(&self, _t: f64) -> T {
self.0
}
fn velocity(&self, _t: f64) -> T::Vector {
T::Vector::ZERO
}
fn duration(&self) -> Option<f64> {
Some(0.0)
}
}
impl<T: Animatable> Animation for Tween<T> {
type Value = T;
fn sample(&self, t: f64) -> T {
Tween::sample(self, t)
}
fn velocity(&self, t: f64) -> T::Vector {
if self.duration <= 0.0 || !(0.0..self.duration).contains(&t) {
return T::Vector::ZERO;
}
let d = self.to.to_vector().minus(self.from.to_vector());
d.times(self.easing.slope(t / self.duration) / self.duration)
}
fn duration(&self) -> Option<f64> {
Some(self.duration.max(0.0))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Delay<A> {
pub inner: A,
pub delay: f64,
}
impl<A: Animation> Delay<A> {
fn local(&self, t: f64) -> f64 {
match self.inner.duration() {
Some(d) if t >= self.delay + d => d,
_ => (t - self.delay).max(0.0),
}
}
}
impl<A: Animation> Animation for Delay<A> {
type Value = A::Value;
fn sample(&self, t: f64) -> A::Value {
self.inner.sample(self.local(t))
}
fn velocity(&self, t: f64) -> <A::Value as Animatable>::Vector {
if t < self.delay || self.is_finished(t) {
return Vector::ZERO;
}
self.inner.velocity(self.local(t))
}
fn duration(&self) -> Option<f64> {
self.inner.duration().map(|d| d + self.delay)
}
fn is_finished(&self, t: f64) -> bool {
match self.duration() {
Some(d) => t >= d,
None => t >= self.delay && self.inner.is_finished(t - self.delay),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Repeat<A> {
pub inner: A,
pub count: Option<f64>,
pub alternate: bool,
}
impl<A: Animation> Repeat<A> {
pub fn alternate(mut self) -> Self {
self.alternate = true;
self
}
fn local(&self, t: f64) -> Option<(f64, bool)> {
let d = self.inner.duration().filter(|d| *d > 0.0)?;
let t = t.max(0.0);
let t = match self.count {
Some(c) => t.min(c * d),
None => t,
};
let mut i = (t / d).floor();
let mut u = t - i * d;
if self.count.is_some_and(|c| t >= c * d) && u <= 1e-12 && i > 0.0 {
i -= 1.0;
u = d;
}
let backward = self.alternate && (i as u64) % 2 == 1;
Some(if backward { (d - u, true) } else { (u, false) })
}
}
impl<A: Animation> Animation for Repeat<A> {
type Value = A::Value;
fn sample(&self, t: f64) -> A::Value {
match self.local(t) {
Some((u, _)) => self.inner.sample(u),
None => self.inner.sample(t),
}
}
fn velocity(&self, t: f64) -> <A::Value as Animatable>::Vector {
if self.is_finished(t) {
return Vector::ZERO;
}
match self.local(t) {
Some((u, true)) => self.inner.velocity(u).times(-1.0),
Some((u, false)) => self.inner.velocity(u),
None => self.inner.velocity(t),
}
}
fn duration(&self) -> Option<f64> {
match self.inner.duration().filter(|d| *d > 0.0) {
Some(d) => self.count.map(|c| c * d),
None => self.inner.duration(),
}
}
}
#[derive(Clone)]
pub struct Sequence<T> {
steps: Vec<BoxedAnimation<T>>,
}
impl<T> core::fmt::Debug for Sequence<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Sequence")
.field("steps", &self.steps.len())
.finish()
}
}
impl<T: Animatable> Sequence<T> {
pub fn new(first: impl Animation<Value = T> + Send + Sync + 'static) -> Self {
Self {
steps: vec![Arc::new(first)],
}
}
pub fn then(mut self, next: impl Animation<Value = T> + Send + Sync + 'static) -> Self {
self.steps.push(Arc::new(next));
self
}
fn at(&self, t: f64) -> (&BoxedAnimation<T>, f64) {
let last = self.steps.len() - 1;
if let Some(total) = self.duration()
&& t >= total
{
let step = &self.steps[last];
return (step, step.duration().unwrap_or(0.0));
}
let mut start = 0.0;
for (i, step) in self.steps.iter().enumerate() {
match step.duration() {
Some(d) if i < last && t >= start + d => start += d,
_ => return (step, t - start),
}
}
unreachable!("a sequence has at least one step")
}
}
impl<T: Animatable> Animation for Sequence<T> {
type Value = T;
fn sample(&self, t: f64) -> T {
let (step, u) = self.at(t);
step.sample(u)
}
fn velocity(&self, t: f64) -> T::Vector {
let (step, u) = self.at(t);
step.velocity(u)
}
fn duration(&self) -> Option<f64> {
self.steps.iter().map(|s| s.duration()).sum()
}
fn is_finished(&self, t: f64) -> bool {
self.duration().is_some_and(|d| t >= d)
}
}
impl<A: Animation, B: Animation> Animation for (A, B) {
type Value = (A::Value, B::Value);
fn sample(&self, t: f64) -> Self::Value {
(self.0.sample(t), self.1.sample(t))
}
fn velocity(&self, t: f64) -> <Self::Value as Animatable>::Vector {
(self.0.velocity(t), self.1.velocity(t))
}
fn duration(&self) -> Option<f64> {
Some(self.0.duration()?.max(self.1.duration()?))
}
fn is_finished(&self, t: f64) -> bool {
self.0.is_finished(t) && self.1.is_finished(t)
}
}
impl<A: Animation, B: Animation, C: Animation> Animation for (A, B, C) {
type Value = (A::Value, B::Value, C::Value);
fn sample(&self, t: f64) -> Self::Value {
(self.0.sample(t), self.1.sample(t), self.2.sample(t))
}
fn velocity(&self, t: f64) -> <Self::Value as Animatable>::Vector {
(self.0.velocity(t), self.1.velocity(t), self.2.velocity(t))
}
fn duration(&self) -> Option<f64> {
Some(
self.0
.duration()?
.max(self.1.duration()?)
.max(self.2.duration()?),
)
}
fn is_finished(&self, t: f64) -> bool {
self.0.is_finished(t) && self.1.is_finished(t) && self.2.is_finished(t)
}
}
pub fn stagger(index: usize, step: f64) -> f64 {
index as f64 * step
}
pub fn stagger_within(index: usize, count: usize, step: f64, max: f64) -> f64 {
let step = if count > 1 {
step.min(max / (count - 1) as f64)
} else {
step
};
stagger(index, step)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Decay<T: Animatable> {
pub from: T,
pub velocity: T::Vector,
pub rate: f64,
}
impl<T: Animatable> Decay<T> {
pub const NORMAL: f64 = 2.002;
pub const FAST: f64 = 10.05;
pub fn new(from: T, velocity: T::Vector, rate: f64) -> Self {
Self {
from,
velocity,
rate,
}
}
pub fn target(&self) -> T {
if self.rate <= 0.0 {
return self.from;
}
T::from_vector(
self.from
.to_vector()
.plus(self.velocity.times(1.0 / self.rate)),
)
}
}
impl<T: Animatable> Animation for Decay<T> {
type Value = T;
fn sample(&self, t: f64) -> T {
if self.rate <= 0.0 {
return self.from;
}
if self.is_finished(t) {
return self.target();
}
let k = (1.0 - (-self.rate * t.max(0.0)).exp()) / self.rate;
T::from_vector(self.from.to_vector().plus(self.velocity.times(k)))
}
fn velocity(&self, t: f64) -> T::Vector {
if self.rate <= 0.0 || self.is_finished(t) {
return T::Vector::ZERO;
}
self.velocity.times((-self.rate * t.max(0.0)).exp())
}
fn duration(&self) -> Option<f64> {
let remaining = self.velocity.norm() / self.rate;
if self.rate <= 0.0 || remaining < super::REST_EPSILON {
return Some(0.0);
}
Some((remaining / super::REST_EPSILON).ln() / self.rate)
}
}
#[cfg(test)]
mod tests {
use super::super::{Easing, Keyframes};
use super::*;
fn close(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-9
}
#[test]
fn delay_holds_then_plays() {
let a = Tween::new(0.0, 10.0, 1.0, Easing::Linear).delay(0.5);
assert_eq!(a.sample(0.0), 0.0);
assert_eq!(a.sample(0.5), 0.0);
assert!(close(a.sample(1.0), 5.0));
assert_eq!(a.velocity(0.25), 0.0);
assert!(close(a.velocity(1.0), 10.0));
assert_eq!(a.duration(), Some(1.5));
assert!(!a.is_finished(1.49) && a.is_finished(1.5));
}
#[test]
fn repeat_counts_and_alternates() {
let tw = Tween::new(0.0, 1.0, 1.0, Easing::Linear);
let r = tw.repeat(3.0);
assert!(close(r.sample(0.25), 0.25));
assert!(close(r.sample(1.25), 0.25), "restarts");
assert_eq!(r.sample(3.0), 1.0, "ends at the end of the last run");
assert_eq!(r.sample(10.0), 1.0);
assert_eq!(r.duration(), Some(3.0));
assert!(r.is_finished(3.0) && !r.is_finished(2.99));
let a = tw.repeat(2.0).alternate();
assert!(close(a.sample(0.75), 0.75));
assert!(close(a.sample(1.25), 0.75), "second run goes back");
assert!(close(a.velocity(1.25), -1.0));
assert!(close(a.sample(2.0), 0.0), "even count ends where it began");
assert_eq!(a.velocity(2.0), 0.0);
let half = tw.repeat(1.5);
assert!(
close(half.sample(5.0), 0.5),
"fractional count stops mid-run"
);
let f = tw.repeat_forever().alternate();
assert_eq!(f.duration(), None);
assert!(!f.is_finished(1e6));
assert!(close(f.sample(1001.5), 0.5));
assert!(close(f.sample(1000.25), 0.25));
assert!(close(f.sample(1001.25), 0.75));
}
#[test]
fn sequences_chain_steps() {
let s = Tween::new(0.0, 1.0, 1.0, Easing::Linear)
.then(Still(1.0).delay(0.5))
.then(Tween::new(1.0, 0.0, 2.0, Easing::Linear));
assert_eq!(s.duration(), Some(3.5));
assert!(close(s.sample(0.5), 0.5));
assert_eq!(s.sample(1.2), 1.0);
assert!(close(s.sample(2.5), 0.5));
assert!(close(s.velocity(2.5), -0.5));
assert_eq!(s.sample(9.0), 0.0);
assert!(!s.is_finished(3.4) && s.is_finished(3.5));
}
#[test]
fn parallel_tuples_finish_together() {
let p = (
Tween::new(0.0, 1.0, 1.0, Easing::Linear),
Keyframes::new(Point::ORIGIN).to(Point::new(4.0, 0.0), 2.0, Easing::Linear),
);
assert_eq!(p.duration(), Some(2.0));
let (a, b) = p.sample(1.0);
assert_eq!((a, b), (1.0, Point::new(2.0, 0.0)));
assert!(!p.is_finished(1.5) && p.is_finished(2.0));
}
use crate::geometry::Point;
#[test]
fn stagger_spaces_items() {
assert_eq!(stagger(0, 0.05), 0.0);
assert!(close(stagger(3, 0.05), 0.15));
assert!(close(stagger_within(100, 101, 0.05, 1.0), 1.0));
assert!(close(stagger_within(2, 3, 0.05, 1.0), 0.1));
let entrance = |i| Tween::new(0.0, 1.0, 0.2, Easing::Linear).delay(stagger(i, 0.1));
assert_eq!(entrance(2).sample(0.19), 0.0);
assert!(close(entrance(2).sample(0.3), 0.5));
assert!(entrance(0).is_finished(0.2) && !entrance(1).is_finished(0.2));
}
#[test]
fn decay_glides_to_its_target() {
let d = Decay::new(0.0, 1000.0, Decay::<f64>::NORMAL);
assert!(close(d.target(), 1000.0 / Decay::<f64>::NORMAL));
assert_eq!(d.velocity(0.0), 1000.0);
let end = d.duration().unwrap();
assert!(end.is_finite() && end > 1.0);
assert_eq!(d.sample(end), d.target());
assert!((d.sample(end * 0.999) - d.target()).abs() < 1e-2);
let h = 1e-6;
let fd = (d.sample(0.3 + h) - d.sample(0.3 - h)) / (2.0 * h);
assert!((fd - d.velocity(0.3)).abs() < 1e-3);
}
}