use std::{cell::RefCell, collections::HashMap, rc::Rc};
use wasm_bindgen::{JsCast, JsValue, closure::Closure};
use web_sys::HtmlElement;
use super::{
context::AnimationContext,
style::{CssProperty, StyleBuilder},
};
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum TriggerMode {
Continuous,
OncePerState,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum AnimationEventType {
MouseEnter,
MouseLeave,
MouseMove { throttle_ms: u32 },
GlobalMouseMove { throttle_ms: u32 },
Click,
Focus,
Blur,
}
pub struct AnimationTrigger {
pub event: AnimationEventType,
pub mode: TriggerMode,
pub debounce_ms: u32,
}
impl Default for AnimationTrigger {
fn default() -> Self {
Self {
event: AnimationEventType::MouseEnter,
mode: TriggerMode::OncePerState,
debounce_ms: 100,
}
}
}
impl AnimationTrigger {
pub fn new(event: AnimationEventType) -> Self {
Self {
event,
..Default::default()
}
}
pub fn with_mode(mut self, mode: TriggerMode) -> Self {
self.mode = mode;
self
}
pub fn with_debounce(mut self, debounce_ms: u32) -> Self {
self.debounce_ms = debounce_ms;
self
}
}
pub struct EventDrivenAnimation<'a> {
elements: &'a HashMap<String, JsValue>,
element: HtmlElement,
closures: Vec<Closure<dyn FnMut(web_sys::MouseEvent)>>,
}
impl<'a> EventDrivenAnimation<'a> {
pub fn new(elements: &'a HashMap<String, JsValue>, element: HtmlElement) -> Self {
Self {
elements,
element,
closures: Vec::new(),
}
}
pub fn on_mouse_enter<F>(mut self, f: F) -> Self
where
F: Fn(&AnimationContext) -> String + 'static,
{
let element = self.element.clone();
let elements = self.elements.clone();
let _trigger = AnimationTrigger::new(AnimationEventType::MouseEnter);
let closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
if let Ok(el) = element.clone().dyn_into::<HtmlElement>() {
let ctx = AnimationContext::new(&el);
let value = f(&ctx);
for (_name, js_val) in elements.iter() {
if let Ok(el) = js_val.clone().dyn_into::<HtmlElement>() {
StyleBuilder::new(&el)
.add(CssProperty::Transform, &value)
.apply();
}
}
}
}) as Box<dyn FnMut(_)>);
let _ = self
.element
.add_event_listener_with_callback("mouseenter", closure.as_ref().unchecked_ref());
self.closures.push(closure);
self
}
pub fn on_mouse_leave<F>(mut self, f: F) -> Self
where
F: Fn(&AnimationContext) -> String + 'static,
{
let element = self.element.clone();
let elements = self.elements.clone();
let closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
if let Ok(el) = element.clone().dyn_into::<HtmlElement>() {
let ctx = AnimationContext::new(&el);
let value = f(&ctx);
for (_name, js_val) in elements.iter() {
if let Ok(el) = js_val.clone().dyn_into::<HtmlElement>() {
StyleBuilder::new(&el)
.add(CssProperty::Transform, &value)
.apply();
}
}
}
}) as Box<dyn FnMut(_)>);
let _ = self
.element
.add_event_listener_with_callback("mouseleave", closure.as_ref().unchecked_ref());
self.closures.push(closure);
self
}
pub fn on_mouse_move<F>(mut self, throttle_ms: u32, f: F) -> Self
where
F: Fn(&AnimationContext) -> String + 'static,
{
let element = self.element.clone();
let elements = self.elements.clone();
let last_time = Rc::new(RefCell::new(0_f64));
let closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
let now = js_sys::Date::now();
let mut last = last_time.borrow_mut();
if now - *last < throttle_ms as f64 {
return;
}
*last = now;
if let Ok(el) = element.clone().dyn_into::<HtmlElement>() {
let ctx = AnimationContext::new(&el);
let value = f(&ctx);
for (_name, js_val) in elements.iter() {
if let Ok(el) = js_val.clone().dyn_into::<HtmlElement>() {
StyleBuilder::new(&el)
.add(CssProperty::Transform, &value)
.add(CssProperty::Transition, "none")
.apply();
}
}
}
}) as Box<dyn FnMut(_)>);
let _ = self
.element
.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref());
self.closures.push(closure);
self
}
pub fn on_global_mouse_move<F>(mut self, throttle_ms: u32, f: F) -> Self
where
F: Fn(&AnimationContext) -> String + 'static,
{
let element = self.element.clone();
let elements = self.elements.clone();
let window = match web_sys::window() {
Some(w) => w,
None => return self,
};
let last_time = Rc::new(RefCell::new(0_f64));
let closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
let now = js_sys::Date::now();
let mut last = last_time.borrow_mut();
if now - *last < throttle_ms as f64 {
return;
}
*last = now;
if let Ok(el) = element.clone().dyn_into::<HtmlElement>() {
let ctx = AnimationContext::new(&el);
let value = f(&ctx);
for (_name, js_val) in elements.iter() {
if let Ok(el) = js_val.clone().dyn_into::<HtmlElement>() {
StyleBuilder::new(&el)
.add(CssProperty::Transform, &value)
.add(CssProperty::Transition, "none")
.apply();
}
}
}
}) as Box<dyn FnMut(_)>);
let _ =
window.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref());
self.closures.push(closure);
self
}
pub fn on<F>(mut self, trigger: AnimationTrigger, f: F) -> Self
where
F: Fn(&AnimationContext) -> String + 'static,
{
match trigger.event {
AnimationEventType::MouseEnter => {
let element = self.element.clone();
let elements = self.elements.clone();
let closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
if let Ok(el) = element.clone().dyn_into::<HtmlElement>() {
let ctx = AnimationContext::new(&el);
let value = f(&ctx);
for (_name, js_val) in elements.iter() {
if let Ok(el) = js_val.clone().dyn_into::<HtmlElement>() {
StyleBuilder::new(&el)
.add(CssProperty::Transform, &value)
.apply();
}
}
}
}) as Box<dyn FnMut(_)>);
let _ = self.element.add_event_listener_with_callback(
"mouseenter",
closure.as_ref().unchecked_ref(),
);
self.closures.push(closure);
}
AnimationEventType::MouseLeave => {
let element = self.element.clone();
let elements = self.elements.clone();
let closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
if let Ok(el) = element.clone().dyn_into::<HtmlElement>() {
let ctx = AnimationContext::new(&el);
let value = f(&ctx);
for (_name, js_val) in elements.iter() {
if let Ok(el) = js_val.clone().dyn_into::<HtmlElement>() {
StyleBuilder::new(&el)
.add(CssProperty::Transform, &value)
.apply();
}
}
}
}) as Box<dyn FnMut(_)>);
let _ = self.element.add_event_listener_with_callback(
"mouseleave",
closure.as_ref().unchecked_ref(),
);
self.closures.push(closure);
}
AnimationEventType::MouseMove { throttle_ms } => {
return self.on_mouse_move(throttle_ms, f);
}
AnimationEventType::GlobalMouseMove { throttle_ms } => {
return self.on_global_mouse_move(throttle_ms, f);
}
_ => {}
}
self
}
pub fn build(mut self) {
for closure in self.closures.drain(..) {
closure.forget();
}
}
}