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
10pub trait Seconds {
12 fn seconds(self) -> f64;
14}
15
16impl Seconds for f64 {
17 fn seconds(self) -> f64 {
18 self
19 }
20}
21
22#[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 pub fn ease(mut self, ease: impl Ease + 'static) -> Self {
52 self.ease = Rc::new(ease);
53 self
54 }
55
56 pub fn duration(mut self, duration: impl Seconds) -> Self {
59 self.duration = duration.seconds().max(0.0);
60 self
61 }
62
63 pub fn from(mut self, value: T) -> Self {
65 self.from = Some(value);
66 self
67 }
68}
69
70pub(crate) fn progress(start: f64, duration: f64) -> Option<f64> {
73 runtime::with(|rt| {
74 let end = start + duration;
75 let now = rt.now();
76 if now + TIME_EPSILON >= end {
77 rt.task_time.set(end);
78 None
79 } else {
80 Some(((now - start) / duration).clamp(0.0, 1.0))
81 }
82 })
83}
84
85impl<T: Lerp + Clone + Unpin + 'static> Future for Tween<T> {
86 type Output = ();
87
88 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
89 let this = self.get_mut();
90 let start = match this.start {
91 Some(s) => s,
92 None => {
93 this.from.get_or_insert_with(|| this.signal.peek());
94 *this.start.insert(runtime::time())
95 }
96 };
97 match progress(start, this.duration) {
98 None => this.signal.set(this.target.clone()),
99 Some(t) => {
100 let from = this.from.as_ref().expect("captured on first poll");
101 this.signal.set(from.lerp(&this.target, this.ease.ease(t)));
102 return Poll::Pending;
103 }
104 }
105 Poll::Ready(())
106 }
107}