use crate::errors::{Error, NativeError};
use js_sys::Object;
use log::info;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{Document, Element, Event, EventTarget, MouseEvent};
pub struct PointerLock<'a> {
trigger: &'a EventTarget,
target: &'a EventTarget,
click_cb: Closure<dyn FnMut(&Event)>,
change_cb: Closure<dyn FnMut(&Event)>,
change_cb_moz: Closure<dyn FnMut(&Event)>,
change_cb_webkit: Closure<dyn FnMut(&Event)>,
document: &'a Document,
}
impl<'a> PointerLock<'a> {
pub fn start<F, G, H>(
trigger: &'a Element,
target: &'a Element,
document: &'a Document,
on_start: F,
on_move: G,
on_end: H,
) -> Self
where
F: Fn(&Element) + 'static,
G: Fn(i32, i32) + 'static,
H: Fn() + 'static,
{
let is_locked = Rc::new(Cell::new(false));
let request_lock = {
let target = target.clone();
let is_locked = Rc::clone(&is_locked);
move |_: &_| {
if !is_locked.get() {
target.request_pointer_lock();
is_locked.set(true);
}
}
};
let on_pointer_move = Rc::new(move |evt: &Event| {
let evt: MouseEvent = JsValue::from(evt).into();
let (x, y) = (evt.movement_x(), evt.movement_y());
on_move(x, y);
});
let pointer_lock_change = {
let target = target.clone();
let document = document.clone();
let is_locked = Rc::clone(&is_locked);
let mut listener: Option<Closure<dyn FnMut(&Event)>> = None;
Rc::new(RefCell::new(move |initial_evt: &Event| {
let lock_enabled = match document.pointer_lock_element() {
None => false,
Some(element) => elements_are_equal(&element, &target),
};
is_locked.set(lock_enabled);
if lock_enabled {
on_start(&target);
info!("lock enabled!");
let move_cb = Closure::wrap(Box::new({
let f = Rc::clone(&on_pointer_move);
move |e: &Event| f(e)
}) as Box<dyn FnMut(&Event)>);
document
.add_event_listener_with_callback(
"mousemove",
move_cb.as_ref().unchecked_ref(),
)
.unwrap_throw();
listener = Some(move_cb);
} else {
on_end();
if let Some(l) = listener.take() {
document
.remove_event_listener_with_callback(
"mousemove",
l.as_ref().unchecked_ref(),
)
.unwrap_throw();
}
}
}))
};
let click_cb = Closure::wrap(Box::new(request_lock) as Box<dyn FnMut(&Event)>);
trigger
.add_event_listener_with_callback("click", click_cb.as_ref().unchecked_ref())
.unwrap_throw();
let change_cb = Closure::wrap(Box::new({
let f = pointer_lock_change.clone();
move |e: &Event| {
let f = &mut *f.borrow_mut();
f(e);
}
}) as Box<dyn FnMut(&Event)>);
document
.add_event_listener_with_callback(
"pointerlockchange",
change_cb.as_ref().unchecked_ref(),
)
.unwrap_throw();
let change_cb_moz = Closure::wrap(Box::new({
let f = pointer_lock_change.clone();
move |e: &Event| {
let f = &mut *f.borrow_mut();
f(e);
}
}) as Box<dyn FnMut(&Event)>);
document
.add_event_listener_with_callback(
"mozpointerlockchange",
change_cb_moz.as_ref().unchecked_ref(),
)
.unwrap_throw();
let change_cb_webkit = Closure::wrap(Box::new({
let f = pointer_lock_change.clone();
move |e: &Event| {
let f = &mut *f.borrow_mut();
f(e);
}
}) as Box<dyn FnMut(&Event)>);
document
.add_event_listener_with_callback(
"webkitpointerlockchange",
change_cb_webkit.as_ref().unchecked_ref(),
)
.unwrap_throw();
Self {
click_cb,
change_cb,
change_cb_moz,
change_cb_webkit,
target,
trigger,
document,
}
}
}
impl<'a> Drop for PointerLock<'a> {
fn drop(&mut self) {
self.trigger
.remove_event_listener_with_callback("click", self.click_cb.as_ref().unchecked_ref())
.unwrap_throw();
self.document
.remove_event_listener_with_callback(
"pointerlockchange",
self.change_cb.as_ref().unchecked_ref(),
)
.unwrap_throw();
self.document
.remove_event_listener_with_callback(
"mozpointerlockchange",
self.change_cb_moz.as_ref().unchecked_ref(),
)
.unwrap_throw();
self.document
.remove_event_listener_with_callback(
"webkitpointerlockchange",
self.change_cb_webkit.as_ref().unchecked_ref(),
)
.unwrap_throw();
info!("dropped!");
}
}
fn elements_are_equal(el1: &Element, el2: &Element) -> bool {
Object::is(&el1, &el2)
}