Skip to main content

ferroui_base/rendering/composition/server/
server_compositor_animations.rs

1use super::{IServerClockItem, ServerObjectAnimations};
2use std::cell::{Cell, RefCell};
3use std::collections::VecDeque;
4use std::rc::Rc;
5use std::time::Duration;
6
7/// The scaling of the clock of the animations by the global playback rate
8/// (WinUI `Compositor.GlobalPlaybackRate`): from the compositor time
9/// `base_real` on, the animation time runs at `rate` from `base_scaled`
10/// (seconds).
11#[derive(Clone, Copy, Debug)]
12struct AnimationTimeScale {
13    rate: f64,
14    base_real: Duration,
15    base_scaled: f64,
16}
17
18/// The clock items and the dirty animated objects of a server compositor.
19#[derive(Default)]
20pub struct ServerCompositorAnimations {
21    clock_items: RefCell<Vec<Rc<dyn IServerClockItem>>>,
22    clock_items_to_update: RefCell<Vec<Rc<dyn IServerClockItem>>>,
23    dirty_animated_object_queue: RefCell<VecDeque<Rc<ServerObjectAnimations>>>,
24    /// `None` until the global playback rate is set: the animation time is
25    /// the compositor time.
26    time_scale: Cell<Option<AnimationTimeScale>>,
27}
28
29fn same_item(a: &Rc<dyn IServerClockItem>, b: &Rc<dyn IServerClockItem>) -> bool {
30    std::ptr::addr_eq(Rc::as_ptr(a), Rc::as_ptr(b))
31}
32
33impl ServerCompositorAnimations {
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    pub fn add_to_clock(&self, item: Rc<dyn IServerClockItem>) {
39        let mut items = self.clock_items.borrow_mut();
40        if !items.iter().any(|i| same_item(i, &item)) {
41            items.push(item);
42        }
43    }
44
45    pub fn remove_from_clock(&self, item: &Rc<dyn IServerClockItem>) {
46        self.clock_items.borrow_mut().retain(|i| !same_item(i, item));
47    }
48
49    pub fn process(&self) {
50        let mut to_update = std::mem::take(&mut *self.clock_items_to_update.borrow_mut());
51        to_update.extend(self.clock_items.borrow().iter().cloned());
52        for animation in &to_update {
53            animation.on_tick();
54        }
55        to_update.clear();
56        *self.clock_items_to_update.borrow_mut() = to_update;
57
58        loop {
59            let Some(animation) = self.dirty_animated_object_queue.borrow_mut().pop_front() else { break };
60            animation.evaluate_animations();
61        }
62    }
63
64    pub fn need_next_tick(&self) -> bool {
65        !self.clock_items.borrow().is_empty()
66    }
67
68    /// The time of the animations at the compositor time `real`: the
69    /// compositor time, scaled by the global playback rate from the moment
70    /// the rate was set. Animations are started and evaluated at this time,
71    /// so a rate of 0.5 runs every animation at half speed.
72    pub fn animation_time(&self, real: Duration) -> Duration {
73        let Some(scale) = self.time_scale.get() else { return real };
74        let scaled = scale.base_scaled + (real.as_secs_f64() - scale.base_real.as_secs_f64()) * scale.rate;
75        Duration::from_secs_f64(if scaled.is_finite() { scaled.max(0.0) } else { 0.0 })
76    }
77
78    /// The global playback rate (1 until it is set).
79    pub fn global_playback_rate(&self) -> f64 {
80        self.time_scale.get().map_or(1.0, |scale| scale.rate)
81    }
82
83    /// Sets the global playback rate from the compositor time `real_now` on:
84    /// the animation time goes on from where it is at that moment.
85    pub fn set_global_playback_rate(&self, rate: f64, real_now: Duration) {
86        let rate = if rate.is_finite() { rate.max(0.0) } else { 1.0 };
87        let base_scaled = self.animation_time(real_now).as_secs_f64();
88        self.time_scale.set(Some(AnimationTimeScale { rate, base_real: real_now, base_scaled }));
89    }
90
91    pub fn add_dirty_animated_object(&self, obj: Rc<ServerObjectAnimations>) {
92        let mut queue = self.dirty_animated_object_queue.borrow_mut();
93        if !queue.iter().any(|o| Rc::ptr_eq(o, &obj)) {
94            queue.push_back(obj);
95        }
96    }
97}