use super::Interpolatable;
use crate::utils::easing::Easing;
#[derive(Clone, Debug)]
pub struct Keyframe<T: Interpolatable> {
pub time: f64,
pub value: T,
pub easing: Easing,
}
impl<T: Interpolatable> Keyframe<T> {
pub fn new(time: f64, value: T) -> Self {
Self {
time: time.clamp(0.0, 1.0),
value,
easing: Easing::Linear,
}
}
pub fn easing(mut self, easing: Easing) -> Self {
self.easing = easing;
self
}
}
#[derive(Clone, Debug)]
pub struct Keyframes<T: Interpolatable> {
keyframes: Vec<Keyframe<T>>,
}
impl<T: Interpolatable> Default for Keyframes<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Interpolatable> Keyframes<T> {
pub fn new() -> Self {
Self {
keyframes: Vec::new(),
}
}
pub fn add(mut self, time: f64, value: T) -> Self {
self.keyframes.push(Keyframe::new(time, value));
self.keyframes
.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
self
}
pub fn add_eased(mut self, time: f64, value: T, easing: Easing) -> Self {
self.keyframes
.push(Keyframe::new(time, value).easing(easing));
self.keyframes
.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
self
}
pub fn len(&self) -> usize {
self.keyframes.len()
}
pub fn is_empty(&self) -> bool {
self.keyframes.is_empty()
}
pub fn at(&self, t: f64) -> Option<T> {
if self.keyframes.is_empty() {
return None;
}
let t = t.clamp(0.0, 1.0);
let mut prev = &self.keyframes[0];
let mut next = &self.keyframes[self.keyframes.len() - 1];
for kf in &self.keyframes {
if kf.time <= t {
prev = kf;
}
if kf.time >= t {
next = kf;
break;
}
}
if prev.time >= next.time {
return Some(prev.value.clone());
}
let local_t = (t - prev.time) / (next.time - prev.time);
let eased_t = next.easing.ease(local_t);
Some(prev.value.lerp(&next.value, eased_t))
}
}