use crate::animated::Animated;
use crate::Easing;
use crate::{Mix, Time};
use std::fmt::Debug;
#[derive(Clone, PartialEq)]
pub struct Inertial<Item: Mix + Clone + PartialEq, X: Time> {
target: Item,
start_time: Option<X>,
duration: X::Duration,
easing: Easing,
parent: Option<Box<Inertial<Item, X>>>,
}
impl<Item: Mix + Clone + PartialEq, X: Time> Animated<Item, X> for Inertial<Item, X> {
fn get(&self, current_time: X) -> Item {
if let Some(start_time) = self.start_time {
if current_time < start_time {
if let Some(parent) = &self.parent {
parent.get(current_time)
} else {
self.target.clone()
}
} else if self.is_finished(current_time) || self.duration == Default::default() {
self.target.clone()
} else if let Some(parent) = &self.parent {
let elapsed = current_time.since(start_time);
let t = X::duration_as_f32(elapsed) / X::duration_as_f32(self.duration);
let t = self.easing.ease(t);
parent.get(current_time).mix(self.target.clone(), t)
} else {
self.target.clone()
}
} else {
self.target.clone()
}
}
fn is_finished(&self, current_time: X) -> bool {
self.end_time()
.map(|end_time| current_time > end_time)
.unwrap_or(true)
}
}
impl<Item: Mix + Clone + PartialEq + Debug, X: Time + Debug> Debug for Inertial<Item, X>
where
X::Duration: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Inertial")
.field("target", &self.target)
.field("start_time", &self.start_time)
.field("duration", &self.duration)
.field("easing", &self.easing)
.field("parent", &self.parent)
.finish()
}
}
impl<Item: Mix + Clone + PartialEq, X: Time> From<Item> for Inertial<Item, X> {
fn from(value: Item) -> Self {
Self::new(value)
}
}
impl<Item: Mix + Clone + PartialEq + Default, X: Time> Default for Inertial<Item, X>
where
X::Duration: Default,
{
fn default() -> Self {
Self {
target: Default::default(),
start_time: Default::default(),
duration: Default::default(),
easing: Easing::None,
parent: None,
}
}
}
impl<Item: Mix + Clone + PartialEq, X: Time> Inertial<Item, X> {
pub fn new(value: Item) -> Self {
Self {
target: value,
start_time: Default::default(),
duration: Default::default(),
easing: Easing::None,
parent: None,
}
}
pub fn target(&self) -> Item {
self.target.clone()
}
pub fn end_time(&self) -> Option<X> {
self.start_time
.map(|start_time| start_time.advance(self.duration))
}
pub fn go_to(self, target: Item, current_time: X, duration: X::Duration) -> Self {
self.ease_to(target, current_time, duration, Easing::default())
}
pub fn ease_to(
self,
target: Item,
current_time: X,
duration: X::Duration,
easing: Easing,
) -> Self {
if target == self.target {
self
} else {
Self {
target,
start_time: Some(current_time),
duration,
easing,
parent: self.clean_up_at(current_time),
}
}
}
pub(self) fn clean_up_at(self, current_time: X) -> Option<Box<Self>> {
let is_finished = self.is_finished(current_time);
Some(Box::new(Self {
target: self.target,
start_time: self.start_time,
duration: self.duration,
easing: self.easing,
parent: if is_finished {
None
} else {
self.parent
.and_then(|parent| parent.clean_up_at(current_time))
},
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
#[test]
fn new_at() {
let start_time = Instant::now();
let inertial = Inertial::new(5);
assert_eq!(inertial.get(start_time), 5);
assert_eq!(inertial.get(start_time + Duration::from_secs(1)), 5);
}
#[test]
fn go_to_at() {
let start_time = Instant::now();
let inertial = Inertial::new(5.0);
let new_start_time = start_time + Duration::from_millis(500);
let new_duration = Duration::from_secs(1);
let new_inertial = inertial.go_to(10.0, new_start_time, new_duration);
assert_eq!(new_inertial.get(start_time), 5.0);
assert_eq!(new_inertial.get(new_start_time), 5.0);
assert_eq!(
new_inertial.get(new_start_time + Duration::from_millis(500)),
7.5
);
assert_eq!(new_inertial.get(new_start_time + new_duration), 10.0);
}
}