use dioxus_core::prelude::{
spawn,
use_hook,
Task,
};
use dioxus_hooks::{
use_memo,
use_reactive,
use_signal,
Dependency,
};
use dioxus_signals::{
Memo,
ReadOnlySignal,
Readable,
Signal,
Writable,
};
use tokio::time::Instant;
use super::AnimatedValue;
use crate::{
use_platform,
UsePlatform,
};
#[derive(Default, PartialEq, Clone)]
pub struct AnimConfiguration {
on_finish: OnFinish,
auto_start: bool,
on_deps_change: OnDepsChange,
}
impl AnimConfiguration {
pub fn on_finish(&mut self, on_finish: OnFinish) -> &mut Self {
self.on_finish = on_finish;
self
}
pub fn auto_start(&mut self, auto_start: bool) -> &mut Self {
self.auto_start = auto_start;
self
}
pub fn on_deps_change(&mut self, on_deps_change: OnDepsChange) -> &mut Self {
self.on_deps_change = on_deps_change;
self
}
}
#[derive(Clone)]
pub struct AnimationContext<Animated: AnimatedValue> {
value: Signal<Animated>,
conf: AnimConfiguration,
}
impl<Animated: AnimatedValue> PartialEq for AnimationContext<Animated> {
fn eq(&self, other: &Self) -> bool {
self.value.eq(&other.value) && self.conf.eq(&other.conf)
}
}
#[derive(Clone, Copy)]
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)]
pub enum OnFinish {
#[default]
Stop,
Reverse,
Restart,
}
#[derive(PartialEq, Clone, Copy, Default)]
pub enum OnDepsChange {
#[default]
Reset,
Finish,
Rerun,
}
#[derive(Clone)]
pub struct UseAnimation<Animated: AnimatedValue> {
pub(crate) context: Memo<AnimationContext<Animated>>,
pub(crate) platform: UsePlatform,
pub(crate) is_running: Signal<bool>,
pub(crate) has_run_yet: Signal<bool>,
pub(crate) task: Signal<Option<Task>>,
pub(crate) last_direction: Signal<AnimDirection>,
}
impl<T: AnimatedValue> PartialEq for UseAnimation<T> {
fn eq(&self, other: &Self) -> bool {
self.context.eq(&other.context)
&& self.platform.eq(&other.platform)
&& self.is_running.eq(&other.is_running)
&& self.has_run_yet.eq(&other.has_run_yet)
&& self.task.eq(&other.task)
&& self.last_direction.eq(&other.last_direction)
}
}
impl<T: AnimatedValue> Copy for UseAnimation<T> {}
impl<Animated: AnimatedValue> UseAnimation<Animated> {
pub fn get(&self) -> ReadOnlySignal<Animated> {
self.context.read().value.into()
}
pub fn reset(&self) {
let mut has_run_yet = self.has_run_yet;
let mut task = self.task;
has_run_yet.set(false);
if let Some(task) = task.write().take() {
task.cancel();
}
self.context
.peek()
.value
.write_unchecked()
.prepare(AnimDirection::Forward);
}
pub fn finish(&self) {
let mut task = self.task;
if let Some(task) = task.write().take() {
task.cancel();
}
self.context
.peek()
.value
.write_unchecked()
.finish(*self.last_direction.peek());
}
pub fn is_running(&self) -> bool {
*self.is_running.read()
}
pub fn has_run_yet(&self) -> bool {
*self.has_run_yet.read()
}
pub fn peek_has_run_yet(&self) -> bool {
*self.has_run_yet.peek()
}
pub fn reverse(&self) {
self.run(AnimDirection::Reverse)
}
pub fn start(&self) {
self.run(AnimDirection::Forward)
}
pub fn run(&self, mut direction: AnimDirection) {
let context = &self.context.peek();
let platform = self.platform;
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 = context.conf.on_finish;
let mut value = context.value;
last_direction.set(direction);
if let Some(task) = task.write().take() {
task.cancel();
}
let peek_has_run_yet = self.peek_has_run_yet();
let mut ticker = platform.new_ticker();
let animation_task = spawn(async move {
platform.request_animation_frame();
let mut index = 0u128;
let mut prev_frame = Instant::now();
value.write().prepare(direction);
if !peek_has_run_yet {
*has_run_yet.write() = true;
}
is_running.set(true);
loop {
ticker.tick().await;
if value.try_peek().is_err() {
break;
}
platform.request_animation_frame();
index += prev_frame.elapsed().as_millis();
let is_finished = value.peek().is_finished(index, direction);
value.write().advance(index, direction);
prev_frame = Instant::now();
if is_finished {
if OnFinish::Reverse == on_finish {
direction.toggle();
}
match on_finish {
OnFinish::Restart | OnFinish::Reverse => {
index = 0;
value.write().prepare(direction);
}
OnFinish::Stop => {
break;
}
}
}
}
is_running.set(false);
task.write().take();
});
task.write().replace(animation_task);
}
}
pub fn use_animation<Animated: AnimatedValue>(
run: impl 'static + Fn(&mut AnimConfiguration) -> Animated,
) -> UseAnimation<Animated> {
let platform = use_platform();
let is_running = use_signal(|| false);
let has_run_yet = use_signal(|| false);
let task = use_signal(|| None);
let last_direction = use_signal(|| AnimDirection::Reverse);
let mut prev_value = use_signal::<Option<Signal<Animated>>>(|| None);
let context = use_memo(move || {
if let Some(prev_value) = prev_value.take() {
prev_value.manually_drop();
}
let mut conf = AnimConfiguration::default();
let value = run(&mut conf);
let value = Signal::new(value);
prev_value.set(Some(value));
AnimationContext { value, conf }
});
let animation = UseAnimation {
context,
platform,
is_running,
has_run_yet,
task,
last_direction,
};
use_hook(move || {
if animation.context.read().conf.auto_start {
animation.run(AnimDirection::Forward);
}
});
use_memo(move || {
let context = context.read();
if *has_run_yet.peek() {
match context.conf.on_deps_change {
OnDepsChange::Finish => animation.finish(),
OnDepsChange::Rerun => {
let last_direction = *animation.last_direction.peek();
animation.run(last_direction);
}
_ => {}
}
}
});
animation
}
pub fn use_animation_with_dependencies<Animated: PartialEq + AnimatedValue, D: Dependency>(
deps: D,
run: impl 'static + Fn(&mut AnimConfiguration, D::Out) -> Animated,
) -> UseAnimation<Animated>
where
D::Out: 'static + Clone,
{
let platform = use_platform();
let is_running = use_signal(|| false);
let has_run_yet = use_signal(|| false);
let task = use_signal(|| None);
let last_direction = use_signal(|| AnimDirection::Reverse);
let mut prev_value = use_signal::<Option<Signal<Animated>>>(|| None);
let context = use_memo(use_reactive(deps, move |deps| {
if let Some(prev_value) = prev_value.take() {
prev_value.manually_drop();
}
let mut conf = AnimConfiguration::default();
let value = run(&mut conf, deps);
let value = Signal::new(value);
prev_value.set(Some(value));
AnimationContext { value, conf }
}));
let animation = UseAnimation {
context,
platform,
is_running,
has_run_yet,
task,
last_direction,
};
use_memo(move || {
let context = context.read();
if *has_run_yet.peek() {
match context.conf.on_deps_change {
OnDepsChange::Finish => animation.finish(),
OnDepsChange::Rerun => {
animation.run(*animation.last_direction.peek());
}
_ => {}
}
}
});
use_hook(move || {
if animation.context.read().conf.auto_start {
animation.run(AnimDirection::Forward);
}
});
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);
)*
}
}
)*
};
}
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),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)
);