use crate::transition_item::TransitionItem;
use crate::Easing;
use std::time::{Duration, SystemTime};
#[derive(Clone, Debug)]
pub struct InertialValue<T: TransitionItem> {
target: T,
start_time: SystemTime,
duration: Duration,
easing: Easing,
parent: Option<Box<InertialValue<T>>>,
}
impl<T: TransitionItem> InertialValue<T> {
pub fn new(value: T, start_time: SystemTime) -> Self {
Self {
target: value,
start_time,
duration: Duration::default(),
easing: Easing::None,
parent: None,
}
}
pub fn is_finished(&self, current_time: SystemTime) -> bool {
current_time > self.start_time + self.duration
}
pub fn target(&self) -> T {
self.target.clone()
}
pub fn end_time(&self) -> SystemTime {
self.start_time + self.duration
}
pub fn get(&self, current_time: SystemTime) -> T {
if current_time < self.start_time {
if let Some(parent) = &self.parent {
parent.get(current_time)
} else {
self.target.clone()
}
} else if self.is_finished(current_time) {
self.target.clone()
} else if let Some(parent) = &self.parent {
let elapsed = current_time
.duration_since(self.start_time)
.unwrap_or_default();
let t = elapsed.as_secs_f32() / self.duration.as_secs_f32();
let t = self.easing.ease(t);
parent.get(current_time).mix(self.target.clone(), t)
} else {
self.target.clone()
}
}
pub fn go_to(self, target: T, current_time: SystemTime, duration: Duration) -> Self {
self.ease_to(target, current_time, duration, Easing::default())
}
pub fn ease_to(
self,
target: T,
current_time: SystemTime,
duration: Duration,
easing: Easing,
) -> Self {
if target == self.target {
self
} else {
Self {
target,
start_time: current_time,
duration,
easing,
parent: self.clean_up_at(current_time),
}
}
}
pub(self) fn clean_up_at(self, current_time: SystemTime) -> 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::*;
#[test]
fn new_at() {
let start_time = SystemTime::now();
let inertial_value = InertialValue::new(5, start_time);
assert_eq!(inertial_value.get(start_time), 5);
assert_eq!(inertial_value.get(start_time + Duration::from_secs(1)), 5);
}
#[test]
fn go_to_at() {
let start_time = SystemTime::now();
let inertial_value = InertialValue::new(5.0, start_time);
let new_start_time = start_time + Duration::from_millis(500);
let new_duration = Duration::from_secs(1);
let new_inertial_value = inertial_value.go_to(10.0, new_start_time, new_duration);
assert_eq!(new_inertial_value.get(start_time), 5.0);
assert_eq!(new_inertial_value.get(new_start_time), 5.0);
assert_eq!(
new_inertial_value.get(new_start_time + Duration::from_millis(500)),
7.5
);
assert_eq!(new_inertial_value.get(new_start_time + new_duration), 10.0);
}
}