rust-dominator/src/animation.rs

508 lines
15 KiB
Rust
Raw Normal View History

2018-03-15 06:54:18 -04:00
use self::unsync::MutableAnimation;
use futures::Async;
use futures::future::Future;
use signals::signal::{Signal, WaitFor};
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
// TODO maybe keep a queue in Rust, so that way it only needs to call the callback once
struct Raf(Value);
impl Raf {
#[inline]
2018-03-15 06:54:18 -04:00
fn new<F>(callback: F) -> Self where F: FnMut(f64, f64) -> bool + 'static {
2018-03-12 20:09:44 -04:00
Raf(js!(
var starting_time = null;
var callback = @{callback};
function loop(time) {
// TODO assign this immediately when the Raf is created ?
if (starting_time === null) {
starting_time = time;
}
if (callback(starting_time, time)) {
value.id = requestAnimationFrame(loop);
} else {
value.id = null;
callback.drop();
}
}
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-12 20:09:44 -04:00
if (self.id !== null) {
cancelAnimationFrame(self.id);
self.callback.drop();
}
}
}
}
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-15 06:54:18 -04:00
use super::{Raf, Percentage, range_inclusive};
use std::rc::Rc;
use std::cell::{Cell, RefCell};
use signals::signal::unsync::{Mutable, MutableSignal};
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>,
raf: RefCell<Option<Raf>>,
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),
raf: RefCell::new(None),
}))
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-15 06:54:18 -04:00
fn stop_raf(&self) {
*self.0.raf.borrow_mut() = None;
2018-03-12 20:09:44 -04:00
}
2018-03-15 06:54:18 -04:00
fn start_raf(&self) {
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-15 06:54:18 -04:00
let value = self.0.value.clone();
2018-03-12 20:09:44 -04:00
2018-03-15 06:54:18 -04:00
*self.0.raf.borrow_mut() = Some(Raf::new(move |starting_time, current_time| {
let diff = (current_time - starting_time) / duration;
2018-03-12 20:09:44 -04:00
2018-03-15 06:54:18 -04:00
if diff >= 1.0 {
value.set(Percentage::new_unchecked(end));
false
} else {
value.set(Percentage::new_unchecked(range_inclusive(diff, start, end)));
true
}
}));
2018-03-12 20:09:44 -04:00
} else {
2018-03-15 06:54:18 -04:00
self.stop_raf();
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 ?
self.stop_raf();
}
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);
self.start_raf();
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-12 20:09:44 -04:00
self.stop_raf();
}
#[inline]
2018-03-15 06:54:18 -04:00
pub fn play(&self) {
self.0.playing.set(true);
2018-03-12 20:09:44 -04:00
self.start_raf();
}
2018-03-15 06:54:18 -04:00
pub fn jump_to(&self, end: Percentage) {
self.stop_raf();
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-12 20:09:44 -04:00
self.start_raf();
}
}
}
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
}
}