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