use super::controller::{AnimationController, Status};
use super::curve::Curve;
use crate::{Alignment, BorderRadius, Color, Insets, Point, Size};
pub trait Lerp: Copy {
fn lerp(self, other: Self, t: f32) -> Self;
}
impl Lerp for f32 {
fn lerp(self, other: Self, t: f32) -> Self {
self + (other - self) * t
}
}
impl Lerp for Color {
fn lerp(self, other: Self, t: f32) -> Self {
Color::lerp(self, other, t)
}
}
impl Lerp for Point {
fn lerp(self, other: Self, t: f32) -> Self {
Point::new(self.x.lerp(other.x, t), self.y.lerp(other.y, t))
}
}
impl Lerp for Size {
fn lerp(self, other: Self, t: f32) -> Self {
Size::new(
self.width.lerp(other.width, t),
self.height.lerp(other.height, t),
)
}
}
impl Lerp for Insets {
fn lerp(self, other: Self, t: f32) -> Self {
Insets::new(
self.top.lerp(other.top, t),
self.right.lerp(other.right, t),
self.bottom.lerp(other.bottom, t),
self.left.lerp(other.left, t),
)
}
}
impl Lerp for BorderRadius {
fn lerp(self, other: Self, t: f32) -> Self {
BorderRadius {
top_left: self.top_left.lerp(other.top_left, t),
top_right: self.top_right.lerp(other.top_right, t),
bottom_right: self.bottom_right.lerp(other.bottom_right, t),
bottom_left: self.bottom_left.lerp(other.bottom_left, t),
}
}
}
impl Lerp for Alignment {
fn lerp(self, other: Self, t: f32) -> Self {
Alignment::new(self.x.lerp(other.x, t), self.y.lerp(other.y, t))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Tween<T> {
pub begin: T,
pub end: T,
}
impl<T: Lerp> Tween<T> {
pub fn new(begin: T, end: T) -> Self {
Self { begin, end }
}
pub fn eval(&self, t: f32) -> T {
self.begin.lerp(self.end, t)
}
}
pub trait Animatable {
type Output;
fn evaluate(&self, t: f32) -> Self::Output;
fn curved(self, curve: Curve) -> Curved<Self>
where
Self: Sized,
{
Curved { inner: self, curve }
}
fn animate<'a>(&'a self, controller: &'a AnimationController) -> Animation<'a, Self>
where
Self: Sized,
{
Animation {
animatable: self,
controller,
}
}
}
impl<T: Lerp> Animatable for Tween<T> {
type Output = T;
fn evaluate(&self, t: f32) -> T {
self.eval(t)
}
}
#[derive(Clone, Debug)]
pub struct Curved<A> {
inner: A,
curve: Curve,
}
impl<A: Animatable> Animatable for Curved<A> {
type Output = A::Output;
fn evaluate(&self, t: f32) -> Self::Output {
self.inner.evaluate(self.curve.transform(t))
}
}
pub struct Animation<'a, A: Animatable> {
animatable: &'a A,
controller: &'a AnimationController,
}
impl<A: Animatable> Animation<'_, A> {
pub fn value(&self) -> A::Output {
let (lower, upper) = self.controller.bounds();
let t = if upper > lower {
(self.controller.value() - lower) / (upper - lower)
} else {
0.0
};
self.animatable.evaluate(t.clamp(0.0, 1.0))
}
pub fn status(&self) -> Status {
self.controller.status()
}
pub fn is_animating(&self) -> bool {
self.controller.is_animating()
}
}
pub struct TweenSequence<T> {
items: Vec<(Box<dyn Animatable<Output = T>>, f32)>,
total_weight: f32,
}
impl<T> TweenSequence<T> {
pub fn new(first: impl Animatable<Output = T> + 'static, weight: f32) -> Self {
let w = weight.max(0.0);
Self {
items: vec![(Box::new(first), w)],
total_weight: w,
}
}
pub fn then(mut self, next: impl Animatable<Output = T> + 'static, weight: f32) -> Self {
let w = weight.max(0.0);
self.total_weight += w;
self.items.push((Box::new(next), w));
self
}
}
impl<T> Animatable for TweenSequence<T> {
type Output = T;
fn evaluate(&self, t: f32) -> T {
let t = t.clamp(0.0, 1.0);
let last = self.items.len() - 1;
if self.total_weight <= 0.0 {
return self.items[last].0.evaluate(t);
}
let target = t * self.total_weight;
let mut acc = 0.0;
for (i, (seg, w)) in self.items.iter().enumerate() {
if i == last || target <= acc + *w {
let local = if *w > 0.0 { (target - acc) / *w } else { 0.0 };
return seg.evaluate(local.clamp(0.0, 1.0));
}
acc += *w;
}
unreachable!("the last segment always catches")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn f32_tween_interpolates() {
let t = Tween::new(10.0, 20.0);
assert_eq!(t.eval(0.0), 10.0);
assert_eq!(t.eval(0.5), 15.0);
assert_eq!(t.eval(1.0), 20.0);
}
#[test]
fn color_tween_interpolates() {
let t = Tween::new(Color::BLACK, Color::WHITE);
let mid = t.eval(0.5);
assert_eq!(mid, Color::rgb(0.5, 0.5, 0.5));
}
#[test]
fn point_tween_interpolates() {
let t = Tween::new(Point::new(0.0, 0.0), Point::new(10.0, 20.0));
assert_eq!(t.eval(0.5), Point::new(5.0, 10.0));
}
#[test]
fn animate_follows_controller() {
let mut ctrl = AnimationController::unit();
let tween = Tween::new(Size::new(100.0, 40.0), Size::new(200.0, 80.0));
assert_eq!(tween.animate(&ctrl).value(), Size::new(100.0, 40.0));
assert_eq!(tween.animate(&ctrl).status(), Status::Dismissed);
ctrl.forward(0.2, Curve::Linear);
while ctrl.tick(0.016) {}
assert_eq!(tween.animate(&ctrl).value(), Size::new(200.0, 80.0));
assert_eq!(tween.animate(&ctrl).status(), Status::Completed);
}
#[test]
fn curved_reshapes_progression() {
let mut ctrl = AnimationController::unit();
ctrl.set_value(0.5);
let linear = Tween::new(0.0f32, 100.0);
let eased = linear.curved(Curve::ease_in());
let mid = eased.animate(&ctrl).value();
assert!(mid < 50.0, "ease_in stays below the linear midpoint: {mid}");
assert!(mid > 0.0);
ctrl.set_value(0.0);
assert!(eased.animate(&ctrl).value().abs() < 0.5);
ctrl.set_value(1.0);
assert!((eased.animate(&ctrl).value() - 100.0).abs() < 0.5);
}
#[test]
fn non_unit_bounds_are_normalized() {
let mut ctrl = AnimationController::new(0.0, 2.0);
ctrl.set_value(1.0); let tween = Tween::new(Color::BLACK, Color::WHITE);
assert_eq!(tween.animate(&ctrl).value(), Color::rgb(0.5, 0.5, 0.5));
}
#[test]
fn alignment_tween_slides_between_anchors() {
let slide = Tween::new(Alignment::TOP_LEFT, Alignment::BOTTOM_RIGHT);
assert_eq!(slide.eval(0.0), Alignment::TOP_LEFT);
assert_eq!(slide.eval(0.5), Alignment::CENTER);
assert_eq!(slide.eval(1.0), Alignment::BOTTOM_RIGHT);
}
#[test]
fn insets_and_radius_tween_interpolate() {
let pad = Tween::new(Insets::uniform(0.0), Insets::uniform(20.0));
assert_eq!(pad.eval(0.5), Insets::uniform(10.0));
let radius = Tween::new(BorderRadius::uniform(4.0), BorderRadius::uniform(24.0));
assert_eq!(radius.eval(0.5), BorderRadius::uniform(14.0));
}
#[test]
fn tween_sequence_relays_equal_weight_segments() {
let seq =
TweenSequence::new(Tween::new(0.0f32, 10.0), 1.0).then(Tween::new(10.0, 30.0), 1.0);
assert_eq!(seq.evaluate(0.0), 0.0);
assert_eq!(seq.evaluate(0.25), 5.0); assert_eq!(seq.evaluate(0.5), 10.0); assert_eq!(seq.evaluate(0.75), 20.0); assert_eq!(seq.evaluate(1.0), 30.0);
}
#[test]
fn tween_sequence_honors_weights() {
let seq =
TweenSequence::new(Tween::new(0.0f32, 100.0), 3.0).then(Tween::new(100.0, 200.0), 1.0);
assert_eq!(seq.evaluate(0.75), 100.0); assert_eq!(seq.evaluate(0.375), 50.0); assert_eq!(seq.evaluate(0.875), 150.0); }
#[test]
fn tween_sequence_drives_from_controller() {
let seq = TweenSequence::new(Tween::new(Color::BLACK, Color::WHITE), 1.0)
.then(Tween::new(Color::WHITE, Color::BLACK), 1.0);
let mut ctrl = AnimationController::unit();
ctrl.set_value(0.5); assert_eq!(seq.animate(&ctrl).value(), Color::WHITE);
ctrl.set_value(1.0); assert_eq!(seq.animate(&ctrl).value(), Color::BLACK);
}
}