1use std::{ops::Sub, time::Duration};
2
3use crate::{AnyMotion, Lerp, Progress};
4
5#[derive(Clone)]
7pub struct AnimatedSample<T> {
8 pub value: T,
10
11 pub is_active: bool,
13
14 pub progress: Progress,
17}
18
19#[derive(Clone)]
45pub struct Animated<T, Time = std::time::Instant> {
46 value: T,
47 initial_value: T,
48 last_value: T,
49 motion: AnyMotion,
50 reverse_progress: bool,
51 settled_progress: Option<Progress>,
52 started_at: Option<Time>,
53}
54
55impl<T, Time> Animated<T, Time>
56where
57 T: Lerp + Clone + PartialEq,
58 Time: Copy + Sub<Time, Output = Duration>,
59{
60 pub fn new(value: T, motion: impl Into<AnyMotion>) -> Self {
62 Self {
63 initial_value: value.clone(),
64 last_value: value.clone(),
65 value,
66 motion: motion.into(),
67 reverse_progress: false,
68 settled_progress: None,
69 started_at: None,
70 }
71 }
72
73 pub fn value(&self) -> &T {
77 &self.value
78 }
79
80 pub(crate) fn progress_at(&self, now: Time) -> Progress {
86 self.started_at.map_or_else(
87 || self.settled_progress.unwrap_or(Progress::END),
88 |started_at| {
89 self.presentation_progress(self.motion.sample_at(started_at, now).progress)
90 },
91 )
92 }
93
94 fn presentation_progress(&self, progress: Progress) -> Progress {
95 if self.reverse_progress {
96 progress.reversed()
97 } else {
98 progress
99 }
100 }
101
102 pub fn set(&mut self, value: T, motion: impl Into<AnyMotion>, now: Time) -> bool {
111 self.retarget(value, motion, now, true)
112 }
113
114 pub(crate) fn restart(&mut self, value: T, motion: impl Into<AnyMotion>, now: Time) -> bool {
116 self.retarget(value, motion, now, false)
117 }
118
119 fn retarget(
120 &mut self,
121 value: T,
122 motion: impl Into<AnyMotion>,
123 now: Time,
124 continuous: bool,
125 ) -> bool {
126 if self.value == value {
127 return false;
128 }
129
130 let current = self.sample(now);
131 self.last_value = if continuous {
132 current.value
133 } else {
134 self.initial_value.clone()
135 };
136 self.value = value;
137 self.motion = motion.into();
138 self.reverse_progress = self.motion.starts_in_reverse();
141 self.settled_progress = None;
142 self.started_at = Some(now);
143 true
144 }
145
146 pub fn jump_to(&mut self, value: T) {
150 self.value = value.clone();
151 self.last_value = value;
152 self.settled_progress = None;
153 self.started_at = None;
154 }
155
156 pub(crate) fn adopt_completed_target(&mut self) {
162 if self.started_at.is_none() && self.settled_progress == Some(Progress::END) {
163 self.last_value = self.value.clone();
164 self.settled_progress = None;
165 }
166 }
167
168 pub fn reset(&mut self) {
172 self.value = self.initial_value.clone();
173 self.last_value = self.initial_value.clone();
174 self.settled_progress = None;
175 self.started_at = None;
176 }
177
178 pub fn sample(&mut self, now: Time) -> AnimatedSample<T> {
184 let Some(started_at) = self.started_at else {
185 return AnimatedSample {
186 value: self.last_value.clone(),
187 is_active: false,
188 progress: self.settled_progress.unwrap_or(Progress::END),
189 };
190 };
191
192 let sample = self.motion.sample_at(started_at, now);
193 let progress = self.presentation_progress(sample.progress);
194 let value = self.last_value.lerp(&self.value, progress.get());
195
196 if !sample.is_active {
197 self.last_value = value.clone();
198 self.settled_progress = Some(progress);
199 self.started_at = None;
200 }
201
202 AnimatedSample {
203 value,
204 is_active: sample.is_active,
205 progress,
206 }
207 }
208
209 pub(crate) fn scale_by(&mut self, ratio: f32)
210 where
211 T: std::ops::Mul<f32, Output = T>,
212 {
213 self.last_value = self.last_value.clone() * ratio;
214 self.value = self.value.clone() * ratio;
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 use crate::{Direction, Motion, SpringConfig};
223
224 fn assert_sample(sample: AnimatedSample<f32>, value: f32, progress: Progress, is_active: bool) {
225 assert_eq!(
226 (sample.value, sample.progress, sample.is_active),
227 (value, progress, is_active)
228 );
229 }
230
231 #[test]
232 fn animated_value_supports_a_complete_lifecycle() {
233 let motion = Motion::new(Duration::from_secs(1));
234 let mut animated = Animated::<f32, Duration>::new(2.0, motion.clone());
235
236 assert_eq!(animated.value(), &2.0);
237 assert_eq!(animated.progress_at(Duration::ZERO), Progress::END);
238 assert_sample(animated.sample(Duration::ZERO), 2.0, Progress::END, false);
239
240 assert!(animated.set(10.0, &motion, Duration::ZERO));
241 assert_eq!(animated.sample(Duration::from_millis(500)).value, 6.0);
242 assert_eq!(
243 animated.progress_at(Duration::from_millis(500)),
244 Progress::clamped(0.5)
245 );
246
247 assert_sample(
248 animated.sample(Duration::from_secs(1)),
249 10.0,
250 Progress::END,
251 false,
252 );
253 assert!(!animated.set(10.0, &motion, Duration::from_secs(2)));
254
255 animated.jump_to(8.0);
256 assert_sample(
257 animated.sample(Duration::from_secs(2)),
258 8.0,
259 Progress::END,
260 false,
261 );
262
263 animated.scale_by(2.0);
264 assert_eq!(animated.value(), &16.0);
265
266 let immediate = Motion::default();
267 assert!(animated.set(12.0, &immediate, Duration::from_secs(2)));
268 assert_sample(
269 animated.sample(Duration::from_secs(2)),
270 12.0,
271 Progress::END,
272 false,
273 );
274 animated.reset();
275
276 assert_eq!(animated.value(), &2.0);
277 assert_sample(
278 animated.sample(Duration::from_millis(500)),
279 2.0,
280 Progress::END,
281 false,
282 );
283 }
284
285 #[test]
286 fn animated_exercises_retargeting_and_non_monotonic_motion() {
287 let motion = Motion::new(Duration::from_secs(1));
288 let mut continuous = Animated::<f32, Duration>::new(0.0, motion.clone());
289 let mut restarting = Animated::<f32, Duration>::new(0.0, motion.clone());
290
291 assert!(continuous.set(10.0, &motion, Duration::ZERO));
292 assert!(restarting.set(10.0, &motion, Duration::ZERO));
293 assert_eq!(continuous.sample(Duration::from_millis(500)).value, 5.0);
294 assert_eq!(restarting.sample(Duration::from_millis(500)).value, 5.0);
295
296 assert!(continuous.set(20.0, &motion, Duration::from_millis(500)));
297 assert!(restarting.restart(20.0, &motion, Duration::from_millis(500)));
298
299 let continuous_anchor = continuous.sample(Duration::from_millis(500));
300 let restarting_anchor = restarting.sample(Duration::from_millis(500));
301 assert_eq!(continuous_anchor.value, 5.0);
302 assert_eq!(restarting_anchor.value, 0.0);
303 assert!(continuous_anchor.is_active);
304 assert!(restarting_anchor.is_active);
305
306 assert_eq!(continuous.sample(Duration::from_secs(1)).value, 12.5);
307 assert_eq!(restarting.sample(Duration::from_secs(1)).value, 10.0);
308 assert!(!continuous.set(20.0, &motion, Duration::from_secs(1)));
309 assert!(!restarting.restart(20.0, &motion, Duration::from_secs(1)));
310
311 let non_monotonic = Motion::new(Duration::from_secs(1)).with_easing(|progress| {
312 if progress < 0.5 {
313 progress * 2.0
314 } else {
315 (1.0 - progress) * 2.0
316 }
317 });
318 let mut animated = Animated::<f32, Duration>::new(0.0, non_monotonic.clone());
319
320 assert!(animated.set(1.0, &non_monotonic, Duration::ZERO));
321 assert_sample(animated.sample(Duration::ZERO), 0.0, Progress::START, true);
322 assert_sample(
323 animated.sample(Duration::from_millis(500)),
324 1.0,
325 Progress::END,
326 true,
327 );
328 assert_sample(
329 animated.sample(Duration::from_secs(1)),
330 0.0,
331 Progress::START,
332 false,
333 );
334 assert_sample(
335 animated.sample(Duration::from_secs(2)),
336 0.0,
337 Progress::START,
338 false,
339 );
340 assert_eq!(
341 animated.progress_at(Duration::from_secs(2)),
342 Progress::START
343 );
344 assert_eq!(animated.value(), &1.0);
345
346 animated.jump_to(2.0);
347 assert_sample(
348 animated.sample(Duration::from_secs(3)),
349 2.0,
350 Progress::END,
351 false,
352 );
353 let spring =
354 Motion::new(Duration::from_secs(1)).with_spring(SpringConfig::new(100.0, 6.0, 1.0));
355 let mut animated = Animated::<f32, Duration>::new(0.0, spring.clone());
356 assert!(animated.set(1.0, &spring, Duration::ZERO));
357
358 assert!(
359 (1..100).any(|step| { animated.sample(Duration::from_millis(step * 10)).value > 1.0 })
360 );
361 assert_eq!(animated.sample(Duration::from_secs(1)).value, 1.0);
362 }
363
364 #[test]
365 fn animated_keeps_the_motion_that_started_each_run() {
366 let one_second = Motion::new(Duration::from_secs(1));
367 let two_seconds = Motion::new(Duration::from_secs(2));
368 let mut animated = Animated::<f32, Duration>::new(0.0, one_second.clone());
369
370 assert!(animated.set(10.0, &one_second, Duration::ZERO));
371 assert_eq!(animated.sample(Duration::from_millis(500)).value, 5.0);
372 assert_eq!(
373 animated.progress_at(Duration::from_millis(750)),
374 Progress::clamped(0.75)
375 );
376
377 assert!(animated.set(20.0, &two_seconds, Duration::from_millis(500)));
378 assert_eq!(animated.sample(Duration::from_millis(500)).value, 5.0);
379 assert_eq!(animated.sample(Duration::from_millis(1_500)).value, 12.5);
380 }
381
382 #[test]
383 fn delayed_retargeting_holds_the_interruption_anchor() {
384 let motion = Motion::new(Duration::from_secs(1)).with_delay(Duration::from_millis(100));
385 let mut animated = Animated::<f32, Duration>::new(0.0, motion.clone());
386
387 assert!(animated.set(10.0, &motion, Duration::ZERO));
388 assert_sample(
389 animated.sample(Duration::from_millis(100)),
390 0.0,
391 Progress::START,
392 true,
393 );
394 assert_eq!(animated.sample(Duration::from_millis(600)).value, 5.0);
395
396 assert!(animated.set(20.0, &motion, Duration::from_millis(600)));
397 for milliseconds in [600, 650, 700] {
398 assert_sample(
399 animated.sample(Duration::from_millis(milliseconds)),
400 5.0,
401 Progress::START,
402 true,
403 );
404 }
405 assert_eq!(animated.sample(Duration::from_millis(1_200)).value, 12.5);
406 }
407
408 #[test]
409 fn reverse_first_retargeting_starts_at_the_current_presentation() {
410 let motion = Motion::new(Duration::from_secs(1)).direction(Direction::Reverse);
411 let mut animated = Animated::<f32, Duration>::new(0.0, motion.clone());
412
413 assert!(animated.set(10.0, &motion, Duration::ZERO));
414 assert_sample(animated.sample(Duration::ZERO), 0.0, Progress::START, true);
415 assert_sample(
416 animated.sample(Duration::from_millis(500)),
417 5.0,
418 Progress::clamped(0.5),
419 true,
420 );
421
422 assert!(animated.set(20.0, &motion, Duration::from_millis(500)));
423 assert_sample(
424 animated.sample(Duration::from_millis(500)),
425 5.0,
426 Progress::START,
427 true,
428 );
429 assert_eq!(animated.sample(Duration::from_secs(1)).value, 12.5);
430 assert_sample(
431 animated.sample(Duration::from_millis(1_500)),
432 20.0,
433 Progress::END,
434 false,
435 );
436
437 let alternating = Motion::new(Duration::from_secs(1))
438 .iterations(2)
439 .direction(Direction::AlternateReverse);
440 assert!(animated.set(30.0, &alternating, Duration::from_secs(2)));
441 assert_eq!(animated.sample(Duration::from_secs(2)).value, 20.0);
442 assert_eq!(animated.sample(Duration::from_secs(3)).value, 30.0);
443 assert_sample(
444 animated.sample(Duration::from_secs(4)),
445 20.0,
446 Progress::START,
447 false,
448 );
449 }
450
451 #[test]
452 fn finite_iterations_settle_at_their_sampled_presentation() {
453 let restarting_motion = Motion::new(Duration::from_secs(1)).iterations(3);
454 let mut restarting = Animated::<f32, Duration>::new(0.0, restarting_motion.clone());
455 assert!(restarting.set(10.0, &restarting_motion, Duration::ZERO));
456 assert_sample(
457 restarting.sample(Duration::from_secs(1)),
458 10.0,
459 Progress::END,
460 true,
461 );
462 assert_sample(
463 restarting.sample(Duration::from_millis(1_500)),
464 5.0,
465 Progress::clamped(0.5),
466 true,
467 );
468 assert_sample(
469 restarting.sample(Duration::from_secs(3)),
470 10.0,
471 Progress::END,
472 false,
473 );
474
475 let alternating_motion = Motion::new(Duration::from_secs(1))
476 .iterations(2)
477 .alternate();
478 let mut idle = Animated::<f32, Duration>::new(4.0, alternating_motion.clone());
479 assert_sample(idle.sample(Duration::ZERO), 4.0, Progress::END, false);
480 idle.jump_to(6.0);
481 assert_sample(idle.sample(Duration::ZERO), 6.0, Progress::END, false);
482
483 let mut alternating = Animated::<f32, Duration>::new(0.0, alternating_motion.clone());
484 assert!(alternating.set(10.0, &alternating_motion, Duration::ZERO));
485 assert_sample(
486 alternating.sample(Duration::from_secs(1)),
487 10.0,
488 Progress::END,
489 true,
490 );
491 assert_sample(
492 alternating.sample(Duration::from_secs(2)),
493 0.0,
494 Progress::START,
495 false,
496 );
497 assert_sample(
498 alternating.sample(Duration::from_secs(20)),
499 0.0,
500 Progress::START,
501 false,
502 );
503
504 let zero_forever = Motion::new(Duration::ZERO).repeat_forever();
505 assert!(alternating.set(20.0, &zero_forever, Duration::from_secs(21)));
506 assert_sample(
507 alternating.sample(Duration::from_secs(21)),
508 0.0,
509 Progress::START,
510 false,
511 );
512 }
513}