Skip to main content

oxframe_core/
tween.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::rc::Rc;
4use std::task::{Context, Poll};
5
6use crate::ease::presets;
7use crate::runtime;
8use crate::{Ease, Lerp, Signal, TIME_EPSILON};
9
10/// Animates a [`Signal`] towards a target. Created by [`Signal::to`].
11///
12/// The first poll captures the start value and the task time; every later
13/// poll is one frame. On completion the task time is advanced to exactly
14/// `start + duration`, not to the frame time, so chained tweens do not
15/// accumulate rounding drift.
16#[must_use = "a tween does nothing unless awaited"]
17pub struct Tween<T> {
18    signal: Signal<T>,
19    from: Option<T>,
20    target: T,
21    duration: f64,
22    ease: Rc<dyn Ease>,
23    start: Option<f64>,
24}
25
26impl<T: Lerp + Clone + 'static> Tween<T> {
27    pub(crate) fn new(signal: Signal<T>, target: T, duration: f64) -> Self {
28        Self {
29            signal,
30            from: None,
31            target,
32            duration: duration.max(0.0),
33            ease: Rc::new(presets::DEFAULT),
34            start: None,
35        }
36    }
37
38    /// Easing curve of this tween; default [`presets::DEFAULT`](crate::presets::DEFAULT).
39    pub fn ease(mut self, ease: impl Ease + 'static) -> Self {
40        self.ease = Rc::new(ease);
41        self
42    }
43
44    /// Starts from `value` instead of the signal's value at the first poll.
45    pub fn from(mut self, value: T) -> Self {
46        self.from = Some(value);
47        self
48    }
49}
50
51/// Progress of a timed operation that started at `start`; `None` once done
52/// (and then the task time is set to the exact end).
53pub(crate) fn progress(start: f64, duration: f64) -> Option<f64> {
54    runtime::with(|rt| {
55        let end = start + duration;
56        let now = rt.now();
57        if now + TIME_EPSILON >= end {
58            rt.task_time.set(end);
59            None
60        } else {
61            Some(((now - start) / duration).clamp(0.0, 1.0))
62        }
63    })
64}
65
66impl<T: Lerp + Clone + Unpin + 'static> Future for Tween<T> {
67    type Output = ();
68
69    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
70        let this = self.get_mut();
71        let start = match this.start {
72            Some(s) => s,
73            None => {
74                this.from.get_or_insert_with(|| this.signal.peek());
75                *this.start.insert(runtime::time())
76            }
77        };
78        match progress(start, this.duration) {
79            None => this.signal.set(this.target.clone()),
80            Some(t) => {
81                let from = this.from.as_ref().expect("captured on first poll");
82                this.signal.set(from.lerp(&this.target, this.ease.ease(t)));
83                return Poll::Pending;
84            }
85        }
86        Poll::Ready(())
87    }
88}