Skip to main content

aura_anim_iced/
timing.rs

1//! Timing configuration and elapsed-time normalization.
2
3mod duration;
4mod iteration;
5mod mode;
6mod normalized;
7mod utils;
8
9pub use duration::{Delay, Duration};
10pub use iced::animation::Easing;
11pub use iteration::IterationCount;
12pub use mode::{Direction, FillMode};
13pub use normalized::{NormalizedTiming, TimingPhase, TimingSampleState};
14
15#[cfg(test)]
16mod tests;
17
18use crate::{
19    nearly_equal_f64,
20    timing::utils::{completed_iterations_from, sanitize_non_negative, sanitize_playback_rate},
21};
22
23/// Timing state for an animation track or timeline step.
24///
25/// # Example
26///
27/// ```
28/// use aura_anim_iced::timing::{
29///     Delay, Direction, Easing, FillMode, Timing, TimingSampleState,
30/// };
31///
32/// let timing = Timing::new(200.0)
33///     .with_delay(Delay::from_millis(50.0))
34///     .with_direction(Direction::Alternate)
35///     .with_fill_mode(FillMode::Both)
36///     .with_easing(Easing::EaseOut)
37///     .with_iterations(2);
38///
39/// let before = timing.normalize_elapsed(25.0);
40/// let active = timing.normalize_elapsed(100.0);
41///
42/// assert_eq!(before.sample_state, TimingSampleState::BackwardsFill);
43/// assert!(active.has_sample());
44/// ```
45#[derive(Debug, Clone, Copy, PartialEq)]
46pub struct Timing {
47    /// Active duration for one iteration.
48    duration: Duration,
49    /// Start delay before the active interval.
50    delay: Delay,
51    /// Playback direction configuration.
52    direction: Direction,
53    /// Fill behavior outside the active interval.
54    fill_mode: FillMode,
55    /// Easing curve applied to normalized iteration progress.
56    easing: Easing,
57    /// Number of active iterations.
58    iterations: IterationCount,
59    /// Elapsed-time multiplier. Values at or below zero are normalized to `1.0`.
60    playback_rate: f64,
61}
62
63impl Timing {
64    /// Creates a timing value with a duration in milliseconds.
65    #[must_use]
66    pub fn new(duration_ms: f64) -> Self {
67        Self {
68            duration: Duration::from_millis(duration_ms),
69            ..Self::default()
70        }
71    }
72
73    /// Returns the duration of the timing.
74    #[must_use]
75    pub const fn duration(&self) -> Duration {
76        self.duration
77    }
78
79    /// Returns the delay of the timing.
80    #[must_use]
81    pub const fn delay(&self) -> Delay {
82        self.delay
83    }
84
85    /// Returns the direction of the timing.
86    #[must_use]
87    pub const fn direction(&self) -> Direction {
88        self.direction
89    }
90
91    /// Returns the fill mode of the timing.
92    #[must_use]
93    pub const fn fill_mode(&self) -> FillMode {
94        self.fill_mode
95    }
96
97    /// Returns the easing curve of the timing.
98    #[must_use]
99    pub const fn easing(&self) -> Easing {
100        self.easing
101    }
102
103    /// Returns the number of iterations of the timing.
104    #[must_use]
105    pub const fn iterations(&self) -> IterationCount {
106        self.iterations
107    }
108
109    /// Returns the playback rate of the timing.
110    #[must_use]
111    pub const fn playback_rate(&self) -> f64 {
112        self.playback_rate
113    }
114
115    /// Sets the start delay.
116    #[must_use]
117    pub const fn with_delay(mut self, delay: Delay) -> Self {
118        self.delay = delay;
119        self
120    }
121
122    /// Sets the playback direction.
123    #[must_use]
124    pub const fn with_direction(mut self, direction: Direction) -> Self {
125        self.direction = direction;
126        self
127    }
128
129    /// Sets the fill mode.
130    #[must_use]
131    pub const fn with_fill_mode(mut self, fill_mode: FillMode) -> Self {
132        self.fill_mode = fill_mode;
133        self
134    }
135
136    /// Sets the easing curve.
137    #[must_use]
138    pub const fn with_easing(mut self, easing: Easing) -> Self {
139        self.easing = easing;
140        self
141    }
142
143    /// Sets the iteration count.
144    #[must_use]
145    pub fn with_iterations(mut self, iterations: impl Into<IterationCount>) -> Self {
146        self.iterations = iterations.into();
147        self
148    }
149
150    /// Sets the playback rate.
151    #[must_use]
152    pub fn with_playback_rate(mut self, playback_rate: f64) -> Self {
153        self.playback_rate = sanitize_playback_rate(playback_rate);
154        self
155    }
156
157    /// Returns the total active duration when the timing has a finite length.
158    #[must_use]
159    pub fn active_duration(self) -> Option<Duration> {
160        let count = self.iterations.finite_count()?;
161
162        self.duration.checked_mul(count)
163    }
164
165    /// Returns the total duration including delay when finite.
166    #[must_use]
167    pub fn total_duration(self) -> Option<Duration> {
168        let active = self.active_duration()?;
169
170        active.checked_add_delay(self.delay)
171    }
172
173    /// Normalizes elapsed milliseconds into active timing coordinates.
174    #[must_use]
175    pub fn normalize_elapsed(self, elapsed_ms: f64) -> NormalizedTiming {
176        let elapsed_ms = sanitize_non_negative(elapsed_ms);
177        let scaled_elapsed = elapsed_ms * self.playback_rate;
178        let delay_ms = self.delay.as_millis();
179
180        if scaled_elapsed < delay_ms {
181            return NormalizedTiming::before_start(self.fill_mode, self.direction.start_progress());
182        }
183
184        let active_elapsed = scaled_elapsed - delay_ms;
185        let duration_ms = self.duration.as_millis();
186
187        if nearly_equal_f64(duration_ms, 0.0) {
188            let count = self.iterations.finite_count().unwrap_or(1);
189
190            return NormalizedTiming::after_end(
191                count,
192                self.fill_mode,
193                self.direction.end_progress(count),
194            );
195        }
196
197        let active_progress = active_elapsed / duration_ms;
198        let completed_iterations = completed_iterations_from(active_progress);
199
200        if let Some(iteration_count) = self.iterations.finite_count()
201            && completed_iterations >= iteration_count
202        {
203            return NormalizedTiming::after_end(
204                iteration_count,
205                self.fill_mode,
206                self.direction.end_progress(iteration_count),
207            );
208        }
209
210        if !active_progress.is_finite() {
211            let directed_iteration_progress =
212                self.direction.sample_progress(completed_iterations, 0.0);
213
214            return NormalizedTiming::active(
215                completed_iterations,
216                directed_iteration_progress,
217                f64::from(completed_iterations),
218            );
219        }
220
221        let iteration_elapsed = active_elapsed % duration_ms;
222        let raw_iteration_progress = iteration_elapsed / duration_ms;
223        let directed_iteration_progress = self
224            .direction
225            .sample_progress(completed_iterations, raw_iteration_progress);
226
227        NormalizedTiming::active(
228            completed_iterations,
229            directed_iteration_progress,
230            active_progress,
231        )
232    }
233}
234
235impl Default for Timing {
236    fn default() -> Self {
237        Self {
238            duration: Duration::ZERO,
239            delay: Delay::ZERO,
240            direction: Direction::default(),
241            fill_mode: FillMode::default(),
242            easing: Easing::Linear,
243            iterations: IterationCount::default(),
244            playback_rate: 1.0,
245        }
246    }
247}