eazy_tweener/
tween.rs

1//! Core tween animation state machine.
2//!
3//! A [`Tween`] interpolates between two values over time using an easing
4//! function from `eazy-data`.
5
6use eazy_core::Curve;
7use eazy_core::Easing;
8
9use crate::callback::Callbacks;
10use crate::control::Controllable;
11use crate::control::Direction;
12use crate::control::TweenState;
13use crate::repeat::Repeat;
14use crate::repeat::RepeatConfig;
15use crate::value::Tweenable;
16
17/// A tween that interpolates between two values of type `T`.
18///
19/// # Examples
20///
21/// ```rust
22/// use eazy_tweener::{Tween, Controllable};
23///
24/// let mut tween = Tween::new(0.0_f32, 100.0, 1.0);
25///
26/// tween.play();
27///
28/// // In your update loop:
29/// let value = tween.value();
30/// tween.tick(0.016); // ~60 FPS
31/// ```
32#[derive(Debug, Clone)]
33pub struct Tween<T: Tweenable> {
34  /// Start value.
35  from: T,
36  /// End value.
37  to: T,
38  /// Duration in seconds.
39  duration: f32,
40  /// Current elapsed time.
41  elapsed: f32,
42  /// Initial delay before starting.
43  delay: f32,
44  /// Remaining delay (counts down).
45  delay_remaining: f32,
46  /// Easing function.
47  easing: Easing,
48  /// Current state.
49  state: TweenState,
50  /// Playback direction.
51  direction: Direction,
52  /// Repeat configuration.
53  repeat_config: RepeatConfig,
54  /// Current repeat iteration.
55  iteration: u32,
56  /// Time scale multiplier.
57  time_scale: f32,
58  /// Lifecycle callbacks.
59  callbacks: Callbacks,
60  /// Whether on_start has been fired for current play.
61  started: bool,
62}
63
64impl<T: Tweenable> Tween<T> {
65  /// Create a new tween from `from` to `to` over `duration` seconds.
66  pub fn new(from: T, to: T, duration: f32) -> Self {
67    Self {
68      from,
69      to,
70      duration: duration.max(0.0),
71      elapsed: 0.0,
72      delay: 0.0,
73      delay_remaining: 0.0,
74      easing: Easing::Linear,
75      state: TweenState::Idle,
76      direction: Direction::Forward,
77      repeat_config: RepeatConfig::default(),
78      iteration: 0,
79      time_scale: 1.0,
80      callbacks: Callbacks::default(),
81      started: false,
82    }
83  }
84
85  /// Create a tween that animates TO the target value.
86  ///
87  /// The `from` value is provided, animating toward `to`.
88  pub fn to(from: T, to: T) -> TweenBuilder<T> {
89    TweenBuilder::new(from, to)
90  }
91
92  /// Create a tween that animates FROM the source value.
93  ///
94  /// The animation goes from `from` to `to`.
95  pub fn from(from: T, to: T) -> TweenBuilder<T> {
96    TweenBuilder::new(from, to)
97  }
98
99  /// Create a tween with explicit from and to values.
100  pub fn from_to(from: T, to: T) -> TweenBuilder<T> {
101    TweenBuilder::new(from, to)
102  }
103
104  // --- Accessors ---
105
106  /// Get the start value.
107  pub fn from_value(&self) -> T {
108    self.from
109  }
110
111  /// Get the end value.
112  pub fn to_value(&self) -> T {
113    self.to
114  }
115
116  /// Get the current interpolated value.
117  ///
118  /// This applies the easing function to the current progress.
119  pub fn value(&self) -> T {
120    let progress = self.eased_progress();
121
122    // When reversed via yoyo, we flip the interpolation
123    if self.repeat_config.should_reverse(self.iteration) {
124      self.to.lerp(self.from, progress)
125    } else {
126      self.from.lerp(self.to, progress)
127    }
128  }
129
130  /// Get the raw progress [0, 1] without easing applied.
131  pub fn raw_progress(&self) -> f32 {
132    if self.duration == 0.0 {
133      1.0
134    } else {
135      (self.elapsed / self.duration).clamp(0.0, 1.0)
136    }
137  }
138
139  /// Get the eased progress using the configured easing function.
140  pub fn eased_progress(&self) -> f32 {
141    let raw = self.raw_progress();
142
143    // Apply direction
144    let directed = if self.direction.is_reverse() {
145      1.0 - raw
146    } else {
147      raw
148    };
149
150    self.easing.y(directed)
151  }
152
153  /// Get the current iteration count.
154  pub fn iteration(&self) -> u32 {
155    self.iteration
156  }
157
158  /// Check if currently in a yoyo reverse phase.
159  pub fn is_yoyo_reversed(&self) -> bool {
160    self.repeat_config.should_reverse(self.iteration)
161  }
162
163  // --- Configuration ---
164
165  /// Set the easing function.
166  pub fn set_easing(&mut self, easing: Easing) {
167    self.easing = easing;
168  }
169
170  /// Set the repeat configuration.
171  pub fn set_repeat(&mut self, config: RepeatConfig) {
172    self.repeat_config = config;
173  }
174
175  /// Set the initial delay.
176  pub fn set_delay(&mut self, delay: f32) {
177    self.delay = delay.max(0.0);
178
179    if self.state == TweenState::Idle {
180      self.delay_remaining = self.delay;
181    }
182  }
183
184  /// Set the callbacks.
185  pub fn set_callbacks(&mut self, callbacks: Callbacks) {
186    self.callbacks = callbacks;
187  }
188
189  // --- Internal ---
190
191  fn handle_completion(&mut self) {
192    // Check if we should repeat
193    if self.repeat_config.repeat.can_repeat(self.iteration) {
194      self.iteration += 1;
195      self.elapsed = 0.0;
196      self.delay_remaining = self.repeat_config.delay;
197      self.callbacks.fire_repeat();
198    } else {
199      self.state = TweenState::Complete;
200      self.callbacks.fire_complete();
201    }
202  }
203}
204
205impl<T: Tweenable> Controllable for Tween<T> {
206  fn play(&mut self) {
207    match self.state {
208      TweenState::Idle => {
209        self.state = TweenState::Playing;
210        self.delay_remaining = self.delay;
211        self.started = false;
212      }
213      TweenState::Paused => {
214        self.state = TweenState::Playing;
215      }
216      TweenState::Complete => {
217        // Restart from beginning
218        self.restart();
219      }
220      TweenState::Playing => {
221        // Already playing
222      }
223    }
224  }
225
226  fn pause(&mut self) {
227    if self.state == TweenState::Playing {
228      self.state = TweenState::Paused;
229    }
230  }
231
232  fn resume(&mut self) {
233    if self.state == TweenState::Paused {
234      self.state = TweenState::Playing;
235    }
236  }
237
238  fn reverse(&mut self) {
239    self.direction.toggle();
240  }
241
242  fn restart(&mut self) {
243    self.elapsed = 0.0;
244    self.iteration = 0;
245    self.delay_remaining = self.delay;
246    self.started = false;
247    self.state = TweenState::Playing;
248  }
249
250  fn seek(&mut self, time: f32) {
251    self.elapsed = time.clamp(0.0, self.duration);
252  }
253
254  fn kill(&mut self) {
255    self.state = TweenState::Idle;
256    self.elapsed = 0.0;
257    self.iteration = 0;
258    self.delay_remaining = self.delay;
259    self.started = false;
260  }
261
262  fn progress(&self) -> f32 {
263    self.raw_progress()
264  }
265
266  fn set_progress(&mut self, progress: f32) {
267    self.elapsed = progress.clamp(0.0, 1.0) * self.duration;
268  }
269
270  fn duration(&self) -> f32 {
271    self.duration
272  }
273
274  fn elapsed(&self) -> f32 {
275    self.elapsed
276  }
277
278  fn state(&self) -> TweenState {
279    self.state
280  }
281
282  fn direction(&self) -> Direction {
283    self.direction
284  }
285
286  fn time_scale(&self) -> f32 {
287    self.time_scale
288  }
289
290  fn set_time_scale(&mut self, scale: f32) {
291    self.time_scale = scale.max(0.0);
292  }
293
294  fn tick(&mut self, delta: f32) -> bool {
295    if self.state != TweenState::Playing {
296      return self.state.is_active();
297    }
298
299    let scaled_delta = delta * self.time_scale;
300
301    // Handle initial delay
302    if self.delay_remaining > 0.0 {
303      self.delay_remaining -= scaled_delta;
304
305      if self.delay_remaining > 0.0 {
306        return true;
307      }
308
309      // Delay complete, apply overflow to elapsed
310      let overflow = -self.delay_remaining;
311
312      self.delay_remaining = 0.0;
313      self.elapsed += overflow;
314    } else {
315      self.elapsed += scaled_delta;
316    }
317
318    // Fire on_start once per play cycle
319    if !self.started {
320      self.started = true;
321      self.callbacks.fire_start();
322    }
323
324    // Fire on_update
325    self.callbacks.fire_update();
326
327    // Check completion
328    if self.elapsed >= self.duration {
329      self.elapsed = self.duration;
330      self.handle_completion();
331    }
332
333    self.state.is_active()
334  }
335}
336
337/// Builder for creating tweens with fluent API.
338#[derive(Debug, Clone)]
339pub struct TweenBuilder<T: Tweenable> {
340  tween: Tween<T>,
341}
342
343impl<T: Tweenable> TweenBuilder<T> {
344  /// Create a new builder.
345  pub fn new(from: T, to: T) -> Self {
346    Self {
347      tween: Tween::new(from, to, 1.0),
348    }
349  }
350
351  /// Set the duration in seconds.
352  pub fn duration(mut self, secs: f32) -> Self {
353    self.tween.duration = secs.max(0.0);
354    self
355  }
356
357  /// Set the easing function.
358  pub fn easing(mut self, easing: Easing) -> Self {
359    self.tween.easing = easing;
360    self
361  }
362
363  /// Set repeat count.
364  pub fn repeat(mut self, repeat: impl Into<Repeat>) -> Self {
365    self.tween.repeat_config.repeat = repeat.into();
366    self
367  }
368
369  /// Enable yoyo mode (alternate direction on each repeat).
370  pub fn yoyo(mut self, enabled: bool) -> Self {
371    self.tween.repeat_config.yoyo = enabled;
372    self
373  }
374
375  /// Set delay between repeats.
376  pub fn repeat_delay(mut self, delay: f32) -> Self {
377    self.tween.repeat_config.delay = delay;
378    self
379  }
380
381  /// Set initial delay before the animation starts.
382  pub fn delay(mut self, delay: f32) -> Self {
383    self.tween.delay = delay.max(0.0);
384    self.tween.delay_remaining = self.tween.delay;
385    self
386  }
387
388  /// Set the time scale multiplier.
389  pub fn time_scale(mut self, scale: f32) -> Self {
390    self.tween.time_scale = scale.max(0.0);
391    self
392  }
393
394  /// Set a sync callback for on_start.
395  pub fn on_start<F>(mut self, f: F) -> Self
396  where
397    F: Fn() + Send + Sync + 'static,
398  {
399    self.tween.callbacks.on_start = Some(crate::callback::Callback::sync(f));
400    self
401  }
402
403  /// Set a sync callback for on_update.
404  pub fn on_update<F>(mut self, f: F) -> Self
405  where
406    F: Fn() + Send + Sync + 'static,
407  {
408    self.tween.callbacks.on_update = Some(crate::callback::Callback::sync(f));
409    self
410  }
411
412  /// Set a sync callback for on_complete.
413  pub fn on_complete<F>(mut self, f: F) -> Self
414  where
415    F: Fn() + Send + Sync + 'static,
416  {
417    self.tween.callbacks.on_complete = Some(crate::callback::Callback::sync(f));
418    self
419  }
420
421  /// Set a sync callback for on_repeat.
422  pub fn on_repeat<F>(mut self, f: F) -> Self
423  where
424    F: Fn() + Send + Sync + 'static,
425  {
426    self.tween.callbacks.on_repeat = Some(crate::callback::Callback::sync(f));
427    self
428  }
429
430  /// Build the tween.
431  pub fn build(self) -> Tween<T> {
432    self.tween
433  }
434}
435
436#[cfg(test)]
437mod tests {
438  use super::*;
439
440  #[test]
441  fn test_basic_tween() {
442    let mut tween = Tween::new(0.0_f32, 100.0, 1.0);
443
444    tween.play();
445
446    assert_eq!(tween.value(), 0.0);
447
448    tween.tick(0.5);
449    assert!((tween.value() - 50.0).abs() < 0.001);
450
451    tween.tick(0.5);
452    assert_eq!(tween.value(), 100.0);
453    assert!(tween.state().is_complete());
454  }
455
456  #[test]
457  fn test_tween_builder() {
458    let tween = Tween::to(0.0_f32, 100.0)
459      .duration(2.0)
460      .easing(Easing::InOutQuadratic)
461      .delay(0.5)
462      .build();
463
464    assert_eq!(tween.duration(), 2.0);
465    assert_eq!(tween.delay, 0.5);
466  }
467
468  #[test]
469  fn test_repeat() {
470    let mut tween =
471      Tween::to(0.0_f32, 100.0).duration(1.0).repeat(2u32).build();
472
473    tween.play();
474    tween.tick(1.0); // Complete first
475    assert_eq!(tween.iteration(), 1);
476    assert!(tween.state().is_active());
477
478    tween.tick(1.0); // Complete second
479    assert_eq!(tween.iteration(), 2);
480    assert!(tween.state().is_active());
481
482    tween.tick(1.0); // Complete third (final)
483    assert!(tween.state().is_complete());
484  }
485
486  #[test]
487  fn test_yoyo() {
488    let mut tween = Tween::to(0.0_f32, 100.0)
489      .duration(1.0)
490      .repeat(1u32)
491      .yoyo(true)
492      .build();
493
494    tween.play();
495
496    // First iteration: 0 -> 100
497    tween.tick(0.5);
498    assert!((tween.value() - 50.0).abs() < 0.001);
499
500    tween.tick(0.5);
501    assert_eq!(tween.iteration(), 1);
502
503    // Second iteration (yoyo): 100 -> 0
504    tween.tick(0.5);
505    assert!((tween.value() - 50.0).abs() < 0.001);
506  }
507
508  #[test]
509  fn test_delay() {
510    let mut tween = Tween::to(0.0_f32, 100.0).duration(1.0).delay(0.5).build();
511
512    tween.play();
513
514    tween.tick(0.25);
515    assert_eq!(tween.elapsed(), 0.0); // Still in delay
516
517    tween.tick(0.25);
518    assert_eq!(tween.elapsed(), 0.0); // Still in delay
519
520    tween.tick(0.25);
521    // Delay complete, 0.25s into animation
522    assert!(tween.elapsed() > 0.0);
523  }
524
525  #[test]
526  fn test_time_scale() {
527    let mut tween = Tween::to(0.0_f32, 100.0)
528      .duration(1.0)
529      .time_scale(2.0)
530      .build();
531
532    tween.play();
533    tween.tick(0.25); // At 2x speed, this is 0.5s of animation
534
535    assert!((tween.elapsed() - 0.5).abs() < 0.001);
536  }
537
538  #[test]
539  fn test_seek() {
540    let mut tween = Tween::new(0.0_f32, 100.0, 1.0);
541
542    tween.seek(0.75);
543    assert!((tween.value() - 75.0).abs() < 0.001);
544  }
545
546  #[test]
547  fn test_array_tween() {
548    let mut tween = Tween::new([0.0_f32, 0.0, 0.0], [100.0, 200.0, 300.0], 1.0);
549
550    tween.play();
551    tween.tick(0.5);
552
553    let value = tween.value();
554
555    assert!((value[0] - 50.0).abs() < 0.001);
556    assert!((value[1] - 100.0).abs() < 0.001);
557    assert!((value[2] - 150.0).abs() < 0.001);
558  }
559}