Skip to main content

argui_animation/
timeline.rs

1use crate::{
2    Direction, Duration, Easing, FillMode, Interpolate, Iterations, Keyframe, Keyframes, Time,
3    Timing, TimingError,
4};
5
6#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
7pub enum PlaybackState {
8    #[default]
9    Idle,
10    Running,
11    Paused,
12    Finished,
13    Canceled,
14}
15
16#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
17pub struct TimelineEvents {
18    pub started: bool,
19    pub iterations: u64,
20    pub finished: bool,
21    pub canceled: bool,
22}
23
24#[derive(Clone, Debug)]
25pub struct TimelineSample<T> {
26    pub value: Option<T>,
27    pub events: TimelineEvents,
28    pub state: PlaybackState,
29}
30
31#[derive(Clone, Debug)]
32pub struct Timeline<T> {
33    keyframes: Keyframes<T>,
34    timing: Timing,
35    state: PlaybackState,
36    anchor_time: Time,
37    anchor_position: f64,
38    playback_rate: f64,
39    started_emitted: bool,
40    iteration_marker: u64,
41    pending_finished: bool,
42    pending_canceled: bool,
43}
44
45impl<T> Timeline<T> {
46    pub fn new(keyframes: Keyframes<T>, timing: Timing) -> Result<Self, TimingError> {
47        let timing = timing.validate()?;
48        Ok(Self {
49            keyframes,
50            timing,
51            state: PlaybackState::Idle,
52            anchor_time: Time::ZERO,
53            anchor_position: 0.0,
54            playback_rate: timing.playback_rate,
55            started_emitted: false,
56            iteration_marker: 0,
57            pending_finished: false,
58            pending_canceled: false,
59        })
60    }
61
62    #[must_use]
63    pub const fn state(&self) -> PlaybackState {
64        self.state
65    }
66
67    #[must_use]
68    pub const fn timing(&self) -> Timing {
69        self.timing
70    }
71
72    #[must_use]
73    pub fn needs_frame(&self) -> bool {
74        self.state == PlaybackState::Running
75    }
76
77    pub fn play(&mut self, now: Time) {
78        match self.state {
79            PlaybackState::Running => return,
80            PlaybackState::Paused => {
81                self.anchor_time = now;
82                self.state = PlaybackState::Running;
83                return;
84            }
85            PlaybackState::Idle | PlaybackState::Finished | PlaybackState::Canceled => {}
86        }
87        self.reset_position();
88        self.anchor_time = now;
89        self.state = PlaybackState::Running;
90    }
91
92    pub fn pause(&mut self, now: Time) {
93        if self.state == PlaybackState::Running {
94            self.anchor_position = self.position(now);
95            self.anchor_time = now;
96            self.state = PlaybackState::Paused;
97        }
98    }
99
100    pub fn resume(&mut self, now: Time) {
101        if self.state == PlaybackState::Paused {
102            self.anchor_time = now;
103            self.state = PlaybackState::Running;
104        }
105    }
106
107    pub fn restart(&mut self, now: Time) {
108        self.reset_position();
109        self.anchor_time = now;
110        self.state = PlaybackState::Running;
111    }
112
113    pub fn seek(&mut self, position: Duration, now: Time) {
114        self.anchor_position = self.clamp_position(position.as_secs_f64());
115        self.anchor_time = now;
116        self.reset_events_for_position();
117    }
118
119    pub fn set_playback_rate(&mut self, playback_rate: f64, now: Time) -> Result<(), TimingError> {
120        if !playback_rate.is_finite() || playback_rate == 0.0 {
121            return Err(TimingError::InvalidPlaybackRate);
122        }
123        self.anchor_position = self.position(now);
124        self.anchor_time = now;
125        self.playback_rate = playback_rate;
126        Ok(())
127    }
128
129    pub fn reverse(&mut self, now: Time) {
130        let rate = -self.playback_rate;
131        if matches!(
132            self.state,
133            PlaybackState::Idle | PlaybackState::Finished | PlaybackState::Canceled
134        ) {
135            self.playback_rate = rate;
136            self.restart(now);
137        } else {
138            self.anchor_position = self.position(now);
139            self.anchor_time = now;
140            self.playback_rate = rate;
141        }
142    }
143
144    pub fn finish(&mut self) {
145        self.anchor_position = if self.playback_rate >= 0.0 {
146            self.timing.total_seconds()
147        } else {
148            0.0
149        };
150        self.state = PlaybackState::Finished;
151        self.pending_finished = true;
152    }
153
154    pub fn cancel(&mut self) {
155        self.state = PlaybackState::Canceled;
156        self.pending_canceled = true;
157    }
158
159    fn position(&self, now: Time) -> f64 {
160        if self.state != PlaybackState::Running {
161            return self.anchor_position;
162        }
163        let elapsed = now.duration_since(self.anchor_time).as_secs_f64();
164        self.clamp_position(self.anchor_position + elapsed * self.playback_rate)
165    }
166
167    fn clamp_position(&self, position: f64) -> f64 {
168        position.max(0.0).min(self.timing.total_seconds())
169    }
170
171    fn reset_position(&mut self) {
172        self.anchor_position = if self.playback_rate >= 0.0 {
173            0.0
174        } else {
175            self.timing.total_seconds()
176        };
177        self.started_emitted = false;
178        self.iteration_marker = if self.playback_rate >= 0.0 {
179            0
180        } else {
181            self.maximum_iteration_events()
182        };
183        self.pending_finished = false;
184        self.pending_canceled = false;
185    }
186
187    fn reset_events_for_position(&mut self) {
188        let active = (self.anchor_position - self.timing.delay.as_secs_f64()).max(0.0);
189        self.started_emitted = active > 0.0;
190        self.iteration_marker = self.iteration_events_at(active);
191        self.pending_finished = false;
192        self.pending_canceled = false;
193    }
194
195    fn maximum_iteration_events(&self) -> u64 {
196        match self.timing.iterations {
197            Iterations::Finite(iterations) => iterations.ceil().max(1.0) as u64 - 1,
198            Iterations::Infinite => u64::MAX,
199        }
200    }
201
202    fn iteration_events_at(&self, active_seconds: f64) -> u64 {
203        let completed = (active_seconds / self.timing.duration.as_secs_f64()).floor();
204        (completed as u64).min(self.maximum_iteration_events())
205    }
206}
207
208impl<T: Clone + Interpolate> Timeline<T> {
209    pub(crate) fn terminal_value(&self) -> T {
210        let progress = if self.playback_rate >= 0.0 {
211            self.final_progress()
212        } else {
213            self.directed_progress(0.0, 0)
214        };
215        self.keyframes.sample(progress)
216    }
217
218    pub fn retarget(
219        &mut self,
220        target: T,
221        duration: Duration,
222        easing: Easing,
223        now: Time,
224    ) -> Result<(), TimingError> {
225        let current = self
226            .sample(now)
227            .value
228            .unwrap_or_else(|| self.keyframes.sample(0.0));
229        self.keyframes = Keyframes::new(vec![
230            Keyframe::new(0.0, current).easing(easing),
231            Keyframe::new(1.0, target),
232        ])?;
233        self.timing = Timing::new(duration).fill(FillMode::Forwards).validate()?;
234        self.playback_rate = 1.0;
235        self.restart(now);
236        Ok(())
237    }
238
239    #[must_use]
240    pub fn sample(&mut self, now: Time) -> TimelineSample<T> {
241        let mut events = TimelineEvents {
242            finished: std::mem::take(&mut self.pending_finished),
243            canceled: std::mem::take(&mut self.pending_canceled),
244            ..TimelineEvents::default()
245        };
246        if matches!(self.state, PlaybackState::Idle | PlaybackState::Canceled) {
247            return TimelineSample {
248                value: None,
249                events,
250                state: self.state,
251            };
252        }
253
254        let mut position = self.position(now);
255        let delay = self.timing.delay.as_secs_f64();
256        let active_end = delay + self.timing.active_seconds();
257        if position >= delay && !self.started_emitted {
258            self.started_emitted = true;
259            events.started = true;
260        }
261        if position >= delay {
262            let marker =
263                self.iteration_events_at((position - delay).min(self.timing.active_seconds()));
264            events.iterations = marker.abs_diff(self.iteration_marker);
265            self.iteration_marker = marker;
266        }
267
268        if self.state == PlaybackState::Running && self.reached_end(position) {
269            position = if self.playback_rate >= 0.0 {
270                self.timing.total_seconds()
271            } else {
272                0.0
273            };
274            self.anchor_position = position;
275            self.anchor_time = now;
276            self.state = PlaybackState::Finished;
277            events.finished = true;
278        }
279
280        let inside_active = position < active_end
281            || (position == active_end && self.state != PlaybackState::Finished);
282        let progress = if position < delay {
283            self.timing
284                .fill
285                .fills_before()
286                .then(|| self.directed_progress(0.0, 0))
287        } else if inside_active {
288            Some(self.progress_at((position - delay).max(0.0)))
289        } else {
290            self.timing
291                .fill
292                .fills_after()
293                .then(|| self.final_progress())
294        };
295        TimelineSample {
296            value: progress.map(|progress| self.keyframes.sample(progress)),
297            events,
298            state: self.state,
299        }
300    }
301
302    fn reached_end(&self, position: f64) -> bool {
303        const TIME_EPSILON: f64 = 1.0e-9;
304        if self.playback_rate >= 0.0 {
305            position + TIME_EPSILON >= self.timing.total_seconds()
306        } else {
307            position <= TIME_EPSILON
308        }
309    }
310
311    fn progress_at(&self, active_seconds: f64) -> f32 {
312        let duration = self.timing.duration.as_secs_f64();
313        let iterations = active_seconds / duration;
314        let index = iterations.floor() as u64;
315        let fraction = iterations.fract();
316        if active_seconds == self.timing.active_seconds() {
317            return self.final_progress();
318        }
319        self.directed_progress(fraction as f32, index)
320    }
321
322    fn final_progress(&self) -> f32 {
323        let iterations = match self.timing.iterations {
324            Iterations::Finite(iterations) => iterations,
325            Iterations::Infinite => return 1.0,
326        };
327        let fraction = iterations.fract();
328        let index = iterations.ceil().max(1.0) as u64 - 1;
329        self.directed_progress(
330            if fraction == 0.0 {
331                1.0
332            } else {
333                fraction as f32
334            },
335            index,
336        )
337    }
338
339    fn directed_progress(&self, progress: f32, iteration: u64) -> f32 {
340        let reverse = match self.timing.direction {
341            Direction::Normal => false,
342            Direction::Reverse => true,
343            Direction::Alternate => !iteration.is_multiple_of(2),
344            Direction::AlternateReverse => iteration.is_multiple_of(2),
345        };
346        if reverse { 1.0 - progress } else { progress }
347    }
348}