eazy_tweener/
repeat.rs

1//! Repeat and yoyo configuration for animations.
2//!
3//! Controls how animations loop and alternate direction.
4
5/// How many times an animation should repeat.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum Repeat {
8  /// Play once, no repeating.
9  #[default]
10  None,
11  /// Repeat a specific number of times after the initial play.
12  /// `Count(1)` means play twice total (initial + 1 repeat).
13  Count(u32),
14  /// Repeat forever.
15  Infinite,
16}
17
18impl Repeat {
19  /// Check if repeating is enabled.
20  pub fn is_repeating(&self) -> bool {
21    !matches!(self, Self::None)
22  }
23
24  /// Check if infinite repeating.
25  pub fn is_infinite(&self) -> bool {
26    matches!(self, Self::Infinite)
27  }
28
29  /// Get the count if finite, None if infinite or none.
30  pub fn count(&self) -> Option<u32> {
31    match self {
32      Self::None => Some(0),
33      Self::Count(n) => Some(*n),
34      Self::Infinite => None,
35    }
36  }
37
38  /// Check if more repeats are available.
39  pub fn can_repeat(&self, current_iteration: u32) -> bool {
40    match self {
41      Self::None => false,
42      Self::Count(n) => current_iteration < *n,
43      Self::Infinite => true,
44    }
45  }
46}
47
48impl From<u32> for Repeat {
49  fn from(count: u32) -> Self {
50    if count == 0 {
51      Self::None
52    } else {
53      Self::Count(count)
54    }
55  }
56}
57
58impl From<i32> for Repeat {
59  fn from(count: i32) -> Self {
60    if count < 0 {
61      Self::Infinite
62    } else if count == 0 {
63      Self::None
64    } else {
65      Self::Count(count as u32)
66    }
67  }
68}
69
70/// Complete repeat configuration for a tween.
71#[derive(Debug, Clone, Copy, Default)]
72pub struct RepeatConfig {
73  /// How many times to repeat.
74  pub repeat: Repeat,
75  /// Alternate direction on each repeat (ping-pong effect).
76  pub yoyo: bool,
77  /// Delay in seconds before each repeat.
78  pub delay: f32,
79}
80
81impl RepeatConfig {
82  /// Create a new repeat config with no repeating.
83  pub fn none() -> Self {
84    Self::default()
85  }
86
87  /// Create a repeat config that repeats `n` times.
88  pub fn count(n: u32) -> Self {
89    Self {
90      repeat: Repeat::Count(n),
91      yoyo: false,
92      delay: 0.0,
93    }
94  }
95
96  /// Create a repeat config that repeats infinitely.
97  pub fn infinite() -> Self {
98    Self {
99      repeat: Repeat::Infinite,
100      yoyo: false,
101      delay: 0.0,
102    }
103  }
104
105  /// Enable yoyo mode (alternate direction on each repeat).
106  pub fn with_yoyo(mut self, yoyo: bool) -> Self {
107    self.yoyo = yoyo;
108    self
109  }
110
111  /// Set delay before each repeat.
112  pub fn with_delay(mut self, delay: f32) -> Self {
113    self.delay = delay;
114    self
115  }
116
117  /// Check if this config has any repeating.
118  pub fn is_repeating(&self) -> bool {
119    self.repeat.is_repeating()
120  }
121
122  /// Check if the animation should reverse on the given iteration.
123  ///
124  /// With yoyo enabled, odd iterations play in reverse.
125  pub fn should_reverse(&self, iteration: u32) -> bool {
126    self.yoyo && iteration % 2 == 1
127  }
128
129  /// Calculate total duration including all repeats.
130  ///
131  /// Returns `None` for infinite repeats.
132  pub fn total_duration(&self, single_duration: f32) -> Option<f32> {
133    match self.repeat {
134      Repeat::None => Some(single_duration),
135      Repeat::Count(n) => {
136        let repeat_duration = single_duration + self.delay;
137
138        Some(single_duration + repeat_duration * n as f32)
139      }
140      Repeat::Infinite => None,
141    }
142  }
143}
144
145#[cfg(test)]
146mod tests {
147  use super::*;
148
149  #[test]
150  fn test_repeat_can_repeat() {
151    assert!(!Repeat::None.can_repeat(0));
152    assert!(Repeat::Count(3).can_repeat(0));
153    assert!(Repeat::Count(3).can_repeat(2));
154    assert!(!Repeat::Count(3).can_repeat(3));
155    assert!(Repeat::Infinite.can_repeat(1000));
156  }
157
158  #[test]
159  fn test_repeat_from_i32() {
160    assert_eq!(Repeat::from(-1), Repeat::Infinite);
161    assert_eq!(Repeat::from(0), Repeat::None);
162    assert_eq!(Repeat::from(5), Repeat::Count(5));
163  }
164
165  #[test]
166  fn test_yoyo_direction() {
167    let config = RepeatConfig::count(3).with_yoyo(true);
168
169    assert!(!config.should_reverse(0)); // forward
170    assert!(config.should_reverse(1)); // reverse
171    assert!(!config.should_reverse(2)); // forward
172    assert!(config.should_reverse(3)); // reverse
173  }
174
175  #[test]
176  fn test_total_duration() {
177    let config = RepeatConfig::count(2).with_delay(0.5);
178
179    // 1.0 + (1.0 + 0.5) * 2 = 1.0 + 3.0 = 4.0
180    assert_eq!(config.total_duration(1.0), Some(4.0));
181  }
182
183  #[test]
184  fn test_infinite_duration() {
185    let config = RepeatConfig::infinite();
186
187    assert_eq!(config.total_duration(1.0), None);
188  }
189}