1use crate::Duration;
2use crate::animations::core::{Animatable, AnimationMode, LoopMode};
3use crate::animations::spring::{Spring, SpringState};
4use crate::keyframes::KeyframeAnimation;
5use crate::prelude::AnimationConfig;
6use crate::sequence::AnimationSequence;
7
8#[cfg(not(feature = "web"))]
9use crate::pool::SpringIntegrator;
10
11#[derive(Clone)]
12pub struct Motion<T: Animatable + Send + 'static> {
13 pub initial: T,
14 pub current: T,
15 pub target: T,
16 pub velocity: T,
17 pub running: bool,
18 pub elapsed: Duration,
19 pub delay_elapsed: Duration,
20 pub current_loop: u8,
21 pub reverse: bool,
22 config: AnimationConfig,
23 pub sequence: Option<AnimationSequence<T>>,
24 pub keyframe_animation: Option<KeyframeAnimation<T>>,
25}
26
27impl<T: Animatable + Send + 'static> Motion<T> {
28 pub fn new(initial: T) -> Self {
29 Self {
30 initial,
31 current: initial,
32 target: initial,
33 velocity: T::default(),
34 running: false,
35 elapsed: Duration::default(),
36 delay_elapsed: Duration::default(),
37 current_loop: 0,
38 reverse: false,
39 config: AnimationConfig::default(),
40 sequence: None,
41 keyframe_animation: None,
42 }
43 }
44
45 pub fn animate_to(&mut self, target: T, config: AnimationConfig) {
46 self.sequence = None;
47 self.keyframe_animation = None;
48 self.start_animation(target, config);
49 }
50
51 pub fn animate_sequence(&mut self, sequence: AnimationSequence<T>) {
52 sequence.reset();
53 if let Some(first_step) = sequence.current_step_data() {
54 self.start_animation(first_step.target, first_step.config.as_ref().clone());
55 self.sequence = Some(sequence);
56 }
57 }
58
59 pub fn animate_keyframes(&mut self, animation: KeyframeAnimation<T>) {
60 self.sequence = None;
61 self.keyframe_animation = Some(animation);
62 self.running = true;
63 self.elapsed = Duration::default();
64 self.delay_elapsed = Duration::default();
65 self.velocity = T::default();
66 self.current_loop = 0;
67 self.reverse = false;
68 }
69
70 pub fn get_value(&self) -> T {
71 self.current
72 }
73
74 pub fn is_running(&self) -> bool {
75 self.running
76 }
77
78 pub fn reset(&mut self) {
79 self.stop();
80 self.current = self.initial;
81 self.target = self.initial;
82 self.elapsed = Duration::default();
83 self.delay_elapsed = Duration::default();
84 }
85
86 pub fn stop(&mut self) {
87 self.running = false;
88 self.current_loop = 0;
89 self.velocity = T::default();
90 self.reverse = false;
91 self.sequence = None;
92 self.keyframe_animation = None;
93 }
94
95 pub fn delay(&mut self, duration: Duration) {
96 self.config.delay = duration;
97 }
98
99 pub fn get_epsilon(&self) -> f32 {
101 self.config.epsilon.unwrap_or_else(T::epsilon)
102 }
103
104 pub fn update(&mut self, dt: f32) -> bool {
105 const MIN_DELTA: f32 = 1.0 / 240.0;
106
107 if !self.running {
108 return false;
109 }
110
111 if dt < MIN_DELTA {
112 return true;
113 }
114
115 if self.delay_elapsed < self.config.delay {
116 self.delay_elapsed += Duration::from_secs_f32(dt);
117 return true;
118 }
119
120 if self.keyframe_animation.is_some() {
121 if self.update_keyframes(dt) {
122 self.finish_motion();
123 return false;
124 }
125 return true;
126 }
127
128 let completed = match self.config.mode {
129 AnimationMode::Spring(spring) => {
130 let state = self.update_spring(spring, dt);
131 matches!(state, SpringState::Completed)
132 }
133 AnimationMode::Tween(tween) => self.update_tween(tween, dt),
134 };
135
136 if !completed {
137 return true;
138 }
139
140 if self.sequence.is_some() {
141 return self.advance_sequence_step();
142 }
143
144 self.handle_completion()
145 }
146
147 fn start_animation(&mut self, target: T, config: AnimationConfig) {
148 self.initial = self.current;
149 self.target = target;
150 self.running = true;
151 self.elapsed = Duration::default();
152 self.delay_elapsed = Duration::default();
153 self.velocity = T::default();
154 self.current_loop = 0;
155 self.reverse = false;
156 self.config = config;
157 }
158
159 fn advance_sequence_step(&mut self) -> bool {
160 let Some(sequence) = self.sequence.as_mut() else {
161 return false;
162 };
163
164 let next_step = if sequence.advance_step() {
165 sequence
166 .current_step_data()
167 .map(|step| (step.target, step.config.as_ref().clone()))
168 } else {
169 sequence.execute_completion();
170 None
171 };
172
173 if let Some((target, config)) = next_step {
174 self.start_animation(target, config);
175 return true;
176 }
177
178 self.finish_motion();
179 false
180 }
181
182 fn update_keyframes(&mut self, dt: f32) -> bool {
183 let Some(animation) = self.keyframe_animation.as_ref() else {
184 return true;
185 };
186
187 let (current, next_elapsed, completed) = {
188 let duration_secs = animation.duration.as_secs_f32();
189 let next_elapsed_secs = self.elapsed.as_secs_f32() + dt;
190 let progress = if duration_secs == 0.0 {
191 1.0
192 } else {
193 (next_elapsed_secs / duration_secs).clamp(0.0, 1.0)
194 };
195
196 if animation.keyframes.is_empty() {
197 return true;
198 }
199
200 let (start, end) = if let Some(window) = animation
201 .keyframes
202 .windows(2)
203 .find(|window| progress >= window[0].offset && progress <= window[1].offset)
204 {
205 (&window[0], &window[1])
206 } else if progress <= animation.keyframes[0].offset {
207 let first = &animation.keyframes[0];
208 (first, first)
209 } else if let Some(last) = animation.keyframes.last() {
210 (last, last)
211 } else {
212 return true;
213 };
214
215 let local_progress = if start.offset == end.offset {
216 1.0
217 } else {
218 (progress - start.offset) / (end.offset - start.offset)
219 };
220
221 let eased_progress = end
222 .easing
223 .map_or(local_progress, |ease| (ease)(local_progress, 0.0, 1.0, 1.0));
224
225 (
226 start.value.interpolate(&end.value, eased_progress),
227 Duration::from_secs_f32(next_elapsed_secs),
228 progress >= 1.0,
229 )
230 };
231
232 self.current = current;
233 self.elapsed = next_elapsed;
234
235 completed
236 }
237
238 fn update_spring(&mut self, spring: Spring, dt: f32) -> SpringState {
239 let epsilon = self.get_epsilon();
240 let delta = self.target - self.current;
241
242 if delta.magnitude() < epsilon && self.velocity.magnitude() < epsilon {
243 self.current = self.target;
244 self.velocity = T::default();
245 return SpringState::Completed;
246 }
247
248 #[cfg(feature = "web")]
249 {
250 let stiffness = spring.stiffness;
251 let damping = spring.damping;
252 let mass_inv = 1.0 / spring.mass;
253
254 const FIXED_DT: f32 = 1.0 / 120.0;
255 let steps = ((dt / FIXED_DT) as usize).max(1);
256 let step_dt = dt / steps as f32;
257
258 for _ in 0..steps {
259 let step_delta = self.target - self.current;
260 let force = step_delta * stiffness;
261 let damping_force = self.velocity * damping;
262 self.velocity = self.velocity + (force - damping_force) * (mass_inv * step_dt);
263 self.current = self.current + self.velocity * step_dt;
264 }
265 }
266
267 #[cfg(not(feature = "web"))]
268 {
269 let mut integrator = SpringIntegrator::new();
270 let (new_pos, new_vel) =
271 integrator.integrate_rk4(self.current, self.velocity, self.target, &spring, dt);
272 self.current = new_pos;
273 self.velocity = new_vel;
274 }
275
276 self.check_spring_completion()
277 }
278
279 fn check_spring_completion(&mut self) -> SpringState {
280 let epsilon = self.get_epsilon();
281 let epsilon_sq = epsilon * epsilon;
282 let velocity_sq = self.velocity.magnitude().powi(2);
283 let delta_sq = (self.target - self.current).magnitude().powi(2);
284
285 if velocity_sq < epsilon_sq && delta_sq < epsilon_sq {
286 self.current = self.target;
287 self.velocity = T::default();
288 SpringState::Completed
289 } else {
290 SpringState::Active
291 }
292 }
293
294 fn update_tween(&mut self, tween: crate::prelude::Tween, dt: f32) -> bool {
295 let elapsed_secs = self.elapsed.as_secs_f32() + dt;
296 self.elapsed = Duration::from_secs_f32(elapsed_secs);
297 let duration_secs = tween.duration.as_secs_f32();
298
299 let progress = if duration_secs == 0.0 {
300 1.0
301 } else {
302 (elapsed_secs / duration_secs).min(1.0)
303 };
304
305 if progress <= 0.0 {
306 self.current = self.initial;
307 return false;
308 }
309
310 if progress >= 1.0 {
311 self.current = self.target;
312 return true;
313 }
314
315 let eased_progress = (tween.easing)(progress, 0.0, 1.0, 1.0);
316 self.current = match eased_progress {
317 0.0 => self.initial,
318 1.0 => self.target,
319 _ => self.initial.interpolate(&self.target, eased_progress),
320 };
321
322 false
323 }
324
325 fn handle_completion(&mut self) -> bool {
326 match self.config.loop_mode.unwrap_or(LoopMode::None) {
327 LoopMode::None => {
328 self.config.execute_completion();
329 self.finish_motion();
330 false
331 }
332 LoopMode::Infinite => {
333 self.restart_motion();
334 true
335 }
336 LoopMode::Times(count) => {
337 self.current_loop += 1;
338 if self.current_loop >= count {
339 self.config.execute_completion();
340 self.finish_motion();
341 false
342 } else {
343 self.restart_motion();
344 true
345 }
346 }
347 LoopMode::Alternate => {
348 self.reverse_motion();
349 true
350 }
351 LoopMode::AlternateTimes(count) => {
352 self.current_loop += 1;
353 if self.current_loop >= count * 2 {
354 self.config.execute_completion();
355 self.finish_motion();
356 false
357 } else {
358 self.reverse_motion();
359 true
360 }
361 }
362 }
363 }
364
365 fn finish_motion(&mut self) {
366 self.running = false;
367 self.current_loop = 0;
368 self.velocity = T::default();
369 self.sequence = None;
370 self.keyframe_animation = None;
371 }
372
373 fn restart_motion(&mut self) {
374 self.current = self.initial;
375 self.elapsed = Duration::default();
376 self.delay_elapsed = Duration::default();
377 self.velocity = T::default();
378 self.running = true;
379 }
380
381 fn reverse_motion(&mut self) {
382 self.reverse = !self.reverse;
383 std::mem::swap(&mut self.initial, &mut self.target);
384 self.restart_motion();
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 #![allow(clippy::unwrap_used)]
391
392 use super::*;
393 use crate::animations::core::AnimationMode;
394 use crate::animations::spring::Spring;
395 use crate::prelude::Tween;
396 use std::sync::{Arc, Mutex};
397
398 fn instant_tween() -> AnimationConfig {
399 AnimationConfig::new(AnimationMode::Tween(Tween::new(Duration::from_secs(0))))
400 }
401
402 #[test]
403 fn test_motion_new() {
404 let motion = Motion::new(0.0f32);
405
406 assert_eq!(motion.initial, 0.0);
407 assert_eq!(motion.current, 0.0);
408 assert_eq!(motion.target, 0.0);
409 assert!(!motion.running);
410 assert!(motion.sequence.is_none());
411 assert!(motion.keyframe_animation.is_none());
412 }
413
414 #[test]
415 fn test_motion_animate_to() {
416 let mut motion = Motion::new(0.0f32);
417 motion.animate_to(
418 100.0,
419 AnimationConfig::new(AnimationMode::Tween(Tween::default())),
420 );
421
422 assert_eq!(motion.target, 100.0);
423 assert!(motion.running);
424 assert!(motion.sequence.is_none());
425 assert!(motion.keyframe_animation.is_none());
426 }
427
428 #[test]
429 fn test_motion_sequence_advances() {
430 let mut motion = Motion::new(0.0f32);
431 let sequence = AnimationSequence::new()
432 .then(50.0f32, instant_tween())
433 .then(100.0f32, instant_tween());
434
435 motion.animate_sequence(sequence);
436
437 assert_eq!(motion.target, 50.0);
438 assert!(motion.sequence.is_some());
439
440 assert!(motion.update(1.0 / 60.0));
441 assert_eq!(motion.target, 100.0);
442 assert!(motion.running);
443
444 assert!(!motion.update(1.0 / 60.0));
445 assert_eq!(motion.current, 100.0);
446 assert!(!motion.running);
447 assert!(motion.sequence.is_none());
448 }
449
450 #[test]
451 fn test_motion_keyframes_progress_and_complete() {
452 let mut motion = Motion::new(0.0f32);
453
454 let animation = KeyframeAnimation::new(Duration::from_secs(1))
455 .add_keyframe(0.0, 0.0, None)
456 .unwrap()
457 .add_keyframe(100.0, 1.0, None)
458 .unwrap();
459
460 motion.animate_keyframes(animation);
461
462 assert!(motion.update(0.5));
463 assert!(motion.current > 0.0);
464 assert!(motion.current < 100.0);
465
466 assert!(!motion.update(0.5));
467 assert_eq!(motion.current, 100.0);
468 assert!(!motion.running);
469 assert!(motion.keyframe_animation.is_none());
470 }
471
472 #[test]
473 fn test_motion_stop() {
474 let mut motion = Motion::new(0.0f32);
475 motion.animate_to(
476 100.0,
477 AnimationConfig::new(AnimationMode::Spring(Spring::default())),
478 );
479
480 motion.stop();
481
482 assert!(!motion.running);
483 assert!(motion.sequence.is_none());
484 assert!(motion.keyframe_animation.is_none());
485 assert_eq!(motion.velocity, 0.0);
486 }
487
488 #[test]
489 fn test_motion_get_epsilon() {
490 let mut motion = Motion::new(0.0f32);
491 assert_eq!(motion.get_epsilon(), f32::epsilon());
492
493 motion.animate_to(
494 1.0,
495 AnimationConfig::new(AnimationMode::Tween(Tween::default())).with_epsilon(0.01),
496 );
497
498 assert_eq!(motion.get_epsilon(), 0.01);
499 }
500
501 #[test]
502 fn test_motion_delay_prevents_early_update() {
503 let mut motion = Motion::new(0.0f32);
504 motion.animate_to(
505 100.0,
506 AnimationConfig::new(AnimationMode::Tween(Tween::default())),
507 );
508 motion.delay(Duration::from_millis(100));
509
510 assert!(motion.update(1.0 / 60.0));
511 assert_eq!(motion.current, motion.initial);
512 }
513
514 #[test]
515 fn test_motion_update_tween_changes_value() {
516 let mut motion = Motion::new(0.0f32);
517 motion.animate_to(
518 100.0,
519 AnimationConfig::new(AnimationMode::Tween(Tween::default())),
520 );
521
522 assert!(motion.update(1.0 / 60.0));
523 assert!(motion.current > 0.0);
524 assert!(motion.current < 100.0);
525 }
526
527 #[test]
528 fn test_motion_spring_completes_when_already_settled() {
529 let mut motion = Motion::new(0.0f32);
530 motion.animate_to(
531 0.0,
532 AnimationConfig::new(AnimationMode::Spring(Spring::default())),
533 );
534 motion.velocity = 0.0;
535
536 assert!(!motion.update(1.0 / 60.0));
537 assert_eq!(motion.current, 0.0);
538 assert!(!motion.running);
539 }
540
541 #[test]
542 fn test_motion_loop_mode_times() {
543 let mut motion = Motion::new(0.0f32);
544 motion.animate_to(100.0, instant_tween().with_loop(LoopMode::Times(2)));
545
546 assert!(motion.update(1.0 / 60.0));
547 assert_eq!(motion.current, motion.initial);
548 assert!(motion.running);
549
550 assert!(!motion.update(1.0 / 60.0));
551 assert!(!motion.running);
552 }
553
554 #[test]
555 fn test_motion_loop_mode_alternate() {
556 let mut motion = Motion::new(0.0f32);
557 motion.animate_to(100.0, instant_tween().with_loop(LoopMode::Alternate));
558
559 assert!(motion.update(1.0 / 60.0));
560 assert!(motion.running);
561 assert!(motion.reverse);
562 assert_eq!(motion.initial, 100.0);
563 assert_eq!(motion.target, 0.0);
564 }
565
566 #[test]
567 fn test_motion_completion_callback() {
568 let called = Arc::new(Mutex::new(false));
569 let called_clone = called.clone();
570 let config = instant_tween().with_on_complete(move || {
571 *called_clone.lock().unwrap() = true;
572 });
573
574 let mut motion = Motion::new(0.0f32);
575 motion.animate_to(100.0, config);
576
577 assert!(!motion.update(1.0 / 60.0));
578 assert!(*called.lock().unwrap());
579 }
580
581 #[test]
582 fn test_motion_get_value_tracks_current_directly() {
583 let mut motion = Motion::new(0.0f32);
584 motion.current = 12.5;
585 assert_eq!(motion.get_value(), 12.5);
586
587 motion.current = 42.0;
588 assert_eq!(motion.get_value(), 42.0);
589 }
590}