use std::{
fmt,
ops::Deref,
time::Duration,
};
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 easer::functions::*;
use freya_engine::prelude::Color;
use freya_node_state::Parse;
use tokio::time::Instant;
use crate::{
use_platform,
UsePlatform,
};
pub fn apply_value(
origin: f32,
destination: f32,
index: u128,
time: Duration,
ease: Ease,
function: Function,
) -> f32 {
let (t, b, c, d) = (
index as f32,
origin,
destination - origin,
time.as_millis() as f32,
);
match function {
Function::Back => match ease {
Ease::In => Back::ease_in(t, b, c, d),
Ease::InOut => Back::ease_in_out(t, b, c, d),
Ease::Out => Back::ease_out(t, b, c, d),
},
Function::Bounce => match ease {
Ease::In => Bounce::ease_in(t, b, c, d),
Ease::InOut => Bounce::ease_in_out(t, b, c, d),
Ease::Out => Bounce::ease_out(t, b, c, d),
},
Function::Circ => match ease {
Ease::In => Circ::ease_in(t, b, c, d),
Ease::InOut => Circ::ease_in_out(t, b, c, d),
Ease::Out => Circ::ease_out(t, b, c, d),
},
Function::Cubic => match ease {
Ease::In => Cubic::ease_in(t, b, c, d),
Ease::InOut => Cubic::ease_in_out(t, b, c, d),
Ease::Out => Cubic::ease_out(t, b, c, d),
},
Function::Elastic => match ease {
Ease::In => Elastic::ease_in(t, b, c, d),
Ease::InOut => Elastic::ease_in_out(t, b, c, d),
Ease::Out => Elastic::ease_out(t, b, c, d),
},
Function::Expo => match ease {
Ease::In => Expo::ease_in(t, b, c, d),
Ease::InOut => Expo::ease_in_out(t, b, c, d),
Ease::Out => Expo::ease_out(t, b, c, d),
},
Function::Linear => match ease {
Ease::In => Linear::ease_in(t, b, c, d),
Ease::InOut => Linear::ease_in_out(t, b, c, d),
Ease::Out => Linear::ease_out(t, b, c, d),
},
Function::Quad => match ease {
Ease::In => Quad::ease_in(t, b, c, d),
Ease::InOut => Quad::ease_in_out(t, b, c, d),
Ease::Out => Quad::ease_out(t, b, c, d),
},
Function::Quart => match ease {
Ease::In => Quart::ease_in(t, b, c, d),
Ease::InOut => Quart::ease_in_out(t, b, c, d),
Ease::Out => Quart::ease_out(t, b, c, d),
},
Function::Sine => match ease {
Ease::In => Sine::ease_in(t, b, c, d),
Ease::InOut => Sine::ease_in_out(t, b, c, d),
Ease::Out => Sine::ease_out(t, b, c, d),
},
}
}
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Function {
Back,
Bounce,
Circ,
Cubic,
Elastic,
Expo,
#[default]
Linear,
Quad,
Quart,
Sine,
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub enum Ease {
In,
#[default]
Out,
InOut,
}
#[derive(Clone, PartialEq)]
pub struct AnimColor {
origin: Color,
destination: Color,
time: Duration,
ease: Ease,
function: Function,
value: Color,
}
impl AnimColor {
pub fn new(origin: &str, destination: &str) -> Self {
Self {
origin: Color::parse(origin).unwrap(),
destination: Color::parse(destination).unwrap(),
time: Duration::default(),
ease: Ease::default(),
function: Function::default(),
value: Color::parse(origin).unwrap(),
}
}
pub fn time(mut self, time: u64) -> Self {
self.time = Duration::from_millis(time);
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.time = duration;
self
}
pub fn ease(mut self, ease: Ease) -> Self {
self.ease = ease;
self
}
pub fn function(mut self, function: Function) -> Self {
self.function = function;
self
}
pub fn read(&self) -> String {
format!(
"rgb({}, {}, {}, {})",
self.value.r(),
self.value.g(),
self.value.b(),
self.value.a()
)
}
}
impl AnimatedValue for AnimColor {
fn prepare(&mut self, direction: AnimDirection) {
match direction {
AnimDirection::Forward => self.value = self.origin,
AnimDirection::Reverse => {
self.value = self.destination;
}
}
}
fn is_finished(&self, index: u128, direction: AnimDirection) -> bool {
match direction {
AnimDirection::Forward => {
index > self.time.as_millis()
&& self.value.r() == self.destination.r()
&& self.value.g() == self.destination.g()
&& self.value.b() == self.destination.b()
&& self.value.a() == self.destination.a()
}
AnimDirection::Reverse => {
index > self.time.as_millis()
&& self.value.r() == self.origin.r()
&& self.value.g() == self.origin.g()
&& self.value.b() == self.origin.b()
&& self.value.a() == self.origin.a()
}
}
}
fn advance(&mut self, index: u128, direction: AnimDirection) {
let (origin, destination) = match direction {
AnimDirection::Forward => (self.origin, self.destination),
AnimDirection::Reverse => (self.destination, self.origin),
};
let r = apply_value(
origin.r() as f32,
destination.r() as f32,
index.min(self.time.as_millis()),
self.time,
self.ease,
self.function,
);
let g = apply_value(
origin.g() as f32,
destination.g() as f32,
index.min(self.time.as_millis()),
self.time,
self.ease,
self.function,
);
let b = apply_value(
origin.b() as f32,
destination.b() as f32,
index.min(self.time.as_millis()),
self.time,
self.ease,
self.function,
);
let a = apply_value(
origin.a() as f32,
destination.a() as f32,
index.min(self.time.as_millis()),
self.time,
self.ease,
self.function,
);
self.value = Color::from_argb(a as u8, r as u8, g as u8, b as u8);
}
fn finish(&mut self, direction: AnimDirection) {
self.advance(self.time.as_millis(), direction);
}
}
#[derive(Clone)]
pub struct AnimSequential<Animated: AnimatedValue, const N: usize> {
values: [Animated; N],
curr_value: usize,
acc_index: u128,
}
impl<Animated: AnimatedValue, const N: usize> AnimSequential<Animated, N> {
pub fn new(values: [Animated; N]) -> Self {
Self {
values,
curr_value: 0,
acc_index: 0,
}
}
}
impl<Animated: AnimatedValue, const N: usize> Deref for AnimSequential<Animated, N> {
type Target = [Animated; N];
fn deref(&self) -> &Self::Target {
&self.values
}
}
impl<Animated: AnimatedValue, const N: usize> AnimatedValue for AnimSequential<Animated, N> {
fn advance(&mut self, index: u128, direction: AnimDirection) {
if let Some(value) = self.values.get_mut(self.curr_value) {
let index = index - self.acc_index;
value.advance(index, direction);
if value.is_finished(index, direction) {
self.curr_value += 1;
self.acc_index += index;
}
}
}
fn is_finished(&self, index: u128, direction: AnimDirection) -> bool {
if let Some(value) = self.values.get(self.curr_value) {
value.is_finished(index, direction)
} else {
true
}
}
fn prepare(&mut self, direction: AnimDirection) {
self.acc_index = 0;
self.curr_value = 0;
for val in &mut self.values {
val.prepare(direction);
}
}
fn finish(&mut self, direction: AnimDirection) {
for value in &mut self.values {
value.finish(direction);
}
}
}
#[derive(Clone, PartialEq)]
pub struct AnimNum {
origin: f32,
destination: f32,
time: Duration,
ease: Ease,
function: Function,
value: f32,
}
impl AnimNum {
pub fn new(origin: f32, destination: f32) -> Self {
Self {
origin,
destination,
time: Duration::default(),
ease: Ease::default(),
function: Function::default(),
value: origin,
}
}
pub fn time(mut self, time: u64) -> Self {
self.time = Duration::from_millis(time);
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.time = duration;
self
}
pub fn ease(mut self, ease: Ease) -> Self {
self.ease = ease;
self
}
pub fn function(mut self, function: Function) -> Self {
self.function = function;
self
}
pub fn read(&self) -> f32 {
self.value
}
}
impl AnimatedValue for AnimNum {
fn prepare(&mut self, direction: AnimDirection) {
match direction {
AnimDirection::Forward => self.value = self.origin,
AnimDirection::Reverse => {
self.value = self.destination;
}
}
}
fn is_finished(&self, index: u128, direction: AnimDirection) -> bool {
match direction {
AnimDirection::Forward => {
index > self.time.as_millis() && self.value == self.destination
}
AnimDirection::Reverse => index > self.time.as_millis() && self.value == self.origin,
}
}
fn advance(&mut self, index: u128, direction: AnimDirection) {
let (origin, destination) = match direction {
AnimDirection::Forward => (self.origin, self.destination),
AnimDirection::Reverse => (self.destination, self.origin),
};
self.value = apply_value(
origin,
destination,
index.min(self.time.as_millis()),
self.time,
self.ease,
self.function,
);
}
fn finish(&mut self, direction: AnimDirection) {
self.advance(self.time.as_millis(), direction);
}
}
pub trait AnimatedValue: Clone + '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);
}
#[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 Context<Animated: AnimatedValue> {
value: Signal<Animated>,
conf: AnimConfiguration,
}
impl<Animated: AnimatedValue> PartialEq for Context<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,
}
#[derive(Clone)]
pub struct UseAnimation<Animated: AnimatedValue> {
pub(crate) context: Memo<Context<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;
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 context = use_memo(move || {
let mut conf = AnimConfiguration::default();
let value = run(&mut conf);
let value = Signal::new(value);
Context { 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);
}
});
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 context = use_memo(use_reactive(deps, move |deps| {
let mut conf = AnimConfiguration::default();
let value = run(&mut conf, deps);
let value = Signal::new(value);
Context { 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() && context.conf.on_deps_change == OnDepsChange::Finish {
animation.finish()
}
});
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)
);