use dioxus_core::prelude::spawn;
use dioxus_signals::{use_memo_with_dependencies, Dependency, Readable, Signal, Writable};
use freya_engine::prelude::Color;
use freya_node_state::Parse;
use tokio::time::Instant;
use uuid::Uuid;
use crate::{use_platform, Animation, TransitionAnimation, UsePlatform};
#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Transition {
Size(f64, f64),
Color(Color, Color),
}
impl Transition {
pub fn new_size(start: f64, end: f64) -> Self {
Self::Size(start, end)
}
pub fn new_color(start: &str, end: &str) -> Self {
let start = Color::parse(start).unwrap();
let end = Color::parse(end).unwrap();
Self::Color(start, end)
}
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub enum TransitionState {
Size(f64),
Color(Color),
}
impl From<&Transition> for TransitionState {
fn from(value: &Transition) -> Self {
match *value {
Transition::Size(start, _) => Self::Size(start),
Transition::Color(start, _) => Self::Color(start),
}
}
}
impl TransitionState {
pub fn set_value(&mut self, animate: &Transition, value: f64) {
match (self, animate) {
(Self::Size(current), Transition::Size(start, end)) => {
let road = *end - *start;
let walked = (road / 100.0) * value;
*current = walked;
}
(Self::Color(current), Transition::Color(start, end)) => {
let apply_index = |v: u8, d: u8, value: f64| -> u8 {
let road = if d > v { d - v } else { v - d };
let walked = (road as f64 / 100.0) * value;
if d > v {
v + walked.round() as u8
} else {
v - walked.round() as u8
}
};
let r = apply_index(start.r(), end.r(), value);
let g = apply_index(start.g(), end.g(), value);
let b = apply_index(start.b(), end.b(), value);
*current = Color::from_rgb(r, g, b)
}
_ => {}
}
}
pub fn clear(&mut self, animate: &Transition) {
match (self, animate) {
(Self::Size(current), Transition::Size(start, _)) => {
*current = *start;
}
(Self::Color(current), Transition::Color(start, _)) => {
*current = *start;
}
_ => {}
}
}
pub fn as_size(&self) -> f64 {
self.to_size().unwrap()
}
pub fn as_color(&self) -> String {
self.to_color().unwrap()
}
pub fn to_size(&self) -> Option<f64> {
match self {
Self::Size(current) => Some(*current),
_ => None,
}
}
pub fn to_color(&self) -> Option<String> {
match self {
Self::Color(current) => Some(format!(
"rgb({}, {}, {})",
current.r(),
current.g(),
current.b()
)),
_ => None,
}
}
pub fn to_raw_color(&self) -> Option<Color> {
match self {
Self::Color(current) => Some(*current),
_ => None,
}
}
}
#[derive(Clone, PartialEq)]
pub struct TransitionsManager {
transitions: Signal<Vec<Transition>>,
transitions_storage: Signal<Vec<TransitionState>>,
transition_animation: TransitionAnimation,
current_animation_id: Signal<Option<Uuid>>,
platform: UsePlatform,
}
impl TransitionsManager {
pub fn reverse(&mut self) {
self.clear();
let animation = self.transition_animation.to_animation(100.0..=0.0);
self.run_with_animation(animation);
}
pub fn start(&mut self) {
self.clear();
let animation = self.transition_animation.to_animation(0.0..=100.0);
self.run_with_animation(animation);
}
fn run_with_animation(&self, mut animation: Animation) {
let animation_id = Uuid::new_v4();
let platform = self.platform.clone();
let mut ticker = platform.new_ticker();
let transitions = self.transitions;
let transitions_storage = self.transitions_storage;
let mut current_animation_id = self.current_animation_id;
current_animation_id.set(Some(animation_id));
spawn(async move {
platform.request_animation_frame();
let mut index = 0;
let mut prev_frame = Instant::now();
loop {
ticker.tick().await;
platform.request_animation_frame();
if *current_animation_id.peek() == Some(animation_id) {
if animation.is_finished() {
current_animation_id.set(None);
break;
}
index += prev_frame.elapsed().as_millis() as i32;
let value = animation.move_value(index);
transitions_storage.with_mut(|storage| {
for (i, storage) in storage.iter_mut().enumerate() {
if let Some(conf) = transitions.peek().get(i) {
storage.set_value(conf, value);
}
}
});
prev_frame = Instant::now();
} else {
break;
}
}
});
}
pub fn clear(&mut self) {
self.current_animation_id.set(None);
self.transitions_storage.with_mut(|storage| {
for (i, storage) in storage.iter_mut().enumerate() {
if let Some(conf) = self.transitions.peek().get(i) {
storage.clear(conf);
}
}
})
}
pub fn is_animating(&self) -> bool {
self.current_animation_id.peek().is_some()
}
pub fn is_at_start(&self) -> bool {
if let Some(storage) = self.get(0) {
let anim = self.transitions.peek()[0];
match anim {
Transition::Size(start, _) => start == storage.to_size().unwrap_or(start),
Transition::Color(start, _) => start == storage.to_raw_color().unwrap_or(start),
}
} else {
true
}
}
pub fn get(&self, index: usize) -> Option<TransitionState> {
self.transitions_storage.read().get(index).copied()
}
}
pub fn use_animation_transition<D>(
transition: TransitionAnimation,
dependencies: D,
init: impl Fn(D::Out) -> Vec<Transition> + 'static,
) -> TransitionsManager
where
D: Dependency + 'static,
{
use_memo_with_dependencies(dependencies.clone(), move |deps| {
let platform = use_platform();
let transitions = init(deps);
let transitions_states = animations_map(&transitions);
TransitionsManager {
current_animation_id: Signal::new(None),
transitions: Signal::new(transitions),
transitions_storage: Signal::new(transitions_states),
transition_animation: transition,
platform,
}
})
.read()
.clone()
}
fn animations_map(animations: &[Transition]) -> Vec<TransitionState> {
animations
.iter()
.map(TransitionState::from)
.collect::<Vec<TransitionState>>()
}