use std::{
ops::Deref,
time::{
Duration,
Instant,
},
};
use async_io::Timer;
use freya_core::prelude::*;
#[derive(Default, PartialEq, Clone, Debug)]
pub struct AnimConfiguration {
on_finish: OnFinish,
on_creation: OnCreation,
on_change: OnChange,
}
impl AnimConfiguration {
pub fn on_finish(&mut self, on_finish: OnFinish) -> &mut Self {
self.on_finish = on_finish;
self
}
pub fn on_creation(&mut self, on_creation: OnCreation) -> &mut Self {
self.on_creation = on_creation;
self
}
pub fn on_change(&mut self, on_change: OnChange) -> &mut Self {
self.on_change = on_change;
self
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum AnimDirection {
Forward,
Reverse,
}
impl AnimDirection {
pub fn toggle(&mut self) {
match self {
Self::Forward => *self = Self::Reverse,
Self::Reverse => *self = Self::Forward,
}
}
}
#[derive(PartialEq, Clone, Copy, Default, Debug)]
pub enum OnFinish {
#[default]
Nothing,
Reverse {
delay: Duration,
},
Restart {
delay: Duration,
},
}
impl OnFinish {
pub fn nothing() -> Self {
Self::Nothing
}
pub fn reverse() -> Self {
Self::Reverse {
delay: Duration::ZERO,
}
}
pub fn reverse_with_delay(delay: Duration) -> Self {
Self::Reverse { delay }
}
pub fn restart() -> Self {
Self::Restart {
delay: Duration::ZERO,
}
}
pub fn restart_with_delay(delay: Duration) -> Self {
Self::Restart { delay }
}
}
#[derive(PartialEq, Clone, Copy, Default, Debug)]
pub enum OnCreation {
#[default]
Nothing,
Run,
Finish,
}
#[derive(PartialEq, Clone, Copy, Default, Debug)]
pub enum OnChange {
#[default]
Reset,
Finish,
Rerun,
Nothing,
}
pub trait ReadAnimatedValue: Clone + 'static {
type Output;
fn value(&self) -> Self::Output;
}
pub trait AnimatedValue: Clone + Default + 'static {
fn prepare(&mut self, direction: AnimDirection);
fn is_finished(&self, index: u128, direction: AnimDirection) -> bool;
fn advance(&mut self, index: u128, direction: AnimDirection);
fn finish(&mut self, direction: AnimDirection);
fn into_reversed(self) -> Self;
}
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub enum Ease {
In,
#[default]
Out,
InOut,
}
#[derive(Clone, PartialEq)]
pub struct UseAnimation<Animated: AnimatedValue> {
pub(crate) animated_value: State<Animated>,
pub(crate) config: State<AnimConfiguration>,
pub(crate) is_running: State<bool>,
pub(crate) has_run_yet: State<bool>,
pub(crate) task: State<Option<TaskHandle>>,
pub(crate) last_direction: State<AnimDirection>,
}
impl<T: AnimatedValue> Copy for UseAnimation<T> {}
impl<Animated: AnimatedValue> Deref for UseAnimation<Animated> {
type Target = State<Animated>;
fn deref(&self) -> &Self::Target {
&self.animated_value
}
}
impl<Animated: AnimatedValue> UseAnimation<Animated> {
pub fn get(&self) -> ReadRef<'static, Animated> {
self.animated_value.read()
}
pub fn direction(&self) -> ReadRef<'static, AnimDirection> {
self.last_direction.read()
}
pub fn start(&mut self) {
self.run(AnimDirection::Forward)
}
pub fn reverse(&mut self) {
self.run(AnimDirection::Reverse)
}
pub fn reset(&mut self) {
if let Some(task) = self.task.write().take() {
task.cancel();
}
self.animated_value
.write()
.prepare(*self.last_direction.peek());
*self.has_run_yet.write() = true;
}
pub fn finish(&mut self) {
if let Some(task) = self.task.write().take() {
task.cancel();
}
self.animated_value
.write()
.finish(*self.last_direction.peek());
*self.has_run_yet.write() = true;
}
pub fn is_running(&self) -> State<bool> {
self.is_running
}
pub fn has_run_yet(&self) -> State<bool> {
self.has_run_yet
}
pub fn run(&self, mut direction: AnimDirection) {
let mut is_running = self.is_running;
let mut has_run_yet = self.has_run_yet;
let mut task = self.task;
let mut last_direction = self.last_direction;
let on_finish = self.config.peek().on_finish;
let mut animated_value = self.animated_value;
last_direction.set(direction);
if let Some(task) = task.write().take() {
task.cancel();
}
let peek_has_run_yet = *self.has_run_yet.peek();
let mut ticker = RenderingTicker::get();
let platform = Platform::get();
let animation_clock = AnimationClock::get();
animated_value.write().prepare(direction);
let animation_task = spawn(async move {
platform.send(UserEvent::RequestRedraw);
let mut index = 0u128;
let mut prev_frame = Instant::now();
if !peek_has_run_yet {
*has_run_yet.write() = true;
}
is_running.set(true);
loop {
ticker.tick().await;
platform.send(UserEvent::RequestRedraw);
let elapsed = animation_clock.correct_elapsed_duration(prev_frame.elapsed());
index += elapsed.as_millis();
let is_finished = {
let mut animated_value = animated_value.write();
let is_finished = animated_value.is_finished(index, direction);
animated_value.advance(index, direction);
is_finished
};
if is_finished {
let delay = match on_finish {
OnFinish::Reverse { delay } => {
direction.toggle();
delay
}
OnFinish::Restart { delay } => delay,
OnFinish::Nothing => {
break;
}
};
if !delay.is_zero() {
Timer::after(delay).await;
}
index = 0;
animated_value.write().prepare(direction);
}
prev_frame = Instant::now();
}
is_running.set(false);
task.write().take();
});
task.write().replace(animation_task);
}
}
pub fn use_animation<Animated: AnimatedValue>(
mut run: impl 'static + FnMut(&mut AnimConfiguration) -> Animated,
) -> UseAnimation<Animated> {
use_hook(|| {
let mut config = State::create(AnimConfiguration::default());
let mut animated_value = State::create(Animated::default());
let is_running = State::create(false);
let has_run_yet = State::create(false);
let task = State::create(None);
let last_direction = State::create(AnimDirection::Forward);
let mut animation = UseAnimation {
animated_value,
config,
is_running,
has_run_yet,
task,
last_direction,
};
Effect::create_sync_with_gen(move |current_gen| {
let mut anim_conf = AnimConfiguration::default();
animated_value.set(run(&mut anim_conf));
*config.write() = anim_conf;
match config.peek().on_change {
OnChange::Finish if current_gen > 0 => {
animation.finish();
}
OnChange::Rerun if current_gen > 0 => {
let last_direction = *animation.last_direction.peek();
animation.run(last_direction);
}
OnChange::Reset if current_gen > 0 => {
animation.reset();
}
_ => {}
}
});
match config.peek().on_creation {
OnCreation::Run => {
animation.run(AnimDirection::Forward);
}
OnCreation::Finish => {
animation.finish();
}
_ => {}
}
animation
})
}
pub fn use_animation_with_dependencies<Animated: AnimatedValue, D: 'static + Clone + PartialEq>(
dependencies: &D,
mut run: impl 'static + FnMut(&mut AnimConfiguration, &D) -> Animated,
) -> UseAnimation<Animated> {
let dependencies = use_reactive(dependencies);
use_hook(|| {
let mut config = State::create(AnimConfiguration::default());
let mut animated_value = State::create(Animated::default());
let is_running = State::create(false);
let has_run_yet = State::create(false);
let task = State::create(None);
let last_direction = State::create(AnimDirection::Forward);
let mut animation = UseAnimation {
animated_value,
config,
is_running,
has_run_yet,
task,
last_direction,
};
Effect::create_sync_with_gen(move |current_gen| {
let dependencies = dependencies.read();
let mut anim_conf = AnimConfiguration::default();
animated_value.set(run(&mut anim_conf, &dependencies));
*config.write() = anim_conf;
match config.peek().on_change {
OnChange::Finish if current_gen > 0 => {
animation.finish();
}
OnChange::Rerun if current_gen > 0 => {
let last_direction = *animation.last_direction.peek();
animation.run(last_direction);
}
OnChange::Reset if current_gen > 0 => {
animation.reset();
}
_ => {}
}
});
match config.peek().on_creation {
OnCreation::Run => {
animation.run(AnimDirection::Forward);
}
OnCreation::Finish => {
animation.finish();
}
_ => {}
}
animation
})
}
macro_rules! impl_tuple_call {
($(($($type:ident),*)),*) => {
$(
impl<$($type,)*> AnimatedValue for ($($type,)*)
where
$($type: AnimatedValue,)*
{
fn prepare(&mut self, direction: AnimDirection) {
#[allow(non_snake_case)]
let ($($type,)*) = self;
$(
$type.prepare(direction);
)*
}
fn is_finished(&self, index: u128, direction: AnimDirection) -> bool {
#[allow(non_snake_case)]
let ($($type,)*) = self;
$(
if !$type.is_finished(index, direction) {
return false;
}
)*
true
}
fn advance(&mut self, index: u128, direction: AnimDirection) {
#[allow(non_snake_case)]
let ($($type,)*) = self;
$(
$type.advance(index, direction);
)*
}
fn finish(&mut self, direction: AnimDirection) {
#[allow(non_snake_case)]
let ($($type,)*) = self;
$(
$type.finish(direction);
)*
}
fn into_reversed(self) -> Self {
#[allow(non_snake_case)]
let ($($type,)*) = self;
(
$(
$type.into_reversed(),
)*
)
}
}
impl<$($type,)*> ReadAnimatedValue for ($($type,)*)
where
$($type: ReadAnimatedValue,)*
{
type Output = (
$(
<$type as ReadAnimatedValue>::Output,
)*
);
fn value(&self) -> Self::Output {
#[allow(non_snake_case)]
let ($($type,)*) = self;
(
$(
$type.value(),
)*
)
}
}
)*
};
}
impl_tuple_call!(
(T1),
(T1, T2),
(T1, T2, T3),
(T1, T2, T3, T4),
(T1, T2, T3, T4, T5),
(T1, T2, T3, T4, T5, T6),
(T1, T2, T3, T4, T5, T6, T7),
(T1, T2, T3, T4, T5, T6, T7, T8),
(T1, T2, T3, T4, T5, T6, T7, T8, T9),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)
);
pub fn use_animation_transition<Animated: AnimatedValue, T: Clone + PartialEq + 'static>(
value: impl IntoReadable<T>,
mut run: impl 'static + FnMut(T, T) -> Animated,
) -> UseAnimation<Animated> {
let values = use_previous_and_current(value);
use_animation(move |conf| {
conf.on_change(OnChange::Rerun);
conf.on_creation(OnCreation::Run);
let (from, to) = values.read().clone();
run(from, to)
})
}