use std::cell::{Cell, RefCell};
use std::rc::Rc;
use crate::reactive::*;
type RafState<'a> = (RcSignal<bool>, &'a dyn Fn(), &'a dyn Fn());
pub fn create_raf<'a>(cx: Scope<'a>, _f: impl FnMut() + 'a) -> RafState<'a> {
let running = create_ref(cx, create_rc_signal(false));
let start: &dyn Fn();
let stop: &dyn Fn();
#[cfg(all(target_arch = "wasm32", feature = "web"))]
{
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
let boxed: Box<dyn FnMut() + 'a> = Box::new(_f);
let extended: Box<dyn FnMut() + 'static> = unsafe { std::mem::transmute(boxed) };
let extended = RefCell::new(extended);
let scope_status = use_scope_status(cx);
let f = Rc::new(RefCell::new(None::<Closure<dyn Fn()>>));
let g = Rc::clone(&f);
*g.borrow_mut() = Some(Closure::wrap(Box::new({
let running = running.clone();
move || {
if *scope_status.get() && *running.get() {
extended.borrow_mut()();
web_sys::window()
.unwrap_throw()
.request_animation_frame(
f.borrow().as_ref().unwrap_throw().as_ref().unchecked_ref(),
)
.unwrap_throw();
}
}
})));
start = create_ref(cx, move || {
if !*running.get() {
running.set(true);
web_sys::window()
.unwrap_throw()
.request_animation_frame(
g.borrow().as_ref().unwrap_throw().as_ref().unchecked_ref(),
)
.unwrap_throw();
}
});
stop = create_ref(cx, || running.set(false));
}
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
{
start = create_ref(cx, || running.set(true));
stop = create_ref(cx, || running.set(false));
}
(running.clone(), start, stop)
}
pub fn create_raf_loop<'a>(cx: Scope<'a>, mut f: impl FnMut() -> bool + 'a) -> RafState<'a> {
let stop_shared = create_ref(cx, Cell::new(None::<&dyn Fn()>));
let (running, start, stop) = create_raf(cx, move || {
if !f() {
stop_shared.get().unwrap()();
}
});
stop_shared.set(Some(stop));
(running, start, stop)
}
pub fn create_tweened_signal<'a, T: Lerp + Clone + 'a>(
cx: Scope<'a>,
initial: T,
transition_duration: std::time::Duration,
easing_fn: impl Fn(f32) -> f32 + 'static,
) -> &'a Tweened<'a, T> {
create_ref(
cx,
Tweened::new(cx, 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<'a, T: Lerp + Clone>(Rc<RefCell<TweenedInner<'a, T>>>);
impl<'a, T: Lerp + Clone> std::fmt::Debug for Tweened<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Tweened").finish()
}
}
struct TweenedInner<'a, T: Lerp + Clone + 'a> {
cx: Scope<'a>,
value: RcSignal<T>,
is_tweening: RcSignal<bool>,
raf_state: Option<RafState<'a>>,
transition_duration_ms: f32,
easing_fn: Rc<dyn Fn(f32) -> f32>,
}
impl<'a, T: Lerp + Clone + 'a> Tweened<'a, T> {
pub(crate) fn new(
cx: Scope<'a>,
initial: T,
transition_duration: std::time::Duration,
easing_fn: impl Fn(f32) -> f32 + 'static,
) -> Self {
let value = create_rc_signal(initial);
Self(Rc::new(RefCell::new(TweenedInner {
cx,
value,
is_tweening: create_rc_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 js_sys::Date;
let start = self.signal().get_untracked().as_ref().clone();
let easing_fn = Rc::clone(&self.0.borrow().easing_fn);
let start_time = Date::now();
let signal = self.0.borrow().value.clone();
let is_tweening = self.0.borrow().is_tweening.clone();
let transition_duration_ms = self.0.borrow().transition_duration_ms;
if let Some((running, _, stop)) = &self.0.borrow_mut().raf_state {
if *running.get_untracked() {
stop();
}
}
let (running, start, stop) = create_raf_loop(self.0.borrow().cx, 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();
self.0.borrow().is_tweening.set(true);
self.0.borrow_mut().raf_state = Some((running, start, stop));
}
}
pub fn get(&self) -> Rc<T> {
self.signal().get()
}
pub fn get_untracked(&self) -> Rc<T> {
self.signal().get_untracked()
}
pub fn signal(&self) -> RcSignal<T> {
self.0.borrow().value.clone()
}
pub fn is_tweening(&self) -> bool {
*self.0.borrow().is_tweening.get()
}
}
impl<'a, T: Lerp + Clone + 'static> Clone for Tweened<'a, T> {
fn clone(&self) -> Self {
Self(Rc::clone(&self.0))
}
}
impl<'a, T: Lerp + Clone + 'static> Clone for TweenedInner<'a, T> {
fn clone(&self) -> Self {
Self {
cx: self.cx,
value: self.value.clone(),
is_tweening: self.is_tweening.clone(),
raf_state: self.raf_state.clone(),
transition_duration_ms: self.transition_duration_ms,
easing_fn: Rc::clone(&self.easing_fn),
}
}
}