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::{
17 GamepadHand, GamepadMappingType, GamepadMethods,
18};
19use crate::dom::bindings::frozenarray::CachedFrozenArray;
20use crate::dom::bindings::inheritance::Castable;
21use crate::dom::bindings::num::Finite;
22use crate::dom::bindings::reflector::DomGlobal;
23use crate::dom::bindings::root::{Dom, DomRoot, DomSlice};
24use crate::dom::bindings::str::DOMString;
25use crate::dom::event::Event;
26use crate::dom::eventtarget::EventTarget;
27use crate::dom::gamepadevent::{GamepadEvent, GamepadEventType};
28use crate::dom::window::Window;
29
30const AXIS_TILT_THRESHOLD: f64 = 0.5;
33const BUTTON_PRESS_THRESHOLD: f64 = 30.0 / 255.0;
36
37#[dom_struct]
38pub(crate) struct Gamepad {
39 reflector_: Reflector,
40 gamepad_id: u32,
41 id: String,
42 index: Cell<i32>,
43 connected: Cell<bool>,
44 timestamp: Cell<f64>,
45 mapping_type: GamepadMappingType,
46 #[ignore_malloc_size_of = "mozjs"]
47 frozen_buttons: CachedFrozenArray,
48 buttons: Vec<Dom<GamepadButton>>,
49 #[ignore_malloc_size_of = "mozjs"]
50 frozen_axes: CachedFrozenArray,
51 axes: RefCell<Vec<f64>>,
52 pose: Option<Dom<GamepadPose>>,
53 #[ignore_malloc_size_of = "Defined in rust-webvr"]
54 hand: GamepadHand,
55 axis_bounds: (f64, f64),
56 button_bounds: (f64, f64),
57 exposed: Cell<bool>,
58 vibration_actuator: Dom<GamepadHapticActuator>,
59}
60
61impl Gamepad {
62 #[allow(clippy::too_many_arguments)]
63 fn new_inherited(
64 gamepad_id: u32,
65 id: String,
66 index: i32,
67 connected: bool,
68 timestamp: f64,
69 mapping_type: GamepadMappingType,
70 buttons: &[&GamepadButton],
71 pose: Option<&GamepadPose>,
72 hand: GamepadHand,
73 axis_bounds: (f64, f64),
74 button_bounds: (f64, f64),
75 vibration_actuator: &GamepadHapticActuator,
76 ) -> Gamepad {
77 Self {
78 reflector_: Reflector::new(),
79 gamepad_id,
80 id,
81 index: Cell::new(index),
82 connected: Cell::new(connected),
83 timestamp: Cell::new(timestamp),
84 mapping_type,
85 frozen_buttons: CachedFrozenArray::new(),
86 buttons: buttons
87 .iter()
88 .map(|button| Dom::from_ref(*button))
89 .collect(),
90 frozen_axes: CachedFrozenArray::new(),
91 axes: RefCell::new(Vec::new()),
92 pose: pose.map(Dom::from_ref),
93 hand,
94 axis_bounds,
95 button_bounds,
96 exposed: Cell::new(false),
97 vibration_actuator: Dom::from_ref(vibration_actuator),
98 }
99 }
100
101 #[allow(clippy::too_many_arguments)]
111 pub(crate) fn new(
112 cx: &mut JSContext,
113 window: &Window,
114 gamepad_id: u32,
115 id: String,
116 mapping_type: GamepadMappingType,
117 axis_bounds: (f64, f64),
118 button_bounds: (f64, f64),
119 supported_haptic_effects: GamepadSupportedHapticEffects,
120 xr: bool,
121 ) -> DomRoot<Gamepad> {
122 let buttons = Gamepad::init_buttons(cx, window);
123 rooted_vec!(let buttons <- buttons.iter().map(DomRoot::as_traced));
124 let vibration_actuator =
125 GamepadHapticActuator::new(cx, window, gamepad_id, supported_haptic_effects);
126 let index = if xr { -1 } else { 0 };
127 let gamepad = reflect_dom_object(
128 cx,
129 Box::new(Gamepad::new_inherited(
130 gamepad_id,
131 id,
132 index,
133 true,
134 0.,
135 mapping_type,
136 buttons.r(),
137 None,
138 GamepadHand::_empty,
139 axis_bounds,
140 button_bounds,
141 &vibration_actuator,
142 )),
143 window,
144 );
145 gamepad.init_axes();
146 gamepad
147 }
148}
149
150impl GamepadMethods<crate::DomTypeHolder> for Gamepad {
151 fn Id(&self) -> DOMString {
153 DOMString::from(self.id.clone())
154 }
155
156 fn Index(&self) -> i32 {
158 self.index.get()
159 }
160
161 fn Connected(&self) -> bool {
163 self.connected.get()
164 }
165
166 fn Timestamp(&self) -> Finite<f64> {
168 Finite::wrap(self.timestamp.get())
169 }
170
171 fn Mapping(&self) -> GamepadMappingType {
173 self.mapping_type
174 }
175
176 fn Axes(&self, cx: &mut JSContext, retval: MutableHandleValue) {
178 self.frozen_axes
179 .get_or_init(cx, || self.axes.borrow().clone(), retval);
180 }
181
182 fn Buttons(&self, cx: &mut JSContext, retval: MutableHandleValue) {
184 self.frozen_buttons.get_or_init(
185 cx,
186 || {
187 self.buttons
188 .iter()
189 .map(|b| DomRoot::from_ref(&**b))
190 .collect()
191 },
192 retval,
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(cx: &mut JSContext, window: &Window) -> Vec<DomRoot<GamepadButton>> {
221 vec![
222 GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), GamepadButton::new(cx, window, false, false), ]
240 }
241
242 pub(crate) fn update_connected(&self, connected: bool) {
243 self.connected.set(connected);
244 }
245
246 pub(crate) fn index(&self) -> i32 {
247 self.index.get()
248 }
249
250 pub(crate) fn update_index(&self, index: i32) {
251 self.index.set(index);
252 }
253
254 pub(crate) fn update_timestamp(&self, timestamp: f64) {
255 self.timestamp.set(timestamp);
256 }
257
258 pub(crate) fn notify_event(
259 &self,
260 cx: &mut js::context::JSContext,
261 event_type: GamepadEventType,
262 ) {
263 let event = GamepadEvent::new_with_type(cx, self.global().as_window(), event_type, self);
264 event
265 .upcast::<Event>()
266 .fire(cx, self.global().as_window().upcast::<EventTarget>());
267 }
268
269 fn init_axes(&self) {
272 *self.axes.borrow_mut() = vec![
273 0., 0., 0., 0., ];
278 }
279
280 pub(crate) fn map_and_normalize_axes(&self, axis_index: usize, value: f64) {
282 let numerator = value - self.axis_bounds.0;
284 let denominator = self.axis_bounds.1 - self.axis_bounds.0;
285 if denominator != 0.0 && denominator.is_finite() {
286 let normalized_value: f64 = 2.0 * numerator / denominator - 1.0;
287 if normalized_value.is_finite() {
288 self.axes.borrow_mut()[axis_index] = normalized_value;
289 self.frozen_axes.clear();
290 } else {
291 warn!("Axis value is not finite!");
292 }
293 } else {
294 warn!("Axis bounds difference is either 0 or non-finite!");
295 }
296 }
297
298 pub(crate) fn map_and_normalize_buttons(&self, button_index: usize, value: f64) {
300 let numerator = value - self.button_bounds.0;
302 let denominator = self.button_bounds.1 - self.button_bounds.0;
303 if denominator != 0.0 && denominator.is_finite() {
304 let normalized_value: f64 = numerator / denominator;
305 if normalized_value.is_finite() {
306 let pressed = normalized_value >= BUTTON_PRESS_THRESHOLD;
307 if let Some(button) = self.buttons.get(button_index) {
309 button.update(pressed, pressed, normalized_value);
310 self.frozen_buttons.clear();
311 }
312 } else {
313 warn!("Button value is not finite!");
314 }
315 } else {
316 warn!("Button bounds difference is either 0 or non-finite!");
317 }
318 }
319
320 pub(crate) fn connected(&self) -> bool {
321 self.connected.get()
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}