1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use embedder_traits::{GamepadSupportedHapticEffects, GamepadUpdateType};
9use js::typedarray::{Float64, Float64Array};
10
11use super::gamepadbuttonlist::GamepadButtonList;
12use super::gamepadhapticactuator::GamepadHapticActuator;
13use super::gamepadpose::GamepadPose;
14use crate::dom::bindings::buffer_source::HeapBufferSource;
15use crate::dom::bindings::codegen::Bindings::GamepadBinding::{GamepadHand, GamepadMethods};
16use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods;
17use crate::dom::bindings::inheritance::Castable;
18use crate::dom::bindings::num::Finite;
19use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
20use crate::dom::bindings::root::{Dom, DomRoot};
21use crate::dom::bindings::str::DOMString;
22use crate::dom::event::Event;
23use crate::dom::eventtarget::EventTarget;
24use crate::dom::gamepadevent::{GamepadEvent, GamepadEventType};
25use crate::dom::globalscope::GlobalScope;
26use crate::dom::window::Window;
27use crate::script_runtime::{CanGc, JSContext};
28
29const AXIS_TILT_THRESHOLD: f64 = 0.5;
32const BUTTON_PRESS_THRESHOLD: f64 = 30.0 / 255.0;
35
36#[dom_struct]
37pub(crate) struct Gamepad {
38 reflector_: Reflector,
39 gamepad_id: u32,
40 id: String,
41 index: Cell<i32>,
42 connected: Cell<bool>,
43 timestamp: Cell<f64>,
44 mapping_type: String,
45 #[ignore_malloc_size_of = "mozjs"]
46 axes: HeapBufferSource<Float64>,
47 buttons: Dom<GamepadButtonList>,
48 pose: Option<Dom<GamepadPose>>,
49 #[ignore_malloc_size_of = "Defined in rust-webvr"]
50 hand: GamepadHand,
51 axis_bounds: (f64, f64),
52 button_bounds: (f64, f64),
53 exposed: Cell<bool>,
54 vibration_actuator: Dom<GamepadHapticActuator>,
55}
56
57impl Gamepad {
58 #[allow(clippy::too_many_arguments)]
59 fn new_inherited(
60 gamepad_id: u32,
61 id: String,
62 index: i32,
63 connected: bool,
64 timestamp: f64,
65 mapping_type: String,
66 buttons: &GamepadButtonList,
67 pose: Option<&GamepadPose>,
68 hand: GamepadHand,
69 axis_bounds: (f64, f64),
70 button_bounds: (f64, f64),
71 vibration_actuator: &GamepadHapticActuator,
72 ) -> Gamepad {
73 Self {
74 reflector_: Reflector::new(),
75 gamepad_id,
76 id,
77 index: Cell::new(index),
78 connected: Cell::new(connected),
79 timestamp: Cell::new(timestamp),
80 mapping_type,
81 axes: HeapBufferSource::default(),
82 buttons: Dom::from_ref(buttons),
83 pose: pose.map(Dom::from_ref),
84 hand,
85 axis_bounds,
86 button_bounds,
87 exposed: Cell::new(false),
88 vibration_actuator: Dom::from_ref(vibration_actuator),
89 }
90 }
91
92 #[allow(clippy::too_many_arguments)]
98 pub(crate) fn new(
99 window: &Window,
100 gamepad_id: u32,
101 id: String,
102 mapping_type: String,
103 axis_bounds: (f64, f64),
104 button_bounds: (f64, f64),
105 supported_haptic_effects: GamepadSupportedHapticEffects,
106 xr: bool,
107 can_gc: CanGc,
108 ) -> DomRoot<Gamepad> {
109 let button_list = GamepadButtonList::init_buttons(window, can_gc);
110 let vibration_actuator =
111 GamepadHapticActuator::new(window, gamepad_id, supported_haptic_effects, can_gc);
112 let index = if xr { -1 } else { 0 };
113 let gamepad = reflect_dom_object(
114 Box::new(Gamepad::new_inherited(
115 gamepad_id,
116 id,
117 index,
118 true,
119 0.,
120 mapping_type,
121 &button_list,
122 None,
123 GamepadHand::_empty,
124 axis_bounds,
125 button_bounds,
126 &vibration_actuator,
127 )),
128 window,
129 can_gc,
130 );
131 gamepad.init_axes(can_gc);
132 gamepad
133 }
134}
135
136impl GamepadMethods<crate::DomTypeHolder> for Gamepad {
137 fn Id(&self) -> DOMString {
139 DOMString::from(self.id.clone())
140 }
141
142 fn Index(&self) -> i32 {
144 self.index.get()
145 }
146
147 fn Connected(&self) -> bool {
149 self.connected.get()
150 }
151
152 fn Timestamp(&self) -> Finite<f64> {
154 Finite::wrap(self.timestamp.get())
155 }
156
157 fn Mapping(&self) -> DOMString {
159 DOMString::from(self.mapping_type.clone())
160 }
161
162 fn Axes(&self, _cx: JSContext) -> Float64Array {
164 self.axes
165 .get_typed_array()
166 .expect("Failed to get gamepad axes.")
167 }
168
169 fn Buttons(&self) -> DomRoot<GamepadButtonList> {
171 DomRoot::from_ref(&*self.buttons)
172 }
173
174 fn VibrationActuator(&self) -> DomRoot<GamepadHapticActuator> {
176 DomRoot::from_ref(&*self.vibration_actuator)
177 }
178
179 fn Hand(&self) -> GamepadHand {
181 self.hand
182 }
183
184 fn GetPose(&self) -> Option<DomRoot<GamepadPose>> {
186 self.pose.as_ref().map(|p| DomRoot::from_ref(&**p))
187 }
188}
189
190#[allow(dead_code)]
191impl Gamepad {
192 pub(crate) fn gamepad_id(&self) -> u32 {
193 self.gamepad_id
194 }
195
196 pub(crate) fn update_connected(&self, connected: bool, has_gesture: bool, can_gc: CanGc) {
197 if self.connected.get() == connected {
198 return;
199 }
200 self.connected.set(connected);
201
202 let event_type = if connected {
203 GamepadEventType::Connected
204 } else {
205 GamepadEventType::Disconnected
206 };
207
208 if has_gesture {
209 self.notify_event(event_type, can_gc);
210 }
211 }
212
213 pub(crate) fn index(&self) -> i32 {
214 self.index.get()
215 }
216
217 pub(crate) fn update_index(&self, index: i32) {
218 self.index.set(index);
219 }
220
221 pub(crate) fn update_timestamp(&self, timestamp: f64) {
222 self.timestamp.set(timestamp);
223 }
224
225 pub(crate) fn notify_event(&self, event_type: GamepadEventType, can_gc: CanGc) {
226 let event =
227 GamepadEvent::new_with_type(self.global().as_window(), event_type, self, can_gc);
228 event
229 .upcast::<Event>()
230 .fire(self.global().as_window().upcast::<EventTarget>(), can_gc);
231 }
232
233 fn init_axes(&self, can_gc: CanGc) {
236 let initial_axes: Vec<f64> = vec![
237 0., 0., 0., 0., ];
242 self.axes
243 .set_data(GlobalScope::get_cx(), &initial_axes, can_gc)
244 .expect("Failed to set axes data on gamepad.")
245 }
246
247 #[allow(unsafe_code)]
248 pub(crate) fn map_and_normalize_axes(&self, axis_index: usize, value: f64) {
250 let numerator = value - self.axis_bounds.0;
252 let denominator = self.axis_bounds.1 - self.axis_bounds.0;
253 if denominator != 0.0 && denominator.is_finite() {
254 let normalized_value: f64 = 2.0 * numerator / denominator - 1.0;
255 if normalized_value.is_finite() {
256 let mut axis_vec = self
257 .axes
258 .typed_array_to_option()
259 .expect("Axes have not been initialized!");
260 unsafe {
261 axis_vec.as_mut_slice()[axis_index] = normalized_value;
262 }
263 } else {
264 warn!("Axis value is not finite!");
265 }
266 } else {
267 warn!("Axis bounds difference is either 0 or non-finite!");
268 }
269 }
270
271 pub(crate) fn map_and_normalize_buttons(&self, button_index: usize, value: f64) {
273 let numerator = value - self.button_bounds.0;
275 let denominator = self.button_bounds.1 - self.button_bounds.0;
276 if denominator != 0.0 && denominator.is_finite() {
277 let normalized_value: f64 = numerator / denominator;
278 if normalized_value.is_finite() {
279 let pressed = normalized_value >= BUTTON_PRESS_THRESHOLD;
280 if let Some(button) = self.buttons.IndexedGetter(button_index as u32) {
282 button.update(pressed, pressed, normalized_value);
283 }
284 } else {
285 warn!("Button value is not finite!");
286 }
287 } else {
288 warn!("Button bounds difference is either 0 or non-finite!");
289 }
290 }
291
292 pub(crate) fn exposed(&self) -> bool {
294 self.exposed.get()
295 }
296
297 pub(crate) fn set_exposed(&self, exposed: bool) {
299 self.exposed.set(exposed);
300 }
301
302 pub(crate) fn vibration_actuator(&self) -> &GamepadHapticActuator {
303 &self.vibration_actuator
304 }
305}
306
307pub(crate) fn contains_user_gesture(update_type: GamepadUpdateType) -> bool {
309 match update_type {
310 GamepadUpdateType::Axis(_, value) => value.abs() > AXIS_TILT_THRESHOLD,
311 GamepadUpdateType::Button(_, value) => value > BUTTON_PRESS_THRESHOLD,
312 }
313}