use std::cell::RefCell;
use std::rc::Rc;
use repose_core::{
Color, Rect, Size, Vec2,
animation::{AnimatedValue, AnimationSpec, KeyframesSpec, RepeatableSpec, SplineKeyframes},
animation_driver, remember_state_with_key, request_frame,
};
pub fn animate_f32_from(
key: impl Into<String>,
initial: f32,
target: f32,
spec: AnimationSpec,
) -> f32 {
let key = key.into();
let anim_key = format!("anim:f32:{key}");
let anim = remember_state_with_key(&anim_key, || AnimatedValue::new(initial, spec));
let last = remember_state_with_key(format!("anim:f32_last:{key}"), || f32::NAN);
animation_driver::touch(&anim_key);
let mut a = anim.borrow_mut();
let mut lt = last.borrow_mut();
let should_set_target = lt.is_nan() || (*lt - target).abs() > 1e-6;
if should_set_target {
a.set_spec(spec);
a.set_target(target);
*lt = target;
drop(lt);
let reg_key = anim_key;
let reg_anim = anim.clone();
animation_driver::register(
reg_key,
Rc::new(RefCell::new(move || reg_anim.borrow_mut().update())),
);
request_frame();
*a.get()
} else {
let needs_reregister = !animation_driver::is_registered(&anim_key) && a.is_animating();
let cur = *a.get();
drop(lt);
drop(a);
if needs_reregister {
let reg_anim = anim.clone();
animation_driver::register(
anim_key,
Rc::new(RefCell::new(move || reg_anim.borrow_mut().update())),
);
request_frame();
}
cur
}
}
pub fn animate_f32(key: impl Into<String>, target: f32, spec: AnimationSpec) -> f32 {
animate_f32_from(key, target, target, spec)
}
macro_rules! animate_from_impl {
($name:ident, $type:ty, $prefix:expr) => {
pub fn $name(
key: impl Into<String>,
initial: $type,
target: $type,
spec: AnimationSpec,
) -> $type {
let key = key.into();
let anim_key = format!("anim:{}:{}", $prefix, key);
let anim = remember_state_with_key(&anim_key, || AnimatedValue::new(initial, spec));
let last =
remember_state_with_key(format!("anim:{}_last:{}", $prefix, key), || None::<$type>);
repose_core::animation_driver::touch(&anim_key);
let mut a = anim.borrow_mut();
let mut lt = last.borrow_mut();
if lt.is_none() || lt.as_ref().unwrap() != &target {
a.set_spec(spec);
a.set_target(target);
*lt = Some(target);
drop(lt);
let reg_key = anim_key.clone();
let reg_anim = anim.clone();
repose_core::animation_driver::register(
reg_key,
std::rc::Rc::new(std::cell::RefCell::new(move || {
reg_anim.borrow_mut().update()
})),
);
repose_core::request_frame();
*a.get()
} else {
let needs_reregister =
!repose_core::animation_driver::is_registered(&anim_key) && a.is_animating();
let cur = *a.get();
drop(lt);
drop(a);
if needs_reregister {
let reg_anim = anim.clone();
repose_core::animation_driver::register(
anim_key,
std::rc::Rc::new(std::cell::RefCell::new(move || {
reg_anim.borrow_mut().update()
})),
);
repose_core::request_frame();
}
cur
}
}
};
}
animate_from_impl!(animate_color_from, Color, "color");
animate_from_impl!(animate_vec2_from, Vec2, "vec2");
animate_from_impl!(animate_size_from, Size, "size");
animate_from_impl!(animate_rect_from, Rect, "rect");
pub fn animate_color(key: impl Into<String>, target: Color, spec: AnimationSpec) -> Color {
animate_color_from(key, target, target, spec)
}
pub fn animate_vec2(key: impl Into<String>, target: Vec2, spec: AnimationSpec) -> Vec2 {
animate_vec2_from(key, target, target, spec)
}
pub fn animate_size(key: impl Into<String>, target: Size, spec: AnimationSpec) -> Size {
animate_size_from(key, target, target, spec)
}
pub fn animate_rect(key: impl Into<String>, target: Rect, spec: AnimationSpec) -> Rect {
animate_rect_from(key, target, target, spec)
}
pub fn animate_keyframes(
key: impl Into<String>,
keyframes: KeyframesSpec<f32>,
spec: AnimationSpec,
) -> f32 {
let key = key.into();
let anim_key = format!("anim:kf:{key}");
let anim = remember_state_with_key(&anim_key, || AnimatedValue::new(0.0, spec));
let last = remember_state_with_key(format!("anim:kf_last:{key}"), || None::<String>);
animation_driver::touch(&anim_key);
let fingerprint = format!("{:?}|{:?}", keyframes.keyframes, spec);
let should_restart = {
let lt = last.borrow();
lt.as_ref() != Some(&fingerprint)
};
let mut a = anim.borrow_mut();
if should_restart {
a.set_spec(spec);
a.set_keyframes(keyframes);
*last.borrow_mut() = Some(fingerprint);
drop(a);
let reg_anim = anim.clone();
animation_driver::register(
anim_key.clone(),
Rc::new(RefCell::new(move || reg_anim.borrow_mut().update())),
);
request_frame();
*anim.borrow().get()
} else {
let needs_reregister = !animation_driver::is_registered(&anim_key) && a.is_animating();
let cur = *a.get();
drop(a);
if needs_reregister {
let reg_anim = anim.clone();
animation_driver::register(
anim_key,
Rc::new(RefCell::new(move || reg_anim.borrow_mut().update())),
);
request_frame();
}
cur
}
}
pub fn animate_spline_keyframes(
key: impl Into<String>,
keyframes: SplineKeyframes,
spec: AnimationSpec,
) -> f32 {
let key = key.into();
let anim = remember_state_with_key(format!("anim:spkf_progress:{key}"), || {
AnimatedValue::new(0.0, spec)
});
let spline = remember_state_with_key(format!("anim:spkf:{key}"), || keyframes.clone());
let anim_key = format!("anim:spkf_progress:{key}");
let last = remember_state_with_key(format!("anim:spkf_last:{key}"), || None::<String>);
animation_driver::touch(&anim_key);
let fingerprint = format!("{}|{spec:?}", keyframes.fingerprint());
let should_restart = {
let lt = last.borrow();
lt.as_ref() != Some(&fingerprint)
};
let progress = {
let mut a = anim.borrow_mut();
if should_restart {
a.set_spec(spec);
a.set_target(1.0);
*last.borrow_mut() = Some(fingerprint);
*spline.borrow_mut() = keyframes;
drop(a);
let reg_anim = anim.clone();
animation_driver::register(
anim_key.clone(),
Rc::new(RefCell::new(move || reg_anim.borrow_mut().update())),
);
request_frame();
*anim.borrow().get()
} else {
let needs_reregister = !animation_driver::is_registered(&anim_key) && a.is_animating();
let cur = *a.get();
drop(a);
if needs_reregister {
let reg_anim = anim.clone();
animation_driver::register(
anim_key,
Rc::new(RefCell::new(move || reg_anim.borrow_mut().update())),
);
request_frame();
}
cur
}
};
spline.borrow().evaluate(progress)
}
fn with_infinite_repeat(spec: AnimationSpec) -> AnimationSpec {
if spec.repeat.is_none() {
spec.repeated(RepeatableSpec::infinite().reverse())
} else {
spec
}
}
pub struct InfiniteTransition {
_private: (),
}
pub fn remember_infinite_transition() -> InfiniteTransition {
InfiniteTransition { _private: () }
}
macro_rules! inf_method {
($method:ident, $from_fn:ident, $type:ty) => {
pub fn $method(
&self,
key: impl Into<String>,
initial: $type,
target: $type,
spec: AnimationSpec,
) -> $type {
let key = key.into();
$from_fn(
format!("inf:{}", key),
initial,
target,
with_infinite_repeat(spec),
)
}
};
}
impl InfiniteTransition {
inf_method!(animate_float, animate_f32_from, f32);
inf_method!(animate_color, animate_color_from, Color);
inf_method!(animate_vec2, animate_vec2_from, Vec2);
inf_method!(animate_size, animate_size_from, Size);
inf_method!(animate_rect, animate_rect_from, Rect);
}
pub struct TransitionScope<T> {
key: String,
spec: AnimationSpec,
state: Rc<RefCell<T>>,
}
pub fn update_transition<T>(
key: impl Into<String>,
target_state: T,
spec: AnimationSpec,
) -> TransitionScope<T>
where
T: PartialEq + Clone + 'static,
{
let key = key.into();
let state: Rc<RefCell<T>> =
remember_state_with_key(format!("tr_state:{key}"), || target_state.clone());
if *state.borrow() != target_state {
*state.borrow_mut() = target_state;
}
TransitionScope { key, spec, state }
}
macro_rules! tr_method {
($method:ident, $fn:ident, $type:ty) => {
pub fn $method<F>(&self, child_key: impl Into<String>, map: F) -> $type
where
F: Fn(&T) -> $type,
{
let target = map(&*self.state.borrow());
$fn(
format!("tr:{}:{}", self.key, child_key.into()),
target,
self.spec,
)
}
};
}
impl<T> TransitionScope<T> {
tr_method!(animate_float, animate_f32, f32);
tr_method!(animate_color, animate_color, Color);
tr_method!(animate_vec2, animate_vec2, Vec2);
tr_method!(animate_size, animate_size, Size);
tr_method!(animate_rect, animate_rect, Rect);
}