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_with_cx};
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)]
110 pub(crate) fn new(
111 cx: &mut JSContext,
112 window: &Window,
113 gamepad_id: u32,
114 id: String,
115 mapping_type: String,
116 axis_bounds: (f64, f64),
117 button_bounds: (f64, f64),
118 supported_haptic_effects: GamepadSupportedHapticEffects,
119 xr: bool,
120 ) -> DomRoot<Gamepad> {
121 let buttons = Gamepad::init_buttons(window, CanGc::from_cx(cx));
122 rooted_vec!(let buttons <- buttons.iter().map(DomRoot::as_traced));
123 let vibration_actuator =
124 GamepadHapticActuator::new(cx, window, gamepad_id, supported_haptic_effects);
125 let index = if xr { -1 } else { 0 };
126 let gamepad = reflect_dom_object_with_cx(
127 Box::new(Gamepad::new_inherited(
128 gamepad_id,
129 id,
130 index,
131 true,
132 0.,
133 mapping_type,
134 buttons.r(),
135 None,
136 GamepadHand::_empty,
137 axis_bounds,
138 button_bounds,
139 &vibration_actuator,
140 )),
141 window,
142 cx,
143 );
144 gamepad.init_axes();
145 gamepad
146 }
147}
148
149impl GamepadMethods<crate::DomTypeHolder> for Gamepad {
150 fn Id(&self) -> DOMString {
152 DOMString::from(self.id.clone())
153 }
154
155 fn Index(&self) -> i32 {
157 self.index.get()
158 }
159
160 fn Connected(&self) -> bool {
162 self.connected.get()
163 }
164
165 fn Timestamp(&self) -> Finite<f64> {
167 Finite::wrap(self.timestamp.get())
168 }
169
170 fn Mapping(&self) -> DOMString {
172 DOMString::from(self.mapping_type.clone())
173 }
174
175 fn Axes(&self, cx: &mut JSContext, retval: MutableHandleValue) {
177 self.frozen_axes.get_or_init(
178 || self.axes.borrow().clone(),
179 cx.into(),
180 retval,
181 CanGc::from_cx(cx),
182 );
183 }
184
185 fn Buttons(&self, cx: &mut JSContext, retval: MutableHandleValue) {
187 self.frozen_buttons.get_or_init(
188 || {
189 self.buttons
190 .iter()
191 .map(|b| DomRoot::from_ref(&**b))
192 .collect()
193 },
194 cx.into(),
195 retval,
196 CanGc::from_cx(cx),
197 );
198 }
199
200 fn VibrationActuator(&self) -> DomRoot<GamepadHapticActuator> {
202 DomRoot::from_ref(&*self.vibration_actuator)
203 }
204
205 fn Hand(&self) -> GamepadHand {
207 self.hand
208 }
209
210 fn GetPose(&self) -> Option<DomRoot<GamepadPose>> {
212 self.pose.as_ref().map(|p| DomRoot::from_ref(&**p))
213 }
214}
215
216#[expect(dead_code)]
217impl Gamepad {
218 pub(crate) fn gamepad_id(&self) -> u32 {
219 self.gamepad_id
220 }
221
222 fn init_buttons(window: &Window, can_gc: CanGc) -> Vec<DomRoot<GamepadButton>> {
225 vec![
226 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), ]
244 }
245
246 pub(crate) fn update_connected(&self, connected: bool) {
247 self.connected.set(connected);
248 }
249
250 pub(crate) fn index(&self) -> i32 {
251 self.index.get()
252 }
253
254 pub(crate) fn update_index(&self, index: i32) {
255 self.index.set(index);
256 }
257
258 pub(crate) fn update_timestamp(&self, timestamp: f64) {
259 self.timestamp.set(timestamp);
260 }
261
262 pub(crate) fn notify_event(
263 &self,
264 cx: &mut js::context::JSContext,
265 event_type: GamepadEventType,
266 ) {
267 let event = GamepadEvent::new_with_type(
268 self.global().as_window(),
269 event_type,
270 self,
271 CanGc::from_cx(cx),
272 );
273 event
274 .upcast::<Event>()
275 .fire(cx, self.global().as_window().upcast::<EventTarget>());
276 }
277
278 fn init_axes(&self) {
281 *self.axes.borrow_mut() = vec![
282 0., 0., 0., 0., ];
287 }
288
289 pub(crate) fn map_and_normalize_axes(&self, axis_index: usize, value: f64) {
291 let numerator = value - self.axis_bounds.0;
293 let denominator = self.axis_bounds.1 - self.axis_bounds.0;
294 if denominator != 0.0 && denominator.is_finite() {
295 let normalized_value: f64 = 2.0 * numerator / denominator - 1.0;
296 if normalized_value.is_finite() {
297 self.axes.borrow_mut()[axis_index] = normalized_value;
298 self.frozen_axes.clear();
299 } else {
300 warn!("Axis value is not finite!");
301 }
302 } else {
303 warn!("Axis bounds difference is either 0 or non-finite!");
304 }
305 }
306
307 pub(crate) fn map_and_normalize_buttons(&self, button_index: usize, value: f64) {
309 let numerator = value - self.button_bounds.0;
311 let denominator = self.button_bounds.1 - self.button_bounds.0;
312 if denominator != 0.0 && denominator.is_finite() {
313 let normalized_value: f64 = numerator / denominator;
314 if normalized_value.is_finite() {
315 let pressed = normalized_value >= BUTTON_PRESS_THRESHOLD;
316 if let Some(button) = self.buttons.get(button_index) {
318 button.update(pressed, pressed, normalized_value);
319 self.frozen_buttons.clear();
320 }
321 } else {
322 warn!("Button value is not finite!");
323 }
324 } else {
325 warn!("Button bounds difference is either 0 or non-finite!");
326 }
327 }
328
329 pub(crate) fn connected(&self) -> bool {
330 self.connected.get()
331 }
332
333 pub(crate) fn exposed(&self) -> bool {
335 self.exposed.get()
336 }
337
338 pub(crate) fn set_exposed(&self, exposed: bool) {
340 self.exposed.set(exposed);
341 }
342
343 pub(crate) fn vibration_actuator(&self) -> &GamepadHapticActuator {
344 &self.vibration_actuator
345 }
346}
347
348pub(crate) fn contains_user_gesture(update_type: GamepadUpdateType) -> bool {
350 match update_type {
351 GamepadUpdateType::Axis(_, value) => value.abs() > AXIS_TILT_THRESHOLD,
352 GamepadUpdateType::Button(_, value) => value > BUTTON_PRESS_THRESHOLD,
353 }
354}