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;
27
28const AXIS_TILT_THRESHOLD: f64 = 0.5;
31const BUTTON_PRESS_THRESHOLD: f64 = 30.0 / 255.0;
34
35#[dom_struct]
36pub(crate) struct Gamepad {
37 reflector_: Reflector,
38 gamepad_id: u32,
39 id: String,
40 index: Cell<i32>,
41 connected: Cell<bool>,
42 timestamp: Cell<f64>,
43 mapping_type: String,
44 #[ignore_malloc_size_of = "mozjs"]
45 frozen_buttons: CachedFrozenArray,
46 buttons: Vec<Dom<GamepadButton>>,
47 #[ignore_malloc_size_of = "mozjs"]
48 frozen_axes: CachedFrozenArray,
49 axes: RefCell<Vec<f64>>,
50 pose: Option<Dom<GamepadPose>>,
51 #[ignore_malloc_size_of = "Defined in rust-webvr"]
52 hand: GamepadHand,
53 axis_bounds: (f64, f64),
54 button_bounds: (f64, f64),
55 exposed: Cell<bool>,
56 vibration_actuator: Dom<GamepadHapticActuator>,
57}
58
59impl Gamepad {
60 #[allow(clippy::too_many_arguments)]
61 fn new_inherited(
62 gamepad_id: u32,
63 id: String,
64 index: i32,
65 connected: bool,
66 timestamp: f64,
67 mapping_type: String,
68 buttons: &[&GamepadButton],
69 pose: Option<&GamepadPose>,
70 hand: GamepadHand,
71 axis_bounds: (f64, f64),
72 button_bounds: (f64, f64),
73 vibration_actuator: &GamepadHapticActuator,
74 ) -> Gamepad {
75 Self {
76 reflector_: Reflector::new(),
77 gamepad_id,
78 id,
79 index: Cell::new(index),
80 connected: Cell::new(connected),
81 timestamp: Cell::new(timestamp),
82 mapping_type,
83 frozen_buttons: CachedFrozenArray::new(),
84 buttons: buttons
85 .iter()
86 .map(|button| Dom::from_ref(*button))
87 .collect(),
88 frozen_axes: CachedFrozenArray::new(),
89 axes: RefCell::new(Vec::new()),
90 pose: pose.map(Dom::from_ref),
91 hand,
92 axis_bounds,
93 button_bounds,
94 exposed: Cell::new(false),
95 vibration_actuator: Dom::from_ref(vibration_actuator),
96 }
97 }
98
99 #[allow(clippy::too_many_arguments)]
109 pub(crate) fn new(
110 cx: &mut JSContext,
111 window: &Window,
112 gamepad_id: u32,
113 id: String,
114 mapping_type: String,
115 axis_bounds: (f64, f64),
116 button_bounds: (f64, f64),
117 supported_haptic_effects: GamepadSupportedHapticEffects,
118 xr: bool,
119 ) -> DomRoot<Gamepad> {
120 let buttons = Gamepad::init_buttons(cx, window);
121 rooted_vec!(let buttons <- buttons.iter().map(DomRoot::as_traced));
122 let vibration_actuator =
123 GamepadHapticActuator::new(cx, window, gamepad_id, supported_haptic_effects);
124 let index = if xr { -1 } else { 0 };
125 let gamepad = reflect_dom_object_with_cx(
126 Box::new(Gamepad::new_inherited(
127 gamepad_id,
128 id,
129 index,
130 true,
131 0.,
132 mapping_type,
133 buttons.r(),
134 None,
135 GamepadHand::_empty,
136 axis_bounds,
137 button_bounds,
138 &vibration_actuator,
139 )),
140 window,
141 cx,
142 );
143 gamepad.init_axes();
144 gamepad
145 }
146}
147
148impl GamepadMethods<crate::DomTypeHolder> for Gamepad {
149 fn Id(&self) -> DOMString {
151 DOMString::from(self.id.clone())
152 }
153
154 fn Index(&self) -> i32 {
156 self.index.get()
157 }
158
159 fn Connected(&self) -> bool {
161 self.connected.get()
162 }
163
164 fn Timestamp(&self) -> Finite<f64> {
166 Finite::wrap(self.timestamp.get())
167 }
168
169 fn Mapping(&self) -> DOMString {
171 DOMString::from(self.mapping_type.clone())
172 }
173
174 fn Axes(&self, cx: &mut JSContext, retval: MutableHandleValue) {
176 self.frozen_axes
177 .get_or_init(cx, || self.axes.borrow().clone(), retval);
178 }
179
180 fn Buttons(&self, cx: &mut JSContext, retval: MutableHandleValue) {
182 self.frozen_buttons.get_or_init(
183 cx,
184 || {
185 self.buttons
186 .iter()
187 .map(|b| DomRoot::from_ref(&**b))
188 .collect()
189 },
190 retval,
191 );
192 }
193
194 fn VibrationActuator(&self) -> DomRoot<GamepadHapticActuator> {
196 DomRoot::from_ref(&*self.vibration_actuator)
197 }
198
199 fn Hand(&self) -> GamepadHand {
201 self.hand
202 }
203
204 fn GetPose(&self) -> Option<DomRoot<GamepadPose>> {
206 self.pose.as_ref().map(|p| DomRoot::from_ref(&**p))
207 }
208}
209
210#[expect(dead_code)]
211impl Gamepad {
212 pub(crate) fn gamepad_id(&self) -> u32 {
213 self.gamepad_id
214 }
215
216 fn init_buttons(cx: &mut JSContext, window: &Window) -> Vec<DomRoot<GamepadButton>> {
219 vec![
220 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), ]
238 }
239
240 pub(crate) fn update_connected(&self, connected: bool) {
241 self.connected.set(connected);
242 }
243
244 pub(crate) fn index(&self) -> i32 {
245 self.index.get()
246 }
247
248 pub(crate) fn update_index(&self, index: i32) {
249 self.index.set(index);
250 }
251
252 pub(crate) fn update_timestamp(&self, timestamp: f64) {
253 self.timestamp.set(timestamp);
254 }
255
256 pub(crate) fn notify_event(
257 &self,
258 cx: &mut js::context::JSContext,
259 event_type: GamepadEventType,
260 ) {
261 let event = GamepadEvent::new_with_type(cx, self.global().as_window(), event_type, self);
262 event
263 .upcast::<Event>()
264 .fire(cx, self.global().as_window().upcast::<EventTarget>());
265 }
266
267 fn init_axes(&self) {
270 *self.axes.borrow_mut() = vec![
271 0., 0., 0., 0., ];
276 }
277
278 pub(crate) fn map_and_normalize_axes(&self, axis_index: usize, value: f64) {
280 let numerator = value - self.axis_bounds.0;
282 let denominator = self.axis_bounds.1 - self.axis_bounds.0;
283 if denominator != 0.0 && denominator.is_finite() {
284 let normalized_value: f64 = 2.0 * numerator / denominator - 1.0;
285 if normalized_value.is_finite() {
286 self.axes.borrow_mut()[axis_index] = normalized_value;
287 self.frozen_axes.clear();
288 } else {
289 warn!("Axis value is not finite!");
290 }
291 } else {
292 warn!("Axis bounds difference is either 0 or non-finite!");
293 }
294 }
295
296 pub(crate) fn map_and_normalize_buttons(&self, button_index: usize, value: f64) {
298 let numerator = value - self.button_bounds.0;
300 let denominator = self.button_bounds.1 - self.button_bounds.0;
301 if denominator != 0.0 && denominator.is_finite() {
302 let normalized_value: f64 = numerator / denominator;
303 if normalized_value.is_finite() {
304 let pressed = normalized_value >= BUTTON_PRESS_THRESHOLD;
305 if let Some(button) = self.buttons.get(button_index) {
307 button.update(pressed, pressed, normalized_value);
308 self.frozen_buttons.clear();
309 }
310 } else {
311 warn!("Button value is not finite!");
312 }
313 } else {
314 warn!("Button bounds difference is either 0 or non-finite!");
315 }
316 }
317
318 pub(crate) fn connected(&self) -> bool {
319 self.connected.get()
320 }
321
322 pub(crate) fn exposed(&self) -> bool {
324 self.exposed.get()
325 }
326
327 pub(crate) fn set_exposed(&self, exposed: bool) {
329 self.exposed.set(exposed);
330 }
331
332 pub(crate) fn vibration_actuator(&self) -> &GamepadHapticActuator {
333 &self.vibration_actuator
334 }
335}
336
337pub(crate) fn contains_user_gesture(update_type: GamepadUpdateType) -> bool {
339 match update_type {
340 GamepadUpdateType::Axis(_, value) => value.abs() > AXIS_TILT_THRESHOLD,
341 GamepadUpdateType::Button(_, value) => value > BUTTON_PRESS_THRESHOLD,
342 }
343}