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