2018-03-15 06:54:18 -04:00
|
|
|
use self::unsync::MutableAnimation;
|
2018-03-16 02:50:21 -04:00
|
|
|
use std::sync::{Arc, Weak, Mutex};
|
|
|
|
use futures::{Async, task};
|
2018-03-15 06:54:18 -04:00
|
|
|
use futures::future::Future;
|
2018-03-16 02:50:21 -04:00
|
|
|
use futures::task::Task;
|
|
|
|
use signals::signal::{Signal, State, WaitFor};
|
2018-03-15 06:54:18 -04:00
|
|
|
use signals::signal::unsync::MutableSignal;
|
|
|
|
use signals::signal_vec::{SignalVec, VecChange};
|
|
|
|
use stdweb::Value;
|
|
|
|
|
|
|
|
|
2018-03-12 20:09:44 -04:00
|
|
|
// TODO generalize this so it works for any target, not just JS
|
|
|
|
struct Raf(Value);
|
|
|
|
|
|
|
|
impl Raf {
|
|
|
|
#[inline]
|
2018-03-16 02:50:21 -04:00
|
|
|
fn new<F>(callback: F) -> Self where F: FnMut(f64) + 'static {
|
2018-03-12 20:09:44 -04:00
|
|
|
Raf(js!(
|
|
|
|
var callback = @{callback};
|
|
|
|
|
|
|
|
function loop(time) {
|
2018-03-16 02:50:21 -04:00
|
|
|
value.id = requestAnimationFrame(loop);
|
|
|
|
callback(time);
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var value = {
|
|
|
|
callback: callback,
|
|
|
|
id: requestAnimationFrame(loop)
|
|
|
|
};
|
|
|
|
|
|
|
|
return value;
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for Raf {
|
|
|
|
#[inline]
|
|
|
|
fn drop(&mut self) {
|
|
|
|
js! { @(no_return)
|
2018-03-15 06:54:18 -04:00
|
|
|
var self = @{&self.0};
|
2018-03-16 02:50:21 -04:00
|
|
|
cancelAnimationFrame(self.id);
|
|
|
|
self.callback.drop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-12 20:09:44 -04:00
|
|
|
|
2018-03-16 02:50:21 -04:00
|
|
|
|
|
|
|
struct TimestampsGlobal {
|
|
|
|
raf: Option<Raf>,
|
|
|
|
// TODO make this more efficient
|
|
|
|
states: Vec<Weak<Mutex<TimestampsState>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct TimestampsState {
|
|
|
|
first: bool,
|
|
|
|
value: Option<f64>,
|
|
|
|
task: Option<Task>,
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO make this more efficient
|
|
|
|
pub struct Timestamps(Arc<Mutex<TimestampsState>>);
|
|
|
|
|
|
|
|
impl Signal for Timestamps {
|
|
|
|
type Item = Option<f64>;
|
|
|
|
|
|
|
|
fn poll(&mut self) -> State<Self::Item> {
|
|
|
|
let mut lock = self.0.lock().unwrap();
|
|
|
|
|
|
|
|
let value = lock.value.take();
|
|
|
|
|
|
|
|
if lock.first {
|
|
|
|
lock.first = false;
|
|
|
|
State::Changed(value)
|
|
|
|
|
|
|
|
} else if value.is_some() {
|
|
|
|
State::Changed(value)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
lock.task = Some(task::current());
|
|
|
|
State::NotChanged
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn timestamps() -> Timestamps {
|
|
|
|
lazy_static! {
|
|
|
|
static ref TIMESTAMPS: Mutex<TimestampsGlobal> = Mutex::new(TimestampsGlobal {
|
|
|
|
raf: None,
|
|
|
|
states: vec![],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
let state = Arc::new(Mutex::new(TimestampsState {
|
|
|
|
first: true,
|
|
|
|
value: None,
|
|
|
|
task: None,
|
|
|
|
}));
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut lock = TIMESTAMPS.lock().unwrap();
|
|
|
|
|
|
|
|
lock.states.push(Arc::downgrade(&state));
|
|
|
|
|
|
|
|
if let None = lock.raf {
|
|
|
|
lock.raf = Some(Raf::new(|time| {
|
|
|
|
// TODO is it possible for this to deadlock or error or whatever ?
|
|
|
|
let mut lock = TIMESTAMPS.lock().unwrap();
|
|
|
|
|
|
|
|
lock.states.retain(|state| {
|
|
|
|
if let Some(state) = state.upgrade() {
|
|
|
|
let mut lock = state.lock().unwrap();
|
|
|
|
|
|
|
|
// TODO it should always poll the most recent time, so this needs to be a has_changed boolean instead
|
|
|
|
lock.value = Some(time);
|
|
|
|
|
|
|
|
if let Some(task) = lock.task.take() {
|
|
|
|
drop(lock);
|
|
|
|
task.notify();
|
|
|
|
}
|
|
|
|
|
|
|
|
true
|
|
|
|
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if lock.states.len() == 0 {
|
|
|
|
lock.raf = None;
|
|
|
|
lock.states = vec![];
|
|
|
|
}
|
|
|
|
}));
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
2018-03-16 02:50:21 -04:00
|
|
|
|
|
|
|
Timestamps(state)
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
pub trait AnimatedSignalVec: SignalVec {
|
|
|
|
type AnimatedSignal: Signal<Item = Percentage>;
|
|
|
|
|
|
|
|
fn animated_map<A, F>(self, duration: f64, f: F) -> AnimatedMap<Self, F>
|
|
|
|
where F: FnMut(Self::Item, Self::AnimatedSignal) -> A,
|
|
|
|
Self: Sized;
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: SignalVec> AnimatedSignalVec for S {
|
2018-03-15 06:54:18 -04:00
|
|
|
type AnimatedSignal = MutableSignal<Percentage>;
|
|
|
|
|
2018-03-12 20:09:44 -04:00
|
|
|
#[inline]
|
2018-03-15 06:54:18 -04:00
|
|
|
fn animated_map<A, F>(self, duration: f64, f: F) -> AnimatedMap<Self, F>
|
|
|
|
where F: FnMut(Self::Item, Self::AnimatedSignal) -> A {
|
2018-03-12 20:09:44 -04:00
|
|
|
AnimatedMap {
|
|
|
|
duration: duration,
|
|
|
|
animations: vec![],
|
|
|
|
signal: Some(self),
|
|
|
|
callback: f,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct AnimatedMapState {
|
|
|
|
animation: MutableAnimation,
|
2018-03-15 06:54:18 -04:00
|
|
|
removing: Option<WaitFor<MutableSignal<Percentage>>>,
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO move this into signals crate and also generalize it to work with any future, not just animations
|
|
|
|
pub struct AnimatedMap<A, B> {
|
|
|
|
duration: f64,
|
|
|
|
animations: Vec<AnimatedMapState>,
|
|
|
|
signal: Option<A>,
|
|
|
|
callback: B,
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
impl<A, F, S> AnimatedMap<S, F>
|
2018-03-12 20:09:44 -04:00
|
|
|
where S: SignalVec,
|
2018-03-15 06:54:18 -04:00
|
|
|
F: FnMut(S::Item, MutableSignal<Percentage>) -> A {
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
fn animated_state(&self) -> AnimatedMapState {
|
|
|
|
let state = AnimatedMapState {
|
|
|
|
animation: MutableAnimation::new(self.duration),
|
|
|
|
removing: None,
|
|
|
|
};
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
state.animation.animate_to(Percentage::new_unchecked(1.0));
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
state
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
fn remove_index(&mut self, index: usize) -> Async<Option<VecChange<A>>> {
|
2018-03-12 20:09:44 -04:00
|
|
|
if index == (self.animations.len() - 1) {
|
|
|
|
self.animations.pop();
|
|
|
|
Async::Ready(Some(VecChange::Pop {}))
|
|
|
|
|
|
|
|
} else {
|
|
|
|
self.animations.remove(index);
|
|
|
|
Async::Ready(Some(VecChange::RemoveAt { index }))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
fn should_remove(&mut self, index: usize) -> bool {
|
|
|
|
let state = &mut self.animations[index];
|
2018-03-12 20:09:44 -04:00
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
state.animation.animate_to(Percentage::new_unchecked(0.0));
|
2018-03-12 20:09:44 -04:00
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
let mut future = state.animation.signal().wait_for(Percentage::new_unchecked(0.0));
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
if future.poll().unwrap().is_ready() {
|
2018-03-15 06:54:18 -04:00
|
|
|
true
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
} else {
|
|
|
|
state.removing = Some(future);
|
2018-03-15 06:54:18 -04:00
|
|
|
false
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_index(&self, parent_index: usize) -> Option<usize> {
|
|
|
|
let mut seen = 0;
|
|
|
|
|
|
|
|
// TODO is there a combinator that can simplify this ?
|
|
|
|
self.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(&self) -> Option<usize> {
|
|
|
|
self.animations.iter().rposition(|state| state.removing.is_none())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
impl<A, F, S> SignalVec for AnimatedMap<S, F>
|
2018-03-12 20:09:44 -04:00
|
|
|
where S: SignalVec,
|
2018-03-15 06:54:18 -04:00
|
|
|
F: FnMut(S::Item, MutableSignal<Percentage>) -> A {
|
|
|
|
type Item = A;
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
// TODO this can probably be implemented more efficiently
|
2018-03-15 06:54:18 -04:00
|
|
|
fn poll(&mut self) -> Async<Option<VecChange<Self::Item>>> {
|
|
|
|
let mut is_done = true;
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
// TODO is this loop correct ?
|
2018-03-15 06:54:18 -04:00
|
|
|
while let Some(mut signal) = self.signal.take() {
|
2018-03-12 20:09:44 -04:00
|
|
|
match signal.poll() {
|
|
|
|
Async::Ready(Some(change)) => {
|
|
|
|
self.signal = Some(signal);
|
|
|
|
|
|
|
|
return match change {
|
|
|
|
// TODO maybe it should play remove / insert animations for this ?
|
|
|
|
VecChange::Replace { values } => {
|
|
|
|
self.animations = Vec::with_capacity(values.len());
|
|
|
|
|
|
|
|
Async::Ready(Some(VecChange::Replace {
|
|
|
|
values: values.into_iter().map(|value| {
|
|
|
|
let state = AnimatedMapState {
|
2018-03-15 06:54:18 -04:00
|
|
|
animation: MutableAnimation::new_with_initial(self.duration, Percentage::new_unchecked(1.0)),
|
2018-03-12 20:09:44 -04:00
|
|
|
removing: None,
|
|
|
|
};
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
let value = (self.callback)(value, state.animation.signal());
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
self.animations.push(state);
|
|
|
|
|
|
|
|
value
|
|
|
|
}).collect()
|
|
|
|
}))
|
|
|
|
},
|
|
|
|
|
|
|
|
VecChange::InsertAt { index, value } => {
|
|
|
|
let index = self.find_index(index).unwrap_or_else(|| self.animations.len());
|
|
|
|
let state = self.animated_state();
|
2018-03-15 06:54:18 -04:00
|
|
|
let value = (self.callback)(value, state.animation.signal());
|
2018-03-12 20:09:44 -04:00
|
|
|
self.animations.insert(index, state);
|
|
|
|
Async::Ready(Some(VecChange::InsertAt { index, value }))
|
|
|
|
},
|
|
|
|
|
|
|
|
VecChange::Push { value } => {
|
|
|
|
let state = self.animated_state();
|
2018-03-15 06:54:18 -04:00
|
|
|
let value = (self.callback)(value, state.animation.signal());
|
2018-03-12 20:09:44 -04:00
|
|
|
self.animations.push(state);
|
|
|
|
Async::Ready(Some(VecChange::Push { value }))
|
|
|
|
},
|
|
|
|
|
|
|
|
VecChange::UpdateAt { index, value } => {
|
|
|
|
let index = self.find_index(index).expect("Could not find value");
|
2018-03-15 06:54:18 -04:00
|
|
|
let state = &self.animations[index];
|
|
|
|
let value = (self.callback)(value, state.animation.signal());
|
2018-03-12 20:09:44 -04:00
|
|
|
Async::Ready(Some(VecChange::UpdateAt { index, value }))
|
|
|
|
},
|
|
|
|
|
|
|
|
VecChange::RemoveAt { index } => {
|
|
|
|
let index = self.find_index(index).expect("Could not find value");
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
if self.should_remove(index) {
|
|
|
|
self.remove_index(index)
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
VecChange::Pop {} => {
|
|
|
|
let index = self.find_last_index().expect("Cannot pop from empty vec");
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
if self.should_remove(index) {
|
|
|
|
self.remove_index(index)
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// TODO maybe it should play remove animation for this ?
|
|
|
|
VecChange::Clear {} => {
|
|
|
|
self.animations = vec![];
|
|
|
|
Async::Ready(Some(VecChange::Clear {}))
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Async::Ready(None) => {
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
Async::NotReady => {
|
|
|
|
self.signal = Some(signal);
|
|
|
|
is_done = false;
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
let mut is_removing = false;
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
// TODO make this more efficient (e.g. using a similar strategy as FuturesUnordered)
|
|
|
|
// This uses rposition so that way it will return VecChange::Pop in more situations
|
2018-03-15 06:54:18 -04:00
|
|
|
let index = self.animations.iter_mut().rposition(|state| {
|
|
|
|
if let Some(ref mut future) = state.removing {
|
2018-03-12 20:09:44 -04:00
|
|
|
is_removing = true;
|
|
|
|
future.poll().unwrap().is_ready()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
match index {
|
|
|
|
Some(index) => {
|
|
|
|
self.remove_index(index)
|
|
|
|
},
|
|
|
|
None => if is_done && !is_removing {
|
|
|
|
Async::Ready(None)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
Async::NotReady
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
2018-03-12 20:09:44 -04:00
|
|
|
pub struct Percentage(f64);
|
|
|
|
|
|
|
|
impl Percentage {
|
|
|
|
#[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)
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn map<F>(self, f: F) -> Self where F: FnOnce(f64) -> f64 {
|
|
|
|
Self::new(f(self.0))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn map_unchecked<F>(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)
|
|
|
|
}
|
|
|
|
|
2018-03-12 20:09:44 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn range_inclusive(&self, low: f64, high: f64) -> f64 {
|
2018-03-15 06:54:18 -04:00
|
|
|
range_inclusive(self.0, low, high)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO figure out better name
|
|
|
|
#[inline]
|
|
|
|
pub fn into_f64(self) -> f64 {
|
|
|
|
self.0
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Into<f64> for Percentage {
|
|
|
|
#[inline]
|
2018-03-15 06:54:18 -04:00
|
|
|
fn into(self) -> f64 {
|
|
|
|
self.0
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
#[inline]
|
|
|
|
fn range_inclusive(percentage: f64, low: f64, high: f64) -> f64 {
|
|
|
|
low + (percentage * (high - low))
|
|
|
|
}
|
|
|
|
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
pub mod unsync {
|
2018-03-16 02:50:21 -04:00
|
|
|
use super::{Percentage, range_inclusive, timestamps};
|
|
|
|
use operations::spawn_future;
|
2018-03-15 06:54:18 -04:00
|
|
|
use std::rc::Rc;
|
|
|
|
use std::cell::{Cell, RefCell};
|
2018-03-16 02:50:21 -04:00
|
|
|
use signals::signal::{Signal, CancelableFutureHandle};
|
2018-03-15 06:54:18 -04:00
|
|
|
use signals::signal::unsync::{Mutable, MutableSignal};
|
2018-03-16 02:50:21 -04:00
|
|
|
use discard::DiscardOnDrop;
|
2018-03-15 06:54:18 -04:00
|
|
|
|
|
|
|
|
|
|
|
struct MutableAnimationState {
|
|
|
|
playing: Cell<bool>,
|
|
|
|
duration: Cell<f64>,
|
2018-03-12 20:09:44 -04:00
|
|
|
value: Mutable<Percentage>,
|
2018-03-15 06:54:18 -04:00
|
|
|
end: Cell<Percentage>,
|
2018-03-16 02:50:21 -04:00
|
|
|
animating: RefCell<Option<DiscardOnDrop<CancelableFutureHandle>>>,
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct MutableAnimation(Rc<MutableAnimationState>);
|
|
|
|
|
2018-03-12 20:09:44 -04:00
|
|
|
impl MutableAnimation {
|
|
|
|
#[inline]
|
|
|
|
pub fn new_with_initial(duration: f64, initial: Percentage) -> Self {
|
|
|
|
debug_assert!(duration >= 0.0);
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
MutableAnimation(Rc::new(MutableAnimationState {
|
|
|
|
playing: Cell::new(true),
|
|
|
|
duration: Cell::new(duration),
|
2018-03-12 20:09:44 -04:00
|
|
|
value: Mutable::new(initial),
|
2018-03-15 06:54:18 -04:00
|
|
|
end: Cell::new(initial),
|
2018-03-16 02:50:21 -04:00
|
|
|
animating: RefCell::new(None),
|
2018-03-15 06:54:18 -04:00
|
|
|
}))
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn new(duration: f64) -> Self {
|
|
|
|
Self::new_with_initial(duration, Percentage::new_unchecked(0.0))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-03-16 02:50:21 -04:00
|
|
|
fn stop_animating(&self) {
|
|
|
|
*self.0.animating.borrow_mut() = None;
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
|
2018-03-16 02:50:21 -04:00
|
|
|
fn start_animating(&self) {
|
2018-03-15 06:54:18 -04:00
|
|
|
if self.0.playing.get() {
|
|
|
|
// TODO use Copy constraint to make value.get() faster ?
|
|
|
|
let start: f64 = self.0.value.get().into();
|
|
|
|
let end: f64 = self.0.end.get().into();
|
|
|
|
|
|
|
|
if start != end {
|
|
|
|
let duration = self.0.duration.get();
|
|
|
|
|
|
|
|
if duration > 0.0 {
|
|
|
|
let duration = (end - start).abs() * duration;
|
2018-03-12 20:09:44 -04:00
|
|
|
|
2018-03-16 02:50:21 -04:00
|
|
|
let state = self.clone();
|
2018-03-12 20:09:44 -04:00
|
|
|
|
2018-03-16 02:50:21 -04:00
|
|
|
let mut starting_time = None;
|
2018-03-12 20:09:44 -04:00
|
|
|
|
2018-03-16 02:50:21 -04:00
|
|
|
*self.0.animating.borrow_mut() = Some(spawn_future(
|
|
|
|
timestamps()
|
|
|
|
.map(move |current_time| {
|
|
|
|
if let Some(current_time) = current_time {
|
|
|
|
let starting_time = *starting_time.get_or_insert(current_time);
|
2018-03-15 06:54:18 -04:00
|
|
|
|
2018-03-16 02:50:21 -04:00
|
|
|
let diff = (current_time - starting_time) / duration;
|
|
|
|
|
|
|
|
// TODO don't update if the new value is the same as the old value
|
|
|
|
if diff >= 1.0 {
|
|
|
|
state.stop_animating();
|
|
|
|
state.0.value.set(Percentage::new_unchecked(end));
|
|
|
|
true
|
|
|
|
|
|
|
|
} else {
|
|
|
|
state.0.value.set(Percentage::new_unchecked(range_inclusive(diff, start, end)));
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.wait_for(true)
|
|
|
|
));
|
2018-03-12 20:09:44 -04:00
|
|
|
|
|
|
|
} else {
|
2018-03-16 02:50:21 -04:00
|
|
|
self.stop_animating();
|
2018-03-15 06:54:18 -04:00
|
|
|
self.0.value.set(Percentage::new_unchecked(end));
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
2018-03-15 06:54:18 -04:00
|
|
|
|
|
|
|
} else {
|
|
|
|
// TODO is this necessary ?
|
2018-03-16 02:50:21 -04:00
|
|
|
self.stop_animating();
|
2018-03-15 06:54:18 -04:00
|
|
|
}
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
pub fn set_duration(&self, duration: f64) {
|
2018-03-12 20:09:44 -04:00
|
|
|
debug_assert!(duration >= 0.0);
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
if self.0.duration.get() != duration {
|
|
|
|
self.0.duration.set(duration);
|
2018-03-16 02:50:21 -04:00
|
|
|
self.start_animating();
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-03-15 06:54:18 -04:00
|
|
|
pub fn pause(&self) {
|
|
|
|
self.0.playing.set(false);
|
2018-03-16 02:50:21 -04:00
|
|
|
self.stop_animating();
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-03-15 06:54:18 -04:00
|
|
|
pub fn play(&self) {
|
|
|
|
self.0.playing.set(true);
|
2018-03-16 02:50:21 -04:00
|
|
|
self.start_animating();
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
pub fn jump_to(&self, end: Percentage) {
|
2018-03-16 02:50:21 -04:00
|
|
|
self.stop_animating();
|
2018-03-15 06:54:18 -04:00
|
|
|
|
|
|
|
self.0.end.set(end);
|
|
|
|
|
2018-03-12 20:09:44 -04:00
|
|
|
// TODO use Copy constraint to make value.get() faster ?
|
2018-03-15 06:54:18 -04:00
|
|
|
if self.0.value.get() != end {
|
|
|
|
self.0.value.set(end);
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
pub fn animate_to(&self, end: Percentage) {
|
|
|
|
if self.0.end.get() != end {
|
|
|
|
if self.0.duration.get() <= 0.0 {
|
2018-03-12 20:09:44 -04:00
|
|
|
self.jump_to(end);
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
} else {
|
|
|
|
self.0.end.set(end);
|
2018-03-16 02:50:21 -04:00
|
|
|
self.start_animating();
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-15 06:54:18 -04:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn signal(&self) -> MutableSignal<Percentage> {
|
|
|
|
self.0.value.signal()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn in_out<F>(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)
|
|
|
|
}
|
|
|
|
})
|
2018-03-12 20:09:44 -04:00
|
|
|
}
|
|
|
|
}
|