use eazy_core::{Curve, Easing};
use eazy_tweener::Tweenable;
#[derive(Debug, Clone)]
pub struct Keyframe<T: Tweenable> {
time: f32,
value: T,
easing: Option<Easing>,
}
impl<T: Tweenable> Keyframe<T> {
pub fn new(time: f32, value: T) -> Self {
Self {
time,
value,
easing: None,
}
}
pub fn with_easing(mut self, easing: Easing) -> Self {
self.easing = Some(easing);
self
}
pub fn set_easing(&mut self, easing: Option<Easing>) {
self.easing = easing;
}
pub fn time(&self) -> f32 {
self.time
}
pub fn value(&self) -> T {
self.value
}
pub fn easing(&self) -> Option<&Easing> {
self.easing.as_ref()
}
pub fn has_easing(&self) -> bool {
self.easing.is_some()
}
#[inline]
pub fn tween_to(&self, next: &Keyframe<T>, time: f32) -> T {
if time <= self.time {
return self.value;
}
if time >= next.time {
return next.value;
}
if next.time <= self.time {
return next.value;
}
let t = (time - self.time) / (next.time - self.time);
let eased_t = match &next.easing {
Some(easing) => easing.y(t),
None => t, };
self.value.lerp(next.value, eased_t)
}
}
impl<T: Tweenable> PartialEq for Keyframe<T> {
fn eq(&self, other: &Self) -> bool {
self.time == other.time
}
}
impl<T: Tweenable> From<(f32, T)> for Keyframe<T> {
#[inline]
fn from((time, value): (f32, T)) -> Self {
Keyframe::new(time, value)
}
}
impl<T: Tweenable> From<(f32, T, Easing)> for Keyframe<T> {
#[inline]
fn from((time, value, easing): (f32, T, Easing)) -> Self {
Keyframe::new(time, value).with_easing(easing)
}
}
impl<T: Tweenable> PartialOrd for Keyframe<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.time.partial_cmp(&other.time)
}
}
pub fn keyframe<T: Tweenable>(time: f32, value: T) -> Keyframe<T> {
Keyframe::new(time, value)
}
pub fn keyframe_eased<T: Tweenable>(
time: f32,
value: T,
easing: Easing,
) -> Keyframe<T> {
Keyframe::new(time, value).with_easing(easing)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keyframe_creation() {
let kf = Keyframe::new(0.5, 100.0_f32);
assert_eq!(kf.time(), 0.5);
assert_eq!(kf.value(), 100.0);
assert!(kf.easing().is_none());
}
#[test]
fn test_keyframe_with_easing() {
let kf = Keyframe::new(0.5, 100.0_f32).with_easing(Easing::OutBounce);
assert!(kf.has_easing());
}
#[test]
fn test_keyframe_ordering() {
let kf1 = Keyframe::new(0.2, 50.0_f32);
let kf2 = Keyframe::new(0.5, 100.0_f32);
let kf3 = Keyframe::new(0.8, 150.0_f32);
assert!(kf1 < kf2);
assert!(kf2 < kf3);
assert!(kf1 < kf3);
}
#[test]
fn test_keyframe_array() {
let kf = Keyframe::new(0.5, [1.0_f32, 2.0, 3.0]);
assert_eq!(kf.value(), [1.0, 2.0, 3.0]);
}
#[test]
fn test_tween_to_linear() {
let kf1 = Keyframe::new(0.0, 0.0_f32);
let kf2 = Keyframe::new(1.0, 100.0_f32);
assert_eq!(kf1.tween_to(&kf2, 0.0), 0.0);
assert_eq!(kf1.tween_to(&kf2, 0.5), 50.0);
assert_eq!(kf1.tween_to(&kf2, 1.0), 100.0);
}
#[test]
fn test_tween_to_clamping() {
let kf1 = Keyframe::new(0.2, 10.0_f32);
let kf2 = Keyframe::new(0.8, 80.0_f32);
assert_eq!(kf1.tween_to(&kf2, 0.0), 10.0);
assert_eq!(kf1.tween_to(&kf2, 0.1), 10.0);
assert_eq!(kf1.tween_to(&kf2, 0.9), 80.0);
assert_eq!(kf1.tween_to(&kf2, 1.0), 80.0);
}
#[test]
fn test_tween_to_with_easing() {
let kf1 = Keyframe::new(0.0, 0.0_f32);
let kf2 = Keyframe::new(1.0, 100.0_f32).with_easing(Easing::InQuadratic);
let value = kf1.tween_to(&kf2, 0.5);
assert_eq!(value, 25.0);
}
#[test]
fn test_tween_to_array() {
let kf1 = Keyframe::new(0.0, [0.0_f32, 0.0, 0.0]);
let kf2 = Keyframe::new(1.0, [100.0, 200.0, 300.0]);
let value = kf1.tween_to(&kf2, 0.5);
assert_eq!(value, [50.0, 100.0, 150.0]);
}
}