use std::cell::OnceCell;
use std::rc::Rc;
use crate::reactive::*;
type RafState = (Signal<bool>, Rc<dyn Fn() + 'static>, Rc<dyn Fn() + 'static>);
pub fn create_raf(mut cb: impl FnMut() + 'static) -> RafState {
let running = create_signal(false);
let start: Rc<dyn Fn()>;
let stop: Rc<dyn Fn()>;
let _ = &mut cb;
#[cfg(all(target_arch = "wasm32", feature = "web"))]
{
use std::cell::RefCell;
use wasm_bindgen::prelude::*;
use crate::web::window;
let f = Rc::new(RefCell::new(None::<Closure<dyn FnMut()>>));
let g = Rc::clone(&f);
*g.borrow_mut() = Some(Closure::new(move || {
if running.get() {
cb();
window()
.request_animation_frame(
f.borrow().as_ref().unwrap_throw().as_ref().unchecked_ref(),
)
.unwrap_throw();
}
}));
start = Rc::new(move || {
if !running.get() {
running.set(true);
window()
.request_animation_frame(
g.borrow().as_ref().unwrap_throw().as_ref().unchecked_ref(),
)
.unwrap_throw();
}
});
stop = Rc::new(move || running.set(false));
}
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
{
start = Rc::new(move || running.set(true));
stop = Rc::new(move || running.set(false));
}
(running, start, stop)
}
pub fn create_raf_loop(mut f: impl FnMut() -> bool + 'static) -> RafState {
let stop_shared = Rc::new(OnceCell::<Rc<dyn Fn()>>::new());
let (running, start, stop) = create_raf({
let stop_shared = Rc::clone(&stop_shared);
move || {
if !f() {
stop_shared.get().unwrap()();
}
}
});
stop_shared.set(Rc::clone(&stop)).ok().unwrap();
(running, start, stop)
}
pub fn create_tweened_signal<T: Lerp + Clone>(
initial: T,
transition_duration: std::time::Duration,
easing_fn: impl Fn(f32) -> f32 + 'static,
) -> Tweened<T> {
Tweened::new(initial, transition_duration, easing_fn)
}
pub trait Lerp {
fn lerp(&self, other: &Self, scalar: f32) -> Self;
}
macro_rules! impl_lerp_for_float {
($($f: path),*) => {
$(
impl Lerp for $f {
fn lerp(&self, other: &Self, scalar: f32) -> Self {
self + (other - self) * scalar as $f
}
}
)*
};
}
impl_lerp_for_float!(f32, f64);
macro_rules! impl_lerp_for_int {
($($i: path),*) => {
$(
impl Lerp for $i {
fn lerp(&self, other: &Self, scalar: f32) -> Self {
(*self as f32 + (other - self) as f32 * scalar).round() as $i
}
}
)*
};
}
impl_lerp_for_int!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
impl<T: Lerp + Clone, const N: usize> Lerp for [T; N] {
fn lerp(&self, other: &Self, scalar: f32) -> Self {
let mut tmp = (*self).clone();
for (t, other) in tmp.iter_mut().zip(other) {
*t = t.lerp(other, scalar);
}
tmp
}
}
pub struct Tweened<T: Lerp + Clone + 'static>(Signal<TweenedInner<T>>);
impl<T: Lerp + Clone> std::fmt::Debug for Tweened<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Tweened").finish()
}
}
struct TweenedInner<T: Lerp + Clone + 'static> {
value: Signal<T>,
is_tweening: Signal<bool>,
raf_state: Option<RafState>,
transition_duration_ms: f32,
easing_fn: Rc<dyn Fn(f32) -> f32>,
}
impl<T: Lerp + Clone> Tweened<T> {
pub(crate) fn new(
initial: T,
transition_duration: std::time::Duration,
easing_fn: impl Fn(f32) -> f32 + 'static,
) -> Self {
let value = create_signal(initial);
Self(create_signal(TweenedInner {
value,
is_tweening: create_signal(false),
raf_state: None,
transition_duration_ms: transition_duration.as_millis() as f32,
easing_fn: Rc::new(easing_fn),
}))
}
pub fn set(&self, _new_value: T) {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
{
use web_sys::js_sys::Date;
let start = self.signal().get_clone_untracked();
let easing_fn = Rc::clone(&self.0.with(|this| this.easing_fn.clone()));
let start_time = Date::now();
let signal = self.0.with(|this| this.value.clone());
let is_tweening = self.0.with(|this| this.is_tweening.clone());
let transition_duration_ms = self.0.with(|this| this.transition_duration_ms);
if let Some((running, _, stop)) = &self.0.with(|this| this.raf_state.clone()) {
if running.get_untracked() {
stop();
}
}
let (running, start, stop) = create_raf_loop(move || {
let now = Date::now();
let since_start = now - start_time;
let scalar = since_start as f32 / transition_duration_ms;
if now < start_time + transition_duration_ms as f64 {
signal.set(start.lerp(&_new_value, easing_fn(scalar)));
true
} else {
signal.set(_new_value.clone());
is_tweening.set(false);
false
}
});
start();
is_tweening.set(true);
self.0
.update(|this| this.raf_state = Some((running, start, stop)));
}
}
pub fn get(&self) -> T
where
T: Copy,
{
self.signal().get()
}
pub fn get_untracked(&self) -> T
where
T: Copy,
{
self.signal().get_untracked()
}
pub fn signal(&self) -> Signal<T> {
self.0.with(|this| this.value)
}
pub fn is_tweening(&self) -> bool {
self.0.with(|this| this.is_tweening.get())
}
}
impl<T: Lerp + Clone + 'static> Clone for Tweened<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Lerp + Clone + 'static> Copy for Tweened<T> {}
impl<T: Lerp + Clone + 'static> Clone for TweenedInner<T> {
fn clone(&self) -> Self {
Self {
value: self.value,
is_tweening: self.is_tweening,
raf_state: self.raf_state.clone(),
transition_duration_ms: self.transition_duration_ms,
easing_fn: Rc::clone(&self.easing_fn),
}
}
}