use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use crate::ease::presets;
use crate::runtime;
use crate::{Ease, Lerp, Signal, TIME_EPSILON};
pub trait Seconds {
fn seconds(self) -> f64;
}
impl Seconds for f64 {
fn seconds(self) -> f64 {
self
}
}
#[must_use = "a tween does nothing unless awaited"]
pub struct Tween<T> {
signal: Signal<T>,
from: Option<T>,
target: T,
duration: f64,
ease: Rc<dyn Ease>,
start: Option<f64>,
}
impl<T: Lerp + Clone + 'static> Tween<T> {
pub(crate) fn new(signal: Signal<T>, target: T, duration: f64) -> Self {
Self {
signal,
from: None,
target,
duration: duration.max(0.0),
ease: Rc::new(presets::DEFAULT),
start: None,
}
}
pub fn ease(mut self, ease: impl Ease + 'static) -> Self {
self.ease = Rc::new(ease);
self
}
pub fn duration(mut self, duration: impl Seconds) -> Self {
self.duration = duration.seconds().max(0.0);
self
}
pub fn from(mut self, value: T) -> Self {
self.from = Some(value);
self
}
}
pub(crate) fn progress(start: f64, duration: f64) -> Option<f64> {
runtime::with(|rt| {
let end = start + duration;
let now = rt.now();
if now + TIME_EPSILON >= end {
rt.task_time.set(end);
None
} else {
Some(((now - start) / duration).clamp(0.0, 1.0))
}
})
}
impl<T: Lerp + Clone + Unpin + 'static> Future for Tween<T> {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
let this = self.get_mut();
let start = match this.start {
Some(s) => s,
None => {
this.from.get_or_insert_with(|| this.signal.peek());
*this.start.insert(runtime::time())
}
};
match progress(start, this.duration) {
None => this.signal.set(this.target.clone()),
Some(t) => {
let from = this.from.as_ref().expect("captured on first poll");
this.signal.set(from.lerp(&this.target, this.ease.ease(t)));
return Poll::Pending;
}
}
Poll::Ready(())
}
}