Skip to main content

ferroui_base/animation/
animation_task.rs

1//! The completion of an animation run.
2//!
3//! Animations are driven by clock ticks on the UI thread, so the result of
4//! running one is completed from within a tick (or a cancellation) and needs
5//! no executor: it can be polled as a [`Future`], queried, or given
6//! continuations, which run synchronously at completion.
7
8use crate::animation::AnimationError;
9use std::cell::RefCell;
10use std::future::Future;
11use std::pin::Pin;
12use std::rc::Rc;
13use std::task::{Context, Poll, Waker};
14
15#[derive(Default)]
16struct State {
17    result: Option<Result<(), AnimationError>>,
18    wakers: Vec<Waker>,
19    continuations: Vec<Box<dyn FnOnce()>>,
20}
21
22/// The pending or completed result of running an animation or a page
23/// transition.
24///
25/// It completes with `Ok` when the animation has ended or was cancelled,
26/// and with an error when the animation could not be run.
27#[derive(Clone, Default)]
28pub struct AnimationTask {
29    state: Rc<RefCell<State>>,
30}
31
32impl AnimationTask {
33    /// A task that has not completed yet.
34    pub(crate) fn pending() -> Self {
35        Self::default()
36    }
37
38    /// A successfully completed task.
39    pub fn completed() -> Self {
40        let task = Self::default();
41        task.state.borrow_mut().result = Some(Ok(()));
42        task
43    }
44
45    /// Whether the task has completed, successfully or not.
46    pub fn is_completed(&self) -> bool {
47        self.state.borrow().result.is_some()
48    }
49
50    /// The result of the task; `None` while it is pending.
51    pub fn result(&self) -> Option<Result<(), AnimationError>> {
52        self.state.borrow().result.clone()
53    }
54
55    /// Runs `continuation` when the task completes; immediately if it has
56    /// completed already.
57    pub fn on_completed(&self, continuation: impl FnOnce() + 'static) {
58        if self.is_completed() {
59            continuation();
60        } else {
61            self.state.borrow_mut().continuations.push(Box::new(continuation));
62        }
63    }
64
65    /// Completes the task unless it has completed already. Returns whether
66    /// it did.
67    pub(crate) fn try_set(&self, result: Result<(), AnimationError>) -> bool {
68        let (wakers, continuations) = {
69            let mut state = self.state.borrow_mut();
70            if state.result.is_some() {
71                return false;
72            }
73            state.result = Some(result);
74            (std::mem::take(&mut state.wakers), std::mem::take(&mut state.continuations))
75        };
76        for continuation in continuations {
77            continuation();
78        }
79        for waker in wakers {
80            waker.wake();
81        }
82        true
83    }
84
85    pub(crate) fn try_set_result(&self) -> bool {
86        self.try_set(Ok(()))
87    }
88
89    /// A task that completes when all of `tasks` have completed: with the
90    /// first error among them, if any.
91    pub fn when_all(tasks: impl IntoIterator<Item = AnimationTask>) -> AnimationTask {
92        let tasks: Vec<AnimationTask> = tasks.into_iter().collect();
93        let all = AnimationTask::pending();
94        let complete = {
95            let all = all.clone();
96            let tasks = tasks.clone();
97            Rc::new(move || {
98                if tasks.iter().all(AnimationTask::is_completed) {
99                    let error = tasks.iter().find_map(|task| task.result().and_then(Result::err));
100                    all.try_set(match error {
101                        Some(error) => Err(error),
102                        None => Ok(()),
103                    });
104                }
105            })
106        };
107        if tasks.is_empty() {
108            all.try_set_result();
109        }
110        for task in &tasks {
111            let complete = complete.clone();
112            task.on_completed(move || complete());
113        }
114        all
115    }
116}
117
118impl Future for AnimationTask {
119    type Output = Result<(), AnimationError>;
120
121    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
122        let mut state = self.state.borrow_mut();
123        match &state.result {
124            Some(result) => Poll::Ready(result.clone()),
125            None => {
126                if !state.wakers.iter().any(|waker| waker.will_wake(cx.waker())) {
127                    state.wakers.push(cx.waker().clone());
128                }
129                Poll::Pending
130            }
131        }
132    }
133}