Skip to main content

ferroui_base/animation/
clock_base.rs

1use crate::animation::{PlayState, TimeSpan};
2use crate::reactive::{IDisposable, IObservable, IObserver, LightweightSubject};
3use std::cell::Cell;
4use std::rc::Rc;
5
6/// The shared implementation of clocks that are driven by another time
7/// source: converts the source's time to the time elapsed since this clock's
8/// first pulse, not counting the time spent paused.
9///
10/// The owning clock forwards its source's ticks to [`pulse`](Self::pulse)
11/// and supplies what to do when the clock stops.
12pub struct ClockBase {
13    observable: LightweightSubject<TimeSpan>,
14    previous_time: Cell<Option<TimeSpan>>,
15    internal_time: Cell<TimeSpan>,
16    play_state: Cell<PlayState>,
17}
18
19impl Default for ClockBase {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl ClockBase {
26    pub fn new() -> Self {
27        Self {
28            observable: LightweightSubject::new(),
29            previous_time: Cell::new(None),
30            internal_time: Cell::new(TimeSpan::ZERO),
31            play_state: Cell::new(PlayState::Run),
32        }
33    }
34
35    /// Whether anything is subscribed to the clock.
36    pub fn has_subscriptions(&self) -> bool {
37        self.observable.has_observers()
38    }
39
40    /// The playback state of the clock.
41    #[inline]
42    pub fn play_state(&self) -> PlayState {
43        self.play_state.get()
44    }
45
46    /// Sets the playback state of the clock.
47    #[inline]
48    pub fn set_play_state(&self, value: PlayState) {
49        self.play_state.set(value)
50    }
51
52    /// Advances the clock to `system_time` and notifies the subscribers.
53    /// `stop` runs afterwards if the clock has been stopped.
54    pub fn pulse(&self, system_time: TimeSpan, stop: impl FnOnce()) {
55        match self.previous_time.get() {
56            None => {
57                self.previous_time.set(Some(system_time));
58                self.internal_time.set(TimeSpan::ZERO);
59            }
60            Some(previous_time) => {
61                if self.play_state.get() == PlayState::Pause {
62                    self.previous_time.set(Some(system_time));
63                    return;
64                }
65                let delta = system_time - previous_time;
66                self.internal_time.set(self.internal_time.get() + delta);
67                self.previous_time.set(Some(system_time));
68            }
69        }
70
71        self.observable.on_next(self.internal_time.get());
72
73        if self.play_state.get() == PlayState::Stop {
74            stop();
75        }
76    }
77
78    /// Subscribes to the clock's time.
79    pub fn subscribe(&self, observer: Rc<dyn IObserver<TimeSpan>>) -> Rc<dyn IDisposable> {
80        self.observable.subscribe(observer)
81    }
82}