oxygengine-input-device-web 0.46.1

Web input devices module for Oxygengine
Documentation
use backend::closure::WebClosure;
use core::{ecs::Universe, Scalar};
use input::device::InputDevice;
use std::{
    any::Any,
    cell::{Ref, RefCell},
    collections::{HashMap, HashSet},
    rc::Rc,
};
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::*;

pub struct WebTouchPointer {
    pub x: Scalar,
    pub y: Scalar,
    pub force: Scalar,
    pub radius_x: Scalar,
    pub radius_y: Scalar,
    pub angle: Scalar,
}

pub struct WebTouchInputDevice {
    element: EventTarget,
    pointers: Rc<RefCell<HashMap<i32, WebTouchPointer>>>,
    started: HashSet<i32>,
    ended: HashSet<i32>,
    cached: HashSet<i32>,
    touch_start_closure: WebClosure,
    touch_end_closure: WebClosure,
    touch_move_closure: WebClosure,
    touch_cancel_closure: WebClosure,
}

unsafe impl Send for WebTouchInputDevice {}
unsafe impl Sync for WebTouchInputDevice {}

impl WebTouchInputDevice {
    pub fn new(element: EventTarget) -> Self {
        Self {
            element,
            pointers: Default::default(),
            cached: Default::default(),
            started: Default::default(),
            ended: Default::default(),
            touch_start_closure: Default::default(),
            touch_end_closure: Default::default(),
            touch_move_closure: Default::default(),
            touch_cancel_closure: Default::default(),
        }
    }

    pub fn touches(&self) -> Ref<HashMap<i32, WebTouchPointer>> {
        self.pointers.borrow()
    }

    pub fn active(&self) -> impl Iterator<Item = i32> + '_ {
        self.cached.iter().copied()
    }

    pub fn started(&self) -> impl Iterator<Item = i32> + '_ {
        self.started.iter().copied()
    }

    pub fn ended(&self) -> impl Iterator<Item = i32> + '_ {
        self.ended.iter().copied()
    }
}

impl InputDevice for WebTouchInputDevice {
    fn name(&self) -> &str {
        "touch"
    }

    fn on_register(&mut self) {
        macro_rules! impl_callback {
            ($name:expr) => {{
                let pointers = self.pointers.clone();
                let closure = Closure::wrap(Box::new(move |event: TouchEvent| {
                    let list = event.target_touches();
                    let count = list.length();
                    let mut pointers = pointers.borrow_mut();
                    pointers.clear();
                    pointers.reserve(count as _);
                    for i in 0..count {
                        let item = list.item(i).unwrap();
                        pointers.insert(
                            item.identifier(),
                            WebTouchPointer {
                                x: item.client_x() as _,
                                y: item.client_y() as _,
                                force: item.force() as _,
                                radius_x: item.radius_x() as _,
                                radius_y: item.radius_y() as _,
                                angle: item.rotation_angle() as _,
                            },
                        );
                    }
                }) as Box<dyn FnMut(_)>);
                self.element
                    .add_event_listener_with_callback($name, closure.as_ref().unchecked_ref())
                    .unwrap();
                WebClosure::acquire(closure)
            }};
        }

        self.touch_start_closure = impl_callback!("touchstart");
        self.touch_end_closure = impl_callback!("touchend");
        self.touch_move_closure = impl_callback!("touchmove");
        self.touch_cancel_closure = impl_callback!("touchcancel");
    }

    fn on_unregister(&mut self) {
        self.touch_start_closure.release();
        self.touch_end_closure.release();
        self.touch_move_closure.release();
        self.touch_cancel_closure.release();
    }

    fn process(&mut self, _: &mut Universe) {
        let pointers = self.pointers.borrow();
        self.started.clear();
        self.started.reserve(pointers.len());
        for id in pointers.keys() {
            if self.cached.contains(id) {
                self.started.insert(*id);
            }
        }
        self.ended.clear();
        self.ended.reserve(pointers.len());
        for id in &self.cached {
            if !pointers.contains_key(id) {
                self.ended.insert(*id);
            }
        }
        self.cached.clear();
        self.cached.reserve(pointers.len());
        self.cached.extend(pointers.keys().copied());
    }

    fn query_axis(&self, name: &str) -> Option<Scalar> {
        match name {
            "x" => self
                .pointers
                .borrow()
                .values()
                .next()
                .map(|pointer| pointer.x),
            "y" => self
                .pointers
                .borrow()
                .values()
                .next()
                .map(|pointer| pointer.y),
            "force" => self
                .pointers
                .borrow()
                .values()
                .next()
                .map(|pointer| pointer.force),
            "radius-x" => self
                .pointers
                .borrow()
                .values()
                .next()
                .map(|pointer| pointer.radius_x),
            "radius-y" => self
                .pointers
                .borrow()
                .values()
                .next()
                .map(|pointer| pointer.radius_y),
            "angle" => self
                .pointers
                .borrow()
                .values()
                .next()
                .map(|pointer| pointer.angle),
            _ => None,
        }
    }

    fn query_trigger(&self, name: &str) -> Option<bool> {
        match name {
            "touch" => Some(!self.pointers.borrow().is_empty()),
            _ => None,
        }
    }

    fn query_text(&self) -> Option<String> {
        None
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}