Skip to main content

aura_anim_core/
timing.rs

1//! Timing configuration and elapsed-time normalization.
2
3mod duration;
4mod iteration;
5mod mode;
6mod utils;
7
8pub use duration::{Delay, Duration};
9pub use iteration::IterationCount;
10pub use mode::Direction;
11
12pub use lilt::Easing;
13
14/// Timing configuration shared by duration-based animations.
15///
16/// # Examples
17///
18/// ```
19/// use aura_anim_core::timing::{Delay, Direction, Timing};
20///
21/// let timing = Timing::ease_out(250.0)
22///     .with_delay(Delay::from_millis(50.0))
23///     .with_direction(Direction::Alternate)
24///     .with_iterations(2);
25///
26/// assert_eq!(timing.duration().as_millis(), 250.0);
27/// assert_eq!(timing.total_duration().unwrap().as_millis(), 550.0);
28/// ```
29#[derive(Debug, Clone, Copy, PartialEq)]
30pub struct Timing {
31    /// Active duration for one iteration.
32    duration: Duration,
33    /// Start delay before the active interval.
34    delay: Delay,
35    /// Playback direction configuration.
36    direction: Direction,
37    /// Easing curve applied to normalized iteration progress.
38    easing: Easing,
39    /// Number of active iterations.
40    iterations: IterationCount,
41}
42
43impl Timing {
44    /// Creates a timing value with a duration in milliseconds.
45    #[must_use]
46    pub fn new(duration_ms: impl Into<Duration>) -> Self {
47        Self {
48            duration: duration_ms.into(),
49            ..Self::default()
50        }
51    }
52
53    /// Creates a linear timing with a duration in milliseconds.
54    #[must_use]
55    pub fn linear(duration_ms: impl Into<Duration>) -> Self {
56        Self::new(duration_ms)
57    }
58
59    /// Creates an ease-in timing with a duration in milliseconds.
60    #[must_use]
61    pub fn ease_in(duration_ms: impl Into<Duration>) -> Self {
62        Self::new(duration_ms).with_easing(Easing::EaseIn)
63    }
64
65    /// Creates an ease-out timing with a duration in milliseconds.
66    #[must_use]
67    pub fn ease_out(duration_ms: impl Into<Duration>) -> Self {
68        Self::new(duration_ms).with_easing(Easing::EaseOut)
69    }
70
71    /// Creates an ease-in-out timing with a duration in milliseconds.
72    #[must_use]
73    pub fn ease_in_out(duration_ms: impl Into<Duration>) -> Self {
74        Self::new(duration_ms).with_easing(Easing::EaseInOut)
75    }
76
77    /// Returns the duration of the timing.
78    #[must_use]
79    pub const fn duration(&self) -> Duration {
80        self.duration
81    }
82
83    /// Returns the delay of the timing.
84    #[must_use]
85    pub const fn delay(&self) -> Delay {
86        self.delay
87    }
88
89    /// Returns the direction of the timing.
90    #[must_use]
91    pub const fn direction(&self) -> Direction {
92        self.direction
93    }
94
95    /// Returns the easing curve of the timing.
96    #[must_use]
97    pub const fn easing(&self) -> Easing {
98        self.easing
99    }
100
101    /// Returns the number of iterations of the timing.
102    #[must_use]
103    pub const fn iterations(&self) -> IterationCount {
104        self.iterations
105    }
106
107    /// Sets the duration.
108    #[must_use]
109    pub const fn with_duration(mut self, duration: Duration) -> Self {
110        self.duration = duration;
111        self
112    }
113
114    /// Sets the start delay.
115    #[must_use]
116    pub const fn with_delay(mut self, delay: Delay) -> Self {
117        self.delay = delay;
118        self
119    }
120
121    /// Sets the playback direction.
122    #[must_use]
123    pub const fn with_direction(mut self, direction: Direction) -> Self {
124        self.direction = direction;
125        self
126    }
127
128    /// Sets the easing curve.
129    #[must_use]
130    pub const fn with_easing(mut self, easing: Easing) -> Self {
131        self.easing = easing;
132        self
133    }
134
135    /// Sets the iteration count.
136    #[must_use]
137    pub fn with_iterations(mut self, iterations: impl Into<IterationCount>) -> Self {
138        self.iterations = iterations.into();
139        self
140    }
141
142    /// Returns the total active duration when the timing has a finite length.
143    #[must_use]
144    pub fn active_duration(self) -> Option<Duration> {
145        let count = self.iterations.finite_count()?;
146
147        self.duration.checked_mul(count)
148    }
149
150    /// Returns the total duration including delay when finite.
151    #[must_use]
152    pub fn total_duration(self) -> Option<Duration> {
153        let active = self.active_duration()?;
154
155        active.checked_add_delay(self.delay)
156    }
157
158    pub(crate) fn with_rate(mut self, rate: f64) -> Self {
159        self.duration = self.duration.divided_by(rate);
160        self
161    }
162}
163
164impl Default for Timing {
165    fn default() -> Self {
166        Self {
167            duration: Duration::ZERO,
168            delay: Delay::ZERO,
169            direction: Direction::default(),
170            easing: Easing::Linear,
171            iterations: IterationCount::default(),
172        }
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::{Delay, Timing};
179    use float_cmp::assert_approx_eq;
180
181    #[test]
182    fn rate_scales_active_duration_without_changing_delay() {
183        let faster = Timing::new(200.0)
184            .with_delay(Delay::from_millis(40.0))
185            .with_iterations(3)
186            .with_rate(2.0);
187        let slower = Timing::new(200.0).with_rate(0.5);
188
189        assert_approx_eq!(f64, faster.duration().as_millis(), 100.0);
190        assert_approx_eq!(f64, faster.delay().as_millis(), 40.0);
191        assert_approx_eq!(f64, faster.total_duration().unwrap().as_millis(), 340.0);
192        assert_approx_eq!(f64, slower.duration().as_millis(), 400.0);
193    }
194
195    #[test]
196    fn invalid_rate_leaves_duration_unchanged() {
197        let timing = Timing::new(200.0);
198
199        assert_eq!(timing.with_rate(0.0).duration(), timing.duration());
200        assert_eq!(timing.with_rate(-1.0).duration(), timing.duration());
201        assert_eq!(timing.with_rate(f64::NAN).duration(), timing.duration());
202        assert_eq!(
203            timing.with_rate(f64::INFINITY).duration(),
204            timing.duration()
205        );
206    }
207}