1use std::cell::{Cell, RefCell};
6
7use dom_struct::dom_struct;
8use embedder_traits::{GamepadSupportedHapticEffects, GamepadUpdateType};
9use js::rust::MutableHandleValue;
10
11use super::gamepadbutton::GamepadButton;
12use super::gamepadhapticactuator::GamepadHapticActuator;
13use super::gamepadpose::GamepadPose;
14use crate::dom::bindings::codegen::Bindings::GamepadBinding::{GamepadHand, GamepadMethods};
15use crate::dom::bindings::frozenarray::CachedFrozenArray;
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::num::Finite;
18use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
19use crate::dom::bindings::root::{Dom, DomRoot, DomSlice};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::event::Event;
22use crate::dom::eventtarget::EventTarget;
23use crate::dom::gamepadevent::{GamepadEvent, GamepadEventType};
24use crate::dom::window::Window;
25use crate::script_runtime::{CanGc, JSContext};
26
27const AXIS_TILT_THRESHOLD: f64 = 0.5;
30const BUTTON_PRESS_THRESHOLD: f64 = 30.0 / 255.0;
33
34#[dom_struct]
35pub(crate) struct Gamepad {
36 reflector_: Reflector,
37 gamepad_id: u32,
38 id: String,
39 index: Cell<i32>,
40 connected: Cell<bool>,
41 timestamp: Cell<f64>,
42 mapping_type: String,
43 #[ignore_malloc_size_of = "mozjs"]
44 frozen_buttons: CachedFrozenArray,
45 buttons: Vec<Dom<GamepadButton>>,
46 #[ignore_malloc_size_of = "mozjs"]
47 frozen_axes: CachedFrozenArray,
48 axes: RefCell<Vec<f64>>,
49 pose: Option<Dom<GamepadPose>>,
50 #[ignore_malloc_size_of = "Defined in rust-webvr"]
51 hand: GamepadHand,
52 axis_bounds: (f64, f64),
53 button_bounds: (f64, f64),
54 exposed: Cell<bool>,
55 vibration_actuator: Dom<GamepadHapticActuator>,
56}
57
58impl Gamepad {
59 #[allow(clippy::too_many_arguments)]
60 fn new_inherited(
61 gamepad_id: u32,
62 id: String,
63 index: i32,
64 connected: bool,
65 timestamp: f64,
66 mapping_type: String,
67 buttons: &[&GamepadButton],
68 pose: Option<&GamepadPose>,
69 hand: GamepadHand,
70 axis_bounds: (f64, f64),
71 button_bounds: (f64, f64),
72 vibration_actuator: &GamepadHapticActuator,
73 ) -> Gamepad {
74 Self {
75 reflector_: Reflector::new(),
76 gamepad_id,
77 id,
78 index: Cell::new(index),
79 connected: Cell::new(connected),
80 timestamp: Cell::new(timestamp),
81 mapping_type,
82 frozen_buttons: CachedFrozenArray::new(),
83 buttons: buttons
84 .iter()
85 .map(|button| Dom::from_ref(*button))
86 .collect(),
87 frozen_axes: CachedFrozenArray::new(),
88 axes: RefCell::new(Vec::new()),
89 pose: pose.map(Dom::from_ref),
90 hand,
91 axis_bounds,
92 button_bounds,
93 exposed: Cell::new(false),
94 vibration_actuator: Dom::from_ref(vibration_actuator),
95 }
96 }
97
98 #[allow(clippy::too_many_arguments)]
104 pub(crate) fn new(
105 window: &Window,
106 gamepad_id: u32,
107 id: String,
108 mapping_type: String,
109 axis_bounds: (f64, f64),
110 button_bounds: (f64, f64),
111 supported_haptic_effects: GamepadSupportedHapticEffects,
112 xr: bool,
113 can_gc: CanGc,
114 ) -> DomRoot<Gamepad> {
115 let buttons = Gamepad::init_buttons(window, can_gc);
116 rooted_vec!(let buttons <- buttons.iter().map(DomRoot::as_traced));
117 let vibration_actuator =
118 GamepadHapticActuator::new(window, gamepad_id, supported_haptic_effects, can_gc);
119 let index = if xr { -1 } else { 0 };
120 let gamepad = reflect_dom_object(
121 Box::new(Gamepad::new_inherited(
122 gamepad_id,
123 id,
124 index,
125 true,
126 0.,
127 mapping_type,
128 buttons.r(),
129 None,
130 GamepadHand::_empty,
131 axis_bounds,
132 button_bounds,
133 &vibration_actuator,
134 )),
135 window,
136 can_gc,
137 );
138 gamepad.init_axes();
139 gamepad
140 }
141}
142
143impl GamepadMethods<crate::DomTypeHolder> for Gamepad {
144 fn Id(&self) -> DOMString {
146 DOMString::from(self.id.clone())
147 }
148
149 fn Index(&self) -> i32 {
151 self.index.get()
152 }
153
154 fn Connected(&self) -> bool {
156 self.connected.get()
157 }
158
159 fn Timestamp(&self) -> Finite<f64> {
161 Finite::wrap(self.timestamp.get())
162 }
163
164 fn Mapping(&self) -> DOMString {
166 DOMString::from(self.mapping_type.clone())
167 }
168
169 fn Axes(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
171 self.frozen_axes
172 .get_or_init(|| self.axes.borrow().clone(), cx, retval, can_gc);
173 }
174
175 fn Buttons(&self, cx: JSContext, retval: MutableHandleValue) {
177 self.frozen_buttons.get_or_init(
178 || {
179 self.buttons
180 .iter()
181 .map(|b| DomRoot::from_ref(&**b))
182 .collect()
183 },
184 cx,
185 retval,
186 CanGc::deprecated_note(),
187 );
188 }
189
190 fn VibrationActuator(&self) -> DomRoot<GamepadHapticActuator> {
192 DomRoot::from_ref(&*self.vibration_actuator)
193 }
194
195 fn Hand(&self) -> GamepadHand {
197 self.hand
198 }
199
200 fn GetPose(&self) -> Option<DomRoot<GamepadPose>> {
202 self.pose.as_ref().map(|p| DomRoot::from_ref(&**p))
203 }
204}
205
206#[expect(dead_code)]
207impl Gamepad {
208 pub(crate) fn gamepad_id(&self) -> u32 {
209 self.gamepad_id
210 }
211
212 fn init_buttons(window: &Window, can_gc: CanGc) -> Vec<DomRoot<GamepadButton>> {
215 vec![
216 GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), GamepadButton::new(window, false, false, can_gc), ]
234 }
235
236 pub(crate) fn update_connected(&self, connected: bool, has_gesture: bool, can_gc: CanGc) {
237 if self.connected.get() == connected {
238 return;
239 }
240 self.connected.set(connected);
241
242 let event_type = if connected {
243 GamepadEventType::Connected
244 } else {
245 GamepadEventType::Disconnected
246 };
247
248 if has_gesture {
249 self.notify_event(event_type, can_gc);
250 }
251 }
252
253 pub(crate) fn index(&self) -> i32 {
254 self.index.get()
255 }
256
257 pub(crate) fn update_index(&self, index: i32) {
258 self.index.set(index);
259 }
260
261 pub(crate) fn update_timestamp(&self, timestamp: f64) {
262 self.timestamp.set(timestamp);
263 }
264
265 pub(crate) fn notify_event(&self, event_type: GamepadEventType, can_gc: CanGc) {
266 let event =
267 GamepadEvent::new_with_type(self.global().as_window(), event_type, self, can_gc);
268 event
269 .upcast::<Event>()
270 .fire(self.global().as_window().upcast::<EventTarget>(), can_gc);
271 }
272
273 fn init_axes(&self) {
276 *self.axes.borrow_mut() = vec![
277 0., 0., 0., 0., ];
282 }
283
284 pub(crate) fn map_and_normalize_axes(&self, axis_index: usize, value: f64) {
286 let numerator = value - self.axis_bounds.0;
288 let denominator = self.axis_bounds.1 - self.axis_bounds.0;
289 if denominator != 0.0 && denominator.is_finite() {
290 let normalized_value: f64 = 2.0 * numerator / denominator - 1.0;
291 if normalized_value.is_finite() {
292 self.axes.borrow_mut()[axis_index] = normalized_value;
293 self.frozen_axes.clear();
294 } else {
295 warn!("Axis value is not finite!");
296 }
297 } else {
298 warn!("Axis bounds difference is either 0 or non-finite!");
299 }
300 }
301
302 pub(crate) fn map_and_normalize_buttons(&self, button_index: usize, value: f64) {
304 let numerator = value - self.button_bounds.0;
306 let denominator = self.button_bounds.1 - self.button_bounds.0;
307 if denominator != 0.0 && denominator.is_finite() {
308 let normalized_value: f64 = numerator / denominator;
309 if normalized_value.is_finite() {
310 let pressed = normalized_value >= BUTTON_PRESS_THRESHOLD;
311 if let Some(button) = self.buttons.get(button_index) {
313 button.update(pressed, pressed, normalized_value);
314 self.frozen_buttons.clear();
315 }
316 } else {
317 warn!("Button value is not finite!");
318 }
319 } else {
320 warn!("Button bounds difference is either 0 or non-finite!");
321 }
322 }
323
324 pub(crate) fn exposed(&self) -> bool {
326 self.exposed.get()
327 }
328
329 pub(crate) fn set_exposed(&self, exposed: bool) {
331 self.exposed.set(exposed);
332 }
333
334 pub(crate) fn vibration_actuator(&self) -> &GamepadHapticActuator {
335 &self.vibration_actuator
336 }
337}
338
339pub(crate) fn contains_user_gesture(update_type: GamepadUpdateType) -> bool {
341 match update_type {
342 GamepadUpdateType::Axis(_, value) => value.abs() > AXIS_TILT_THRESHOLD,
343 GamepadUpdateType::Button(_, value) => value > BUTTON_PRESS_THRESHOLD,
344 }
345}