use std::{cell::RefCell, rc::Rc};
use cranpose_app_shell::{AppShell, PointerSource};
use cranpose_platform_web::WebPlatform;
use cranpose_render_wgpu::WgpuRenderer;
use cranpose_ui_graphics::Point;
use wasm_bindgen::{JsCast, prelude::*};
use web_sys::{HtmlCanvasElement, PointerEvent};
type App = Rc<RefCell<AppShell<WgpuRenderer>>>;
type Handler = fn(&Pointers, &PointerEvent, &mut AppShell<WgpuRenderer>, Point);
#[derive(Clone)]
struct Pointers {
app: App,
platform: Rc<RefCell<WebPlatform>>,
canvas: HtmlCanvasElement,
request_frame: Rc<dyn Fn()>,
secondaries: Rc<RefCell<Vec<u64>>>,
}
pub(crate) fn listen(
canvas: &HtmlCanvasElement,
app: App,
platform: Rc<RefCell<WebPlatform>>,
request_frame: Rc<dyn Fn()>,
) -> Result<(), JsValue> {
let pointers = Pointers {
app,
platform,
canvas: canvas.clone(),
request_frame,
secondaries: Rc::default(),
};
pointers.on("pointermove", Pointers::moved)?;
pointers.on("pointerdown", Pointers::pressed)?;
pointers.on("pointerup", Pointers::released)?;
pointers.on("pointercancel", Pointers::cancelled)
}
pub(crate) fn web_pointer_source(event: &PointerEvent) -> PointerSource {
match event.pointer_type().as_str() {
"touch" => PointerSource::Touch,
"pen" => PointerSource::Stylus,
"mouse" => PointerSource::Mouse,
_ => PointerSource::Unknown,
}
}
fn secondary_id(event: &PointerEvent) -> Option<u64> {
(!event.is_primary()).then(|| u64::from(event.pointer_id().unsigned_abs()) + 1)
}
impl Pointers {
fn on(&self, name: &str, handle: Handler) -> Result<(), JsValue> {
let pointers = self.clone();
let closure = Closure::wrap(Box::new(move |event: PointerEvent| {
event.prevent_default();
let at = pointers
.platform
.borrow()
.pointer_position(f64::from(event.offset_x()), f64::from(event.offset_y()));
if let Ok(mut app) = pointers.app.try_borrow_mut() {
app.set_pointer_source(web_pointer_source(&event));
app.set_modifiers(crate::web::web_modifiers(&event));
handle(&pointers, &event, &mut app, at);
(pointers.request_frame)();
}
}) as Box<dyn FnMut(PointerEvent)>);
self.canvas
.add_event_listener_with_callback(name, closure.as_ref().unchecked_ref())?;
closure.forget();
Ok(())
}
fn moved(&self, event: &PointerEvent, app: &mut AppShell<WgpuRenderer>, at: Point) {
match secondary_id(event) {
Some(id) => {
app.secondary_pointer_moved(id, at.x, at.y, None);
}
None => {
app.set_cursor(at.x, at.y);
}
}
}
fn pressed(&self, event: &PointerEvent, app: &mut AppShell<WgpuRenderer>, at: Point) {
let _ = self.canvas.set_pointer_capture(event.pointer_id());
match secondary_id(event) {
Some(id) => {
self.secondaries.borrow_mut().push(id);
app.secondary_pointer_pressed(id, at.x, at.y, None);
}
None => {
let event_time = app.realtime_pointer_event_time(None);
app.set_cursor_at_event_time(at.x, at.y, event_time);
app.pointer_pressed_at_event_time(event_time);
}
}
}
fn released(&self, event: &PointerEvent, app: &mut AppShell<WgpuRenderer>, at: Point) {
match secondary_id(event) {
Some(id) => self.release_secondary(app, id, at),
None => {
let lifted = std::mem::take(&mut *self.secondaries.borrow_mut());
for id in lifted {
app.secondary_pointer_released(id, at.x, at.y, None);
}
let event_time = app.realtime_pointer_event_time(None);
app.pointer_released_at_position_event_time(at.x, at.y, event_time);
}
}
let _ = self.canvas.release_pointer_capture(event.pointer_id());
}
fn cancelled(&self, event: &PointerEvent, app: &mut AppShell<WgpuRenderer>, at: Point) {
match secondary_id(event) {
Some(id) => self.release_secondary(app, id, at),
None => {
self.secondaries.borrow_mut().clear();
app.cancel_gesture();
}
}
let _ = self.canvas.release_pointer_capture(event.pointer_id());
}
fn release_secondary(&self, app: &mut AppShell<WgpuRenderer>, id: u64, at: Point) {
self.secondaries.borrow_mut().retain(|&finger| finger != id);
app.secondary_pointer_released(id, at.x, at.y, None);
}
}