1use super::controller::{AnimationController, Status};
5use super::curve::Curve;
6use crate::{Alignment, BorderRadius, Color, Insets, Point, Size};
7
8pub trait Lerp: Copy {
10 fn lerp(self, other: Self, t: f32) -> Self;
12}
13
14impl Lerp for f32 {
15 fn lerp(self, other: Self, t: f32) -> Self {
16 self + (other - self) * t
17 }
18}
19
20impl Lerp for Color {
21 fn lerp(self, other: Self, t: f32) -> Self {
22 Color::lerp(self, other, t)
23 }
24}
25
26impl Lerp for Point {
27 fn lerp(self, other: Self, t: f32) -> Self {
28 Point::new(self.x.lerp(other.x, t), self.y.lerp(other.y, t))
29 }
30}
31
32impl Lerp for Size {
33 fn lerp(self, other: Self, t: f32) -> Self {
34 Size::new(
35 self.width.lerp(other.width, t),
36 self.height.lerp(other.height, t),
37 )
38 }
39}
40
41impl Lerp for Insets {
42 fn lerp(self, other: Self, t: f32) -> Self {
43 Insets::new(
44 self.top.lerp(other.top, t),
45 self.right.lerp(other.right, t),
46 self.bottom.lerp(other.bottom, t),
47 self.left.lerp(other.left, t),
48 )
49 }
50}
51
52impl Lerp for BorderRadius {
53 fn lerp(self, other: Self, t: f32) -> Self {
54 BorderRadius {
55 top_left: self.top_left.lerp(other.top_left, t),
56 top_right: self.top_right.lerp(other.top_right, t),
57 bottom_right: self.bottom_right.lerp(other.bottom_right, t),
58 bottom_left: self.bottom_left.lerp(other.bottom_left, t),
59 }
60 }
61}
62
63impl Lerp for Alignment {
64 fn lerp(self, other: Self, t: f32) -> Self {
65 Alignment::new(self.x.lerp(other.x, t), self.y.lerp(other.y, t))
66 }
67}
68
69#[derive(Clone, Copy, Debug, PartialEq)]
74pub struct Tween<T> {
75 pub begin: T,
77 pub end: T,
79}
80
81impl<T: Lerp> Tween<T> {
82 pub fn new(begin: T, end: T) -> Self {
84 Self { begin, end }
85 }
86
87 pub fn eval(&self, t: f32) -> T {
89 self.begin.lerp(self.end, t)
90 }
91}
92
93pub trait Animatable {
100 type Output;
102
103 fn evaluate(&self, t: f32) -> Self::Output;
105
106 fn curved(self, curve: Curve) -> Curved<Self>
109 where
110 Self: Sized,
111 {
112 Curved { inner: self, curve }
113 }
114
115 fn animate<'a>(&'a self, controller: &'a AnimationController) -> Animation<'a, Self>
120 where
121 Self: Sized,
122 {
123 Animation {
124 animatable: self,
125 controller,
126 }
127 }
128}
129
130impl<T: Lerp> Animatable for Tween<T> {
131 type Output = T;
132
133 fn evaluate(&self, t: f32) -> T {
134 self.eval(t)
135 }
136}
137
138#[derive(Clone, Debug)]
141pub struct Curved<A> {
142 inner: A,
143 curve: Curve,
144}
145
146impl<A: Animatable> Animatable for Curved<A> {
147 type Output = A::Output;
148
149 fn evaluate(&self, t: f32) -> Self::Output {
150 self.inner.evaluate(self.curve.transform(t))
151 }
152}
153
154pub struct Animation<'a, A: Animatable> {
158 animatable: &'a A,
159 controller: &'a AnimationController,
160}
161
162impl<A: Animatable> Animation<'_, A> {
163 pub fn value(&self) -> A::Output {
165 let (lower, upper) = self.controller.bounds();
166 let t = if upper > lower {
167 (self.controller.value() - lower) / (upper - lower)
168 } else {
169 0.0
170 };
171 self.animatable.evaluate(t.clamp(0.0, 1.0))
172 }
173
174 pub fn status(&self) -> Status {
176 self.controller.status()
177 }
178
179 pub fn is_animating(&self) -> bool {
181 self.controller.is_animating()
182 }
183}
184
185pub struct TweenSequence<T> {
194 items: Vec<(Box<dyn Animatable<Output = T>>, f32)>,
196 total_weight: f32,
197}
198
199impl<T> TweenSequence<T> {
200 pub fn new(first: impl Animatable<Output = T> + 'static, weight: f32) -> Self {
203 let w = weight.max(0.0);
204 Self {
205 items: vec![(Box::new(first), w)],
206 total_weight: w,
207 }
208 }
209
210 pub fn then(mut self, next: impl Animatable<Output = T> + 'static, weight: f32) -> Self {
212 let w = weight.max(0.0);
213 self.total_weight += w;
214 self.items.push((Box::new(next), w));
215 self
216 }
217}
218
219impl<T> Animatable for TweenSequence<T> {
220 type Output = T;
221
222 fn evaluate(&self, t: f32) -> T {
223 let t = t.clamp(0.0, 1.0);
224 let last = self.items.len() - 1;
225 if self.total_weight <= 0.0 {
227 return self.items[last].0.evaluate(t);
228 }
229 let target = t * self.total_weight;
230 let mut acc = 0.0;
231 for (i, (seg, w)) in self.items.iter().enumerate() {
232 if i == last || target <= acc + *w {
234 let local = if *w > 0.0 { (target - acc) / *w } else { 0.0 };
235 return seg.evaluate(local.clamp(0.0, 1.0));
236 }
237 acc += *w;
238 }
239 unreachable!("the last segment always catches")
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn f32_tween_interpolates() {
249 let t = Tween::new(10.0, 20.0);
250 assert_eq!(t.eval(0.0), 10.0);
251 assert_eq!(t.eval(0.5), 15.0);
252 assert_eq!(t.eval(1.0), 20.0);
253 }
254
255 #[test]
256 fn color_tween_interpolates() {
257 let t = Tween::new(Color::BLACK, Color::WHITE);
258 let mid = t.eval(0.5);
259 assert_eq!(mid, Color::rgb(0.5, 0.5, 0.5));
260 }
261
262 #[test]
263 fn point_tween_interpolates() {
264 let t = Tween::new(Point::new(0.0, 0.0), Point::new(10.0, 20.0));
265 assert_eq!(t.eval(0.5), Point::new(5.0, 10.0));
266 }
267
268 #[test]
271 fn animate_follows_controller() {
272 let mut ctrl = AnimationController::unit();
273 let tween = Tween::new(Size::new(100.0, 40.0), Size::new(200.0, 80.0));
274 assert_eq!(tween.animate(&ctrl).value(), Size::new(100.0, 40.0));
275 assert_eq!(tween.animate(&ctrl).status(), Status::Dismissed);
276
277 ctrl.forward(0.2, Curve::Linear);
278 while ctrl.tick(0.016) {}
279 assert_eq!(tween.animate(&ctrl).value(), Size::new(200.0, 80.0));
280 assert_eq!(tween.animate(&ctrl).status(), Status::Completed);
281 }
282
283 #[test]
286 fn curved_reshapes_progression() {
287 let mut ctrl = AnimationController::unit();
288 ctrl.set_value(0.5);
289 let linear = Tween::new(0.0f32, 100.0);
290 let eased = linear.curved(Curve::ease_in());
291 let mid = eased.animate(&ctrl).value();
292 assert!(mid < 50.0, "ease_in stays below the linear midpoint: {mid}");
293 assert!(mid > 0.0);
294 ctrl.set_value(0.0);
296 assert!(eased.animate(&ctrl).value().abs() < 0.5);
297 ctrl.set_value(1.0);
298 assert!((eased.animate(&ctrl).value() - 100.0).abs() < 0.5);
299 }
300
301 #[test]
304 fn non_unit_bounds_are_normalized() {
305 let mut ctrl = AnimationController::new(0.0, 2.0);
306 ctrl.set_value(1.0); let tween = Tween::new(Color::BLACK, Color::WHITE);
308 assert_eq!(tween.animate(&ctrl).value(), Color::rgb(0.5, 0.5, 0.5));
309 }
310
311 #[test]
312 fn alignment_tween_slides_between_anchors() {
313 let slide = Tween::new(Alignment::TOP_LEFT, Alignment::BOTTOM_RIGHT);
314 assert_eq!(slide.eval(0.0), Alignment::TOP_LEFT);
315 assert_eq!(slide.eval(0.5), Alignment::CENTER);
316 assert_eq!(slide.eval(1.0), Alignment::BOTTOM_RIGHT);
317 }
318
319 #[test]
320 fn insets_and_radius_tween_interpolate() {
321 let pad = Tween::new(Insets::uniform(0.0), Insets::uniform(20.0));
322 assert_eq!(pad.eval(0.5), Insets::uniform(10.0));
323 let radius = Tween::new(BorderRadius::uniform(4.0), BorderRadius::uniform(24.0));
324 assert_eq!(radius.eval(0.5), BorderRadius::uniform(14.0));
325 }
326
327 #[test]
330 fn tween_sequence_relays_equal_weight_segments() {
331 let seq =
332 TweenSequence::new(Tween::new(0.0f32, 10.0), 1.0).then(Tween::new(10.0, 30.0), 1.0);
333 assert_eq!(seq.evaluate(0.0), 0.0);
334 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);
338 }
339
340 #[test]
343 fn tween_sequence_honors_weights() {
344 let seq =
346 TweenSequence::new(Tween::new(0.0f32, 100.0), 3.0).then(Tween::new(100.0, 200.0), 1.0);
347 assert_eq!(seq.evaluate(0.75), 100.0); assert_eq!(seq.evaluate(0.375), 50.0); assert_eq!(seq.evaluate(0.875), 150.0); }
351
352 #[test]
354 fn tween_sequence_drives_from_controller() {
355 let seq = TweenSequence::new(Tween::new(Color::BLACK, Color::WHITE), 1.0)
356 .then(Tween::new(Color::WHITE, Color::BLACK), 1.0);
357 let mut ctrl = AnimationController::unit();
358 ctrl.set_value(0.5); assert_eq!(seq.animate(&ctrl).value(), Color::WHITE);
360 ctrl.set_value(1.0); assert_eq!(seq.animate(&ctrl).value(), Color::BLACK);
362 }
363}