use std::{
fmt,
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,
}
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
}
}
impl AnimatedValue for AnimColor {
fn time(&self) -> Duration {
self.time
}
fn as_f32(&self) -> f32 {
panic!("This is not a f32.")
}
fn as_string(&self) -> String {
format!(
"rgb({}, {}, {}, {})",
self.value.r(),
self.value.g(),
self.value.b(),
self.value.a()
)
}
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);
}
}
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
}
}
impl AnimatedValue for AnimNum {
fn time(&self) -> Duration {
self.time
}
fn as_f32(&self) -> f32 {
self.value
}
fn as_string(&self) -> String {
panic!("This is not a String");
}
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,
)
}
}
pub trait AnimatedValue {
fn time(&self) -> Duration;
fn as_f32(&self) -> f32;
fn as_string(&self) -> String;
fn prepare(&mut self, direction: AnimDirection);
fn is_finished(&self, index: u128, direction: AnimDirection) -> bool;
fn advance(&mut self, index: u128, direction: AnimDirection);
}
pub type ReadAnimatedValue = ReadOnlySignal<Box<dyn AnimatedValue>>;
#[derive(Default, PartialEq, Clone)]
pub struct Context {
animated_values: Vec<Signal<Box<dyn AnimatedValue>>>,
on_finish: OnFinish,
auto_start: bool,
on_deps_change: OnDepsChange,
}
impl Context {
pub fn with(&mut self, animated_value: impl AnimatedValue + 'static) -> ReadAnimatedValue {
let val: Box<dyn AnimatedValue> = Box::new(animated_value);
let signal = Signal::new(val);
self.animated_values.push(signal);
ReadOnlySignal::new(signal)
}
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, 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,
Run,
}
#[derive(PartialEq, Clone)]
pub struct UseAnimator<Animated: PartialEq + Clone + 'static> {
pub(crate) value_and_ctx: Memo<(Animated, Context)>,
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: PartialEq + Clone + 'static> Copy for UseAnimator<T> {}
impl<Animated: PartialEq + Clone + 'static> UseAnimator<Animated> {
pub fn get(&self) -> Animated {
self.value_and_ctx.read().0.clone()
}
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();
}
for value in &self.value_and_ctx.read().1.animated_values {
let mut value = *value;
value.write().prepare(AnimDirection::Forward);
}
}
pub fn run_update(&self) {
let mut task = self.task;
if let Some(task) = task.write().take() {
task.cancel();
}
for value in &self.value_and_ctx.read().1.animated_values {
let mut value = *value;
let time = value.peek().time().as_millis();
value.write().advance(time, *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 ctx = &self.value_and_ctx.peek().1;
let platform = self.platform;
let mut is_running = self.is_running;
let mut ticker = platform.new_ticker();
let mut values = ctx.animated_values.clone();
let mut has_run_yet = self.has_run_yet;
let on_finish = ctx.on_finish;
let mut task = self.task;
let mut last_direction = self.last_direction;
last_direction.set(direction);
if let Some(task) = task.write().take() {
task.cancel();
}
let peek_has_run_yet = self.peek_has_run_yet();
let animation_task = spawn(async move {
platform.request_animation_frame();
let mut index = 0u128;
let mut prev_frame = Instant::now();
for value in values.iter_mut() {
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 = values
.iter()
.all(|value| value.peek().is_finished(index, direction));
for value in values.iter_mut() {
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;
for value in values.iter_mut() {
value.write().prepare(direction);
}
}
OnFinish::Stop => {
break;
}
}
}
}
is_running.set(false);
task.write().take();
});
task.write().replace(animation_task);
}
}
pub fn use_animation<Animated: PartialEq + Clone + 'static>(
run: impl Fn(&mut Context) -> Animated + Clone + 'static,
) -> UseAnimator<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 value_and_ctx = use_memo(move || {
let mut ctx = Context::default();
(run(&mut ctx), ctx)
});
let animator = UseAnimator {
value_and_ctx,
platform,
is_running,
has_run_yet,
task,
last_direction,
};
use_hook(move || {
if animator.value_and_ctx.read().1.auto_start {
animator.run(AnimDirection::Forward);
}
});
animator
}
pub fn use_animation_with_dependencies<Animated: PartialEq + Clone + 'static, D: Dependency>(
deps: D,
run: impl Fn(&mut Context, D::Out) -> Animated + 'static,
) -> UseAnimator<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 value_and_ctx = use_memo(use_reactive(deps, move |vals| {
let mut ctx = Context::default();
(run(&mut ctx, vals), ctx)
}));
let animator = UseAnimator {
value_and_ctx,
platform,
is_running,
has_run_yet,
task,
last_direction,
};
use_memo(move || {
let value_and_ctx = value_and_ctx.read();
if *has_run_yet.peek() && value_and_ctx.1.on_deps_change == OnDepsChange::Run {
animator.run_update()
}
});
use_hook(move || {
if animator.value_and_ctx.read().1.auto_start {
animator.run(AnimDirection::Forward);
}
});
animator
}