oxygengine_input_device_web/
touch.rs

1use backend::closure::WebClosure;
2use core::{ecs::Universe, Scalar};
3use input::device::InputDevice;
4use std::{
5    any::Any,
6    cell::{Ref, RefCell},
7    collections::{HashMap, HashSet},
8    rc::Rc,
9};
10use wasm_bindgen::{prelude::*, JsCast};
11use web_sys::*;
12
13pub struct WebTouchPointer {
14    pub x: Scalar,
15    pub y: Scalar,
16    pub force: Scalar,
17    pub radius_x: Scalar,
18    pub radius_y: Scalar,
19    pub angle: Scalar,
20}
21
22pub struct WebTouchInputDevice {
23    element: EventTarget,
24    pointers: Rc<RefCell<HashMap<i32, WebTouchPointer>>>,
25    started: HashSet<i32>,
26    ended: HashSet<i32>,
27    cached: HashSet<i32>,
28    touch_start_closure: WebClosure,
29    touch_end_closure: WebClosure,
30    touch_move_closure: WebClosure,
31    touch_cancel_closure: WebClosure,
32}
33
34unsafe impl Send for WebTouchInputDevice {}
35unsafe impl Sync for WebTouchInputDevice {}
36
37impl WebTouchInputDevice {
38    pub fn new(element: EventTarget) -> Self {
39        Self {
40            element,
41            pointers: Default::default(),
42            cached: Default::default(),
43            started: Default::default(),
44            ended: Default::default(),
45            touch_start_closure: Default::default(),
46            touch_end_closure: Default::default(),
47            touch_move_closure: Default::default(),
48            touch_cancel_closure: Default::default(),
49        }
50    }
51
52    pub fn touches(&self) -> Ref<HashMap<i32, WebTouchPointer>> {
53        self.pointers.borrow()
54    }
55
56    pub fn active(&self) -> impl Iterator<Item = i32> + '_ {
57        self.cached.iter().copied()
58    }
59
60    pub fn started(&self) -> impl Iterator<Item = i32> + '_ {
61        self.started.iter().copied()
62    }
63
64    pub fn ended(&self) -> impl Iterator<Item = i32> + '_ {
65        self.ended.iter().copied()
66    }
67}
68
69impl InputDevice for WebTouchInputDevice {
70    fn name(&self) -> &str {
71        "touch"
72    }
73
74    fn on_register(&mut self) {
75        macro_rules! impl_callback {
76            ($name:expr) => {{
77                let pointers = self.pointers.clone();
78                let closure = Closure::wrap(Box::new(move |event: TouchEvent| {
79                    let list = event.target_touches();
80                    let count = list.length();
81                    let mut pointers = pointers.borrow_mut();
82                    pointers.clear();
83                    pointers.reserve(count as _);
84                    for i in 0..count {
85                        let item = list.item(i).unwrap();
86                        pointers.insert(
87                            item.identifier(),
88                            WebTouchPointer {
89                                x: item.client_x() as _,
90                                y: item.client_y() as _,
91                                force: item.force() as _,
92                                radius_x: item.radius_x() as _,
93                                radius_y: item.radius_y() as _,
94                                angle: item.rotation_angle() as _,
95                            },
96                        );
97                    }
98                }) as Box<dyn FnMut(_)>);
99                self.element
100                    .add_event_listener_with_callback($name, closure.as_ref().unchecked_ref())
101                    .unwrap();
102                WebClosure::acquire(closure)
103            }};
104        }
105
106        self.touch_start_closure = impl_callback!("touchstart");
107        self.touch_end_closure = impl_callback!("touchend");
108        self.touch_move_closure = impl_callback!("touchmove");
109        self.touch_cancel_closure = impl_callback!("touchcancel");
110    }
111
112    fn on_unregister(&mut self) {
113        self.touch_start_closure.release();
114        self.touch_end_closure.release();
115        self.touch_move_closure.release();
116        self.touch_cancel_closure.release();
117    }
118
119    fn process(&mut self, _: &mut Universe) {
120        let pointers = self.pointers.borrow();
121        self.started.clear();
122        self.started.reserve(pointers.len());
123        for id in pointers.keys() {
124            if self.cached.contains(id) {
125                self.started.insert(*id);
126            }
127        }
128        self.ended.clear();
129        self.ended.reserve(pointers.len());
130        for id in &self.cached {
131            if !pointers.contains_key(id) {
132                self.ended.insert(*id);
133            }
134        }
135        self.cached.clear();
136        self.cached.reserve(pointers.len());
137        self.cached.extend(pointers.keys().copied());
138    }
139
140    fn query_axis(&self, name: &str) -> Option<Scalar> {
141        match name {
142            "x" => self
143                .pointers
144                .borrow()
145                .values()
146                .next()
147                .map(|pointer| pointer.x),
148            "y" => self
149                .pointers
150                .borrow()
151                .values()
152                .next()
153                .map(|pointer| pointer.y),
154            "force" => self
155                .pointers
156                .borrow()
157                .values()
158                .next()
159                .map(|pointer| pointer.force),
160            "radius-x" => self
161                .pointers
162                .borrow()
163                .values()
164                .next()
165                .map(|pointer| pointer.radius_x),
166            "radius-y" => self
167                .pointers
168                .borrow()
169                .values()
170                .next()
171                .map(|pointer| pointer.radius_y),
172            "angle" => self
173                .pointers
174                .borrow()
175                .values()
176                .next()
177                .map(|pointer| pointer.angle),
178            _ => None,
179        }
180    }
181
182    fn query_trigger(&self, name: &str) -> Option<bool> {
183        match name {
184            "touch" => Some(!self.pointers.borrow().is_empty()),
185            _ => None,
186        }
187    }
188
189    fn query_text(&self) -> Option<String> {
190        None
191    }
192
193    fn as_any(&self) -> &dyn Any {
194        self
195    }
196}