use std::fmt; use std::rc::Rc; use std::cell::RefCell; use std::pin::Pin; use std::sync::{Arc, Weak, Mutex}; use std::task::{Poll, Waker, Context}; use futures_util::future::{ready, FutureExt}; use futures_signals::CancelableFutureHandle; use futures_signals::signal::{Signal, SignalExt, WaitFor, MutableSignal, Mutable}; use futures_signals::signal_vec::{SignalVec, VecDiff}; use discard::DiscardOnDrop; use pin_project::pin_project; use wasm_bindgen::JsCast; use wasm_bindgen::closure::Closure; use web_sys::window; use crate::operations::spawn_future; struct RafState { id: i32, closure: Closure, } // TODO generalize this so it works for any target, not just JS // TODO move this into gloo struct Raf { state: Rc>>, } impl Raf { fn new(mut callback: F) -> Self where F: FnMut(f64) + 'static { let state: Rc>> = Rc::new(RefCell::new(None)); fn schedule(callback: &Closure) -> i32 { window() .unwrap() .request_animation_frame(callback.as_ref().unchecked_ref()) .unwrap() } let closure = { let state = state.clone(); Closure::wrap(Box::new(move |time| { { let mut state = state.borrow_mut(); let state = state.as_mut().unwrap(); state.id = schedule(&state.closure); } callback(time); }) as Box) }; *state.borrow_mut() = Some(RafState { id: schedule(&closure), closure }); Self { state } } } impl Drop for Raf { fn drop(&mut self) { // The take is necessary in order to prevent an Rc leak let state = self.state.borrow_mut().take().unwrap(); window() .unwrap() .cancel_animation_frame(state.id) .unwrap(); } } struct TimestampsManager { raf: Option, // TODO make this more efficient states: Vec>>, } impl TimestampsManager { fn new() -> Self { Self { raf: None, states: vec![], } } } #[derive(Debug)] struct TimestampsState { changed: bool, value: Option, waker: Option, } impl TimestampsState { fn new() -> Self { Self { changed: true, value: None, waker: None, } } } #[must_use = "Signals do nothing unless polled"] #[derive(Debug)] pub struct Timestamps { state: Arc>, } impl Timestamps { fn new() -> Self { Self { state: Arc::new(Mutex::new(TimestampsState::new())), } } } impl Signal for Timestamps { type Item = Option; // TODO implement Poll::Ready(None) fn poll_change(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let mut lock = self.state.lock().unwrap(); if lock.changed { lock.changed = false; Poll::Ready(Some(lock.value)) } else { lock.waker = Some(cx.waker().clone()); Poll::Pending } } } // TODO somehow share this safely between threads ? thread_local! { static TIMESTAMPS_MANAGER: Rc> = Rc::new(RefCell::new(TimestampsManager::new())); } pub fn timestamps() -> Timestamps { TIMESTAMPS_MANAGER.with(|timestamps_manager| { let timestamps = Timestamps::new(); { let mut lock = timestamps_manager.borrow_mut(); lock.states.push(Arc::downgrade(×tamps.state)); if let None = lock.raf { let timestamps_manager = timestamps_manager.clone(); lock.raf = Some(Raf::new(move |time| { let mut lock = timestamps_manager.borrow_mut(); lock.states.retain(|state| { if let Some(state) = state.upgrade() { let mut lock = state.lock().unwrap(); lock.changed = true; lock.value = Some(time); if let Some(waker) = lock.waker.take() { drop(lock); waker.wake(); } true } else { false } }); if lock.states.len() == 0 { lock.raf = None; // TODO is this a good idea ? lock.states = vec![]; } })); } } timestamps }) } pub trait AnimatedSignalVec: SignalVec { type Animation; fn animated_map(self, duration: f64, f: F) -> AnimatedMap where F: FnMut(Self::Item, Self::Animation) -> A, Self: Sized; } impl AnimatedSignalVec for S { type Animation = AnimatedMapBroadcaster; #[inline] fn animated_map(self, duration: f64, f: F) -> AnimatedMap where F: FnMut(Self::Item, Self::Animation) -> A { AnimatedMap { duration: duration, animations: vec![], signal: Some(self), callback: f, } } } #[derive(Debug)] pub struct AnimatedMapBroadcaster(MutableAnimation); impl AnimatedMapBroadcaster { // TODO it should return a custom type #[inline] pub fn signal(&self) -> MutableAnimationSignal { self.0.signal() } } #[derive(Debug)] struct AnimatedMapState { animation: MutableAnimation, removing: Option>, } // TODO move this into signals crate and also generalize it to work with any future, not just animations #[pin_project(project = AnimatedMapProj)] #[derive(Debug)] pub struct AnimatedMap { duration: f64, animations: Vec, #[pin] signal: Option, callback: B, } impl AnimatedMap where S: SignalVec, F: FnMut(S::Item, AnimatedMapBroadcaster) -> A { fn animated_state(duration: f64) -> AnimatedMapState { let state = AnimatedMapState { animation: MutableAnimation::new(duration), removing: None, }; state.animation.animate_to(Percentage::new_unchecked(1.0)); state } fn remove_index(animations: &mut Vec, index: usize) -> Poll>> { if index == (animations.len() - 1) { animations.pop(); Poll::Ready(Some(VecDiff::Pop {})) } else { animations.remove(index); Poll::Ready(Some(VecDiff::RemoveAt { index })) } } fn should_remove(animations: &mut Vec, cx: &mut Context, index: usize) -> bool { let state = &mut animations[index]; state.animation.animate_to(Percentage::new_unchecked(0.0)); let mut future = state.animation.signal().wait_for(Percentage::new_unchecked(0.0)); if future.poll_unpin(cx).is_ready() { true } else { state.removing = Some(future); false } } fn find_index(animations: &Vec, parent_index: usize) -> Option { let mut seen = 0; // TODO is there a combinator that can simplify this ? animations.iter().position(|state| { if state.removing.is_none() { if seen == parent_index { true } else { seen += 1; false } } else { false } }) } #[inline] fn find_last_index(animations: &Vec) -> Option { animations.iter().rposition(|state| state.removing.is_none()) } } impl SignalVec for AnimatedMap where S: SignalVec, F: FnMut(S::Item, AnimatedMapBroadcaster) -> A { type Item = A; // TODO this can probably be implemented more efficiently fn poll_vec_change(self: Pin<&mut Self>, cx: &mut Context) -> Poll>> { let mut is_done = true; let AnimatedMapProj { mut animations, mut signal, callback, duration, .. } = self.project(); // TODO is this loop correct ? while let Some(result) = signal.as_mut().as_pin_mut().map(|signal| signal.poll_vec_change(cx)) { match result { Poll::Ready(Some(change)) => return match change { // TODO maybe it should play remove / insert animations for this ? VecDiff::Replace { values } => { *animations = Vec::with_capacity(values.len()); Poll::Ready(Some(VecDiff::Replace { values: values.into_iter().map(|value| { let state = AnimatedMapState { animation: MutableAnimation::new_with_initial(*duration, Percentage::new_unchecked(1.0)), removing: None, }; let value = callback(value, AnimatedMapBroadcaster(state.animation.raw_clone())); animations.push(state); value }).collect() })) }, VecDiff::InsertAt { index, value } => { let index = Self::find_index(&animations, index).unwrap_or_else(|| animations.len()); let state = Self::animated_state(*duration); let value = callback(value, AnimatedMapBroadcaster(state.animation.raw_clone())); animations.insert(index, state); Poll::Ready(Some(VecDiff::InsertAt { index, value })) }, VecDiff::Push { value } => { let state = Self::animated_state(*duration); let value = callback(value, AnimatedMapBroadcaster(state.animation.raw_clone())); animations.push(state); Poll::Ready(Some(VecDiff::Push { value })) }, VecDiff::UpdateAt { index, value } => { let index = Self::find_index(&animations, index).unwrap(); let state = { let state = &animations[index]; AnimatedMapBroadcaster(state.animation.raw_clone()) }; let value = callback(value, state); Poll::Ready(Some(VecDiff::UpdateAt { index, value })) }, // TODO test this // TODO should this be treated as a removal + insertion ? VecDiff::Move { old_index, new_index } => { let old_index = Self::find_index(&animations, old_index).unwrap(); let state = animations.remove(old_index); let new_index = Self::find_index(&animations, new_index).unwrap_or_else(|| animations.len()); animations.insert(new_index, state); Poll::Ready(Some(VecDiff::Move { old_index, new_index })) }, VecDiff::RemoveAt { index } => { let index = Self::find_index(&animations, index).unwrap(); if Self::should_remove(&mut animations, cx, index) { Self::remove_index(&mut animations, index) } else { continue; } }, VecDiff::Pop {} => { let index = Self::find_last_index(&animations).unwrap(); if Self::should_remove(&mut animations, cx, index) { Self::remove_index(&mut animations, index) } else { continue; } }, // TODO maybe it should play remove animation for this ? VecDiff::Clear {} => { animations.clear(); Poll::Ready(Some(VecDiff::Clear {})) }, }, Poll::Ready(None) => { signal.set(None); break; }, Poll::Pending => { is_done = false; break; }, } } let mut is_removing = false; // TODO make this more efficient (e.g. using a similar strategy as FuturesUnordered) // This uses rposition so that way it will return VecDiff::Pop in more situations let index = animations.iter_mut().rposition(|state| { if let Some(ref mut future) = state.removing { is_removing = true; future.poll_unpin(cx).is_ready() } else { false } }); match index { Some(index) => { Self::remove_index(&mut animations, index) }, None => if is_done && !is_removing { Poll::Ready(None) } else { Poll::Pending }, } } } #[derive(Debug, Clone, Copy, PartialEq)] pub struct Percentage(f64); impl Percentage { pub const START: Percentage = Percentage(0.0); pub const END: Percentage = Percentage(1.0); #[inline] pub fn new(input: f64) -> Self { debug_assert!(input >= 0.0 && input <= 1.0); Self::new_unchecked(input) } #[inline] pub fn new_unchecked(input: f64) -> Self { Percentage(input) } #[inline] pub fn map(self, f: F) -> Self where F: FnOnce(f64) -> f64 { Self::new(f(self.0)) } #[inline] pub fn map_unchecked(self, f: F) -> Self where F: FnOnce(f64) -> f64 { Self::new_unchecked(f(self.0)) } #[inline] pub fn invert(self) -> Self { // TODO use new instead ? Self::new_unchecked(1.0 - self.0) } #[inline] pub fn range_inclusive(&self, low: f64, high: f64) -> f64 { range_inclusive(self.0, low, high) } // TODO figure out better name #[inline] pub fn into_f64(self) -> f64 { self.0 } pub fn none_if(self, percentage: f64) -> Option { if self.0 == percentage { None } else { Some(self) } } } #[inline] fn range_inclusive(percentage: f64, low: f64, high: f64) -> f64 { low + (percentage * (high - low)) } /*pub struct MutableTimestamps { callback: Arc, animating: Mutex>>, } impl MutableTimestamps where F: FnMut(f64) { pub fn new(callback: F) -> Self { Self { callback: Arc::new(callback), animating: Mutex::new(None), } } pub fn stop(&self) { let mut lock = self.animating.lock().unwrap(); *lock = None; } pub fn start(&self) { let mut lock = self.animating.lock().unwrap(); if let None = animating { let callback = self.callback.clone(); let mut starting_time = None; *animating = Some(OnTimestampDiff::new(move |value| callback(value))); } } }*/ pub fn timestamps_absolute_difference() -> impl Signal> { let mut starting_time = None; timestamps().map(move |current_time| { current_time.map(|current_time| { let starting_time = *starting_time.get_or_insert(current_time); current_time - starting_time }) }) } pub fn timestamps_difference() -> impl Signal> { let mut previous_time = None; timestamps().map(move |current_time| { let diff = current_time.map(|current_time| { previous_time.map(|previous_time| current_time - previous_time).unwrap_or(0.0) }); previous_time = current_time; diff }) } pub struct OnTimestampDiff(DiscardOnDrop); impl OnTimestampDiff { pub fn new(mut callback: F) -> Self where F: FnMut(f64) + 'static { OnTimestampDiff(spawn_future( timestamps_absolute_difference() .for_each(move |diff| { if let Some(diff) = diff { callback(diff); } ready(()) }) )) } } impl fmt::Debug for OnTimestampDiff { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_tuple("OnTimestampDiff") .finish() } } #[derive(Debug)] pub struct MutableAnimationSignal(MutableSignal); impl Signal for MutableAnimationSignal { type Item = Percentage; #[inline] fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { self.0.poll_change_unpin(cx) } } // TODO verify that this is Sync and Send struct MutableAnimationState { playing: bool, duration: f64, end: Percentage, _animating: Option, } struct MutableAnimationInner { state: Mutex, value: Mutable, } // TODO deref to ReadOnlyMutable ? // TODO provide read_only() method ? // TODO add `is_playing` method ? #[derive(Clone)] pub struct MutableAnimation { inner: Arc, } impl fmt::Debug for MutableAnimation { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let state = self.inner.state.lock().unwrap(); fmt.debug_struct("MutableAnimation") .field("playing", &state.playing) .field("duration", &state.duration) .field("current", &self.inner.value.get()) .field("end", &state.end) .finish() } } impl MutableAnimation { #[inline] pub fn new_with_initial(duration: f64, initial: Percentage) -> Self { debug_assert!(duration >= 0.0); Self { inner: Arc::new(MutableAnimationInner { state: Mutex::new(MutableAnimationState { playing: true, duration: duration, end: initial, _animating: None, }), value: Mutable::new(initial), }), } } #[inline] pub fn new(duration: f64) -> Self { Self::new_with_initial(duration, Percentage::new_unchecked(0.0)) } #[inline] fn raw_clone(&self) -> Self { Self { inner: self.inner.clone(), } } #[inline] fn stop_animating(lock: &mut MutableAnimationState) { lock._animating = None; } fn start_animating(&self, lock: &mut MutableAnimationState) { if lock.playing { // TODO use Copy constraint to make value.get() faster ? let start: f64 = self.inner.value.get().into_f64(); let end: f64 = lock.end.into_f64(); if start != end { if lock.duration > 0.0 { let duration = (end - start).abs() * lock.duration; let state = self.raw_clone(); lock._animating = Some(OnTimestampDiff::new(move |diff| { let diff = diff / duration; // TODO test the performance of set_neq if diff >= 1.0 { { let mut lock = state.inner.state.lock().unwrap(); Self::stop_animating(&mut lock); } state.inner.value.set_neq(Percentage::new_unchecked(end)); } else { state.inner.value.set_neq(Percentage::new_unchecked(range_inclusive(diff, start, end))); } })); } else { Self::stop_animating(lock); self.inner.value.set_neq(Percentage::new_unchecked(end)); } } else { // TODO is this necessary ? Self::stop_animating(lock); } } } pub fn set_duration(&self, duration: f64) { debug_assert!(duration >= 0.0); let mut lock = self.inner.state.lock().unwrap(); if lock.duration != duration { lock.duration = duration; self.start_animating(&mut lock); } } #[inline] pub fn pause(&self) { let mut lock = self.inner.state.lock().unwrap(); if lock.playing { lock.playing = false; Self::stop_animating(&mut lock); } } #[inline] pub fn play(&self) { let mut lock = self.inner.state.lock().unwrap(); if !lock.playing { lock.playing = true; self.start_animating(&mut lock); } } fn _jump_to(mut lock: &mut MutableAnimationState, mutable: &Mutable, end: Percentage) { Self::stop_animating(&mut lock); lock.end = end; mutable.set_neq(end); } pub fn jump_to(&self, end: Percentage) { let mut lock = self.inner.state.lock().unwrap(); Self::_jump_to(&mut lock, &self.inner.value, end); } pub fn animate_to(&self, end: Percentage) { let mut lock = self.inner.state.lock().unwrap(); if lock.end != end { if lock.duration <= 0.0 { Self::_jump_to(&mut lock, &self.inner.value, end); } else { lock.end = end; self.start_animating(&mut lock); } } } #[inline] pub fn signal(&self) -> MutableAnimationSignal { MutableAnimationSignal(self.inner.value.signal()) } #[inline] pub fn current_percentage(&self) -> Percentage { self.inner.value.get() } } pub mod easing { use super::Percentage; // TODO should this use map rather than map_unchecked ? #[inline] pub fn powi(p: Percentage, n: i32) -> Percentage { p.map_unchecked(|p| p.powi(n)) } #[inline] pub fn cubic(p: Percentage) -> Percentage { powi(p, 3) } #[inline] pub fn out(p: Percentage, f: F) -> Percentage where F: FnOnce(Percentage) -> Percentage { f(p.invert()).invert() } pub fn in_out(p: Percentage, f: F) -> Percentage where F: FnOnce(Percentage) -> Percentage { p.map_unchecked(|p| { if p <= 0.5 { f(Percentage::new_unchecked(p * 2.0)).into_f64() / 2.0 } else { 1.0 - (f(Percentage::new_unchecked((1.0 - p) * 2.0)).into_f64() / 2.0) } }) } /*pub struct Point { pub x: f64, pub y: f64, }*/ /*impl Point { fn range_inclusive(percentage: f64, from: &Self, to: &Self) -> Self { Point { x: range_inclusive(percentage, from.x, to.x), y: range_inclusive(percentage, from.y, to.y), } } }*/ /*#[inline] fn get_values(start: f64, end: f64) -> (f64, f64, f64) { let start = 3.0 * start; let end = 3.0 * end; let a = 1.0 - end + start; let b = end - (2.0 * start); (a, b, start) } fn interpolate(p: f64, start: f64, end: f64) -> f64 { let (a, b, c) = get_values(start, end); ((a * p + b) * p + c) * p } fn get_slope(p: f64, start: f64, end: f64) -> f64 { let (a, b, c) = get_values(start, end); (3.0 * a * p * p) + (2.0 * b * p) + c }*/ const EPSILON: f64 = 1e-6; pub struct CubicBezier { ax: f64, bx: f64, cx: f64, ay: f64, by: f64, cy: f64, } impl CubicBezier { pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self { assert!(x1 >= 0.0 && x1 <= 1.0); assert!(y1 >= 0.0 && y1 <= 1.0); assert!(x2 >= 0.0 && x2 <= 1.0); assert!(y2 >= 0.0 && y2 <= 1.0); let cx = 3.0 * x1; let bx = 3.0 * (x2 - x1) - cx; let ax = 1.0 - cx - bx; let cy = 3.0 * y1; let by = 3.0 * (y2 - y1) - cy; let ay = 1.0 - cy - by; Self { ax, bx, cx, ay, by, cy } } /*fn values(p: f64) -> (f64, f64, f64, f64) { let t2 = p * p; let one_t = 1.0 - p; let one_t2 = one_t * one_t; ( one_t2 * one_t, 3.0 * one_t2 * p, 3.0 * one_t * t2, t2 * p, ) }*/ pub fn easing(&self, p: Percentage) -> Percentage { // TODO is unchecked okay ? p.map_unchecked(|p| { if p == 0.0 { 0.0 } else if p == 1.0 { 1.0 } else { self.y(self.get_t_for_x(p)) } }) } /*pub fn point(&self, p: Percentage) -> Point { let p = p.into_f64(); Point { x: self.x(p), y: self.y(p), } }*/ fn x(&self, p: f64) -> f64 { ((self.ax * p + self.bx) * p + self.cx) * p /*let p1 = range_inclusive(p, self.start.x, self.ctrl1.x); let p2 = range_inclusive(p, self.ctrl1.x, self.ctrl2.x); let p3 = range_inclusive(p, self.ctrl2.x, self.end.x); let p4 = range_inclusive(p, p1, p2); let p5 = range_inclusive(p, p2, p3); range_inclusive(p, p4, p5)*/ } fn x_derivative(&self, p: f64) -> f64 { (3.0 * self.ax * p + 2.0 * self.bx) * p + self.cx } /*fn x(&self, values: (f64, f64, f64, f64)) -> f64 { self.start.x * values.0 + self.ctrl1.x * values.1 + self.ctrl2.x * values.2 + self.end.x * values.3 }*/ fn y(&self, p: f64) -> f64 { ((self.ay * p + self.by) * p + self.cy) * p /*let p1 = range_inclusive(p, self.start.y, self.ctrl1.y); let p2 = range_inclusive(p, self.ctrl1.y, self.ctrl2.y); let p3 = range_inclusive(p, self.ctrl2.y, self.end.y); let p4 = range_inclusive(p, p1, p2); let p5 = range_inclusive(p, p2, p3); range_inclusive(p, p4, p5)*/ } /*fn y(&self, values: (f64, f64, f64, f64)) -> f64 { self.start.y * values.0 + self.ctrl1.y * values.1 + self.ctrl2.y * values.2 + self.end.y * values.3 }*/ fn bisect(&self, x: f64) -> f64 { let mut start = 0.0; let mut end = 1.0; let mut t = x; debug_assert!(t >= start); debug_assert!(t <= end); while start < end { let x = self.x(t) - x; if x.abs() < EPSILON { return t; } if x > 0.0 { end = t; } else { start = t; } t = (end - start) * 0.5 + start; } t } fn get_t_for_x(&self, x: f64) -> f64 { let mut t = x; // Use Newton's method first, because it's faster for _ in 0..8 { let x = self.x(t) - x; if x.abs() < EPSILON { return t; } let d = self.x_derivative(t); if d.abs() < EPSILON { break; } t -= x / d; } // No solution found, bisect instead self.bisect(x) } /*fn get_t_for_x(&self, x: f64) -> f64 { const NEWTON_ITERATIONS: usize = 100; let mut t = x; for _ in 0..NEWTON_ITERATIONS { let slope = get_slope(t, self.ctrl1.x, self.ctrl2.x); if slope == 0.0 { break; } else { let new_x = self.x(t) - x; t -= new_x / slope; } } t }*/ /*fn get_t_for_x(&self, x: f64) -> f64 { const MAX_ITERATIONS: usize = 1000; //const TOLERANCE: f64 = 0.0001; const TOLERANCE: f64 = 0.0000000001; let mut start = 0.0; let mut end = 1.0; let mut iterations = 0; loop { let t = (end - start) / 2.0 + start; let new_x = self.x(t) - x; iterations += 1; if iterations == MAX_ITERATIONS || new_x.abs() <= TOLERANCE { web_sys::console::log_1(&wasm_bindgen::JsValue::from(&format!("{} {} {} {} {}", iterations, start, end, t, new_x))); return t; } else if new_x > 0.0 { end = t; } else { start = t; } } }*/ } } /*cubic_bezier(t, Percentage::new(1.0), Percentage::new(0.0), Percentage::new(0.66), Percentage::new(0.66), )*/