1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use embedder_traits::{GamepadSupportedHapticEffects, GamepadUpdateType};
9use js::typedarray::{Float64, HeapFloat64Array};
10use script_bindings::trace::RootedTraceableBox;
11
12use super::gamepadbuttonlist::GamepadButtonList;
13use super::gamepadhapticactuator::GamepadHapticActuator;
14use super::gamepadpose::GamepadPose;
15use crate::dom::bindings::buffer_source::HeapBufferSource;
16use crate::dom::bindings::codegen::Bindings::GamepadBinding::{GamepadHand, GamepadMethods};
17use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods;
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::num::Finite;
20use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
21use crate::dom::bindings::root::{Dom, DomRoot};
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::globalscope::GlobalScope;
27use crate::dom::window::Window;
28use crate::script_runtime::{CanGc, JSContext};
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: String,
46 #[ignore_malloc_size_of = "mozjs"]
47 axes: HeapBufferSource<Float64>,
48 buttons: Dom<GamepadButtonList>,
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: &GamepadButtonList,
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 axes: HeapBufferSource::default(),
83 buttons: Dom::from_ref(buttons),
84 pose: pose.map(Dom::from_ref),
85 hand,
86 axis_bounds,
87 button_bounds,
88 exposed: Cell::new(false),
89 vibration_actuator: Dom::from_ref(vibration_actuator),
90 }
91 }
92
93 #[allow(clippy::too_many_arguments)]
99 pub(crate) fn new(
100 window: &Window,
101 gamepad_id: u32,
102 id: String,
103 mapping_type: String,
104 axis_bounds: (f64, f64),
105 button_bounds: (f64, f64),
106 supported_haptic_effects: GamepadSupportedHapticEffects,
107 xr: bool,
108 can_gc: CanGc,
109 ) -> DomRoot<Gamepad> {
110 let button_list = GamepadButtonList::init_buttons(window, can_gc);
111 let vibration_actuator =
112 GamepadHapticActuator::new(window, gamepad_id, supported_haptic_effects, can_gc);
113 let index = if xr { -1 } else { 0 };
114 let gamepad = reflect_dom_object(
115 Box::new(Gamepad::new_inherited(
116 gamepad_id,
117 id,
118 index,
119 true,
120 0.,
121 mapping_type,
122 &button_list,
123 None,
124 GamepadHand::_empty,
125 axis_bounds,
126 button_bounds,
127 &vibration_actuator,
128 )),
129 window,
130 can_gc,
131 );
132 gamepad.init_axes(can_gc);
133 gamepad
134 }
135}
136
137impl GamepadMethods<crate::DomTypeHolder> for Gamepad {
138 fn Id(&self) -> DOMString {
140 DOMString::from(self.id.clone())
141 }
142
143 fn Index(&self) -> i32 {
145 self.index.get()
146 }
147
148 fn Connected(&self) -> bool {
150 self.connected.get()
151 }
152
153 fn Timestamp(&self) -> Finite<f64> {
155 Finite::wrap(self.timestamp.get())
156 }
157
158 fn Mapping(&self) -> DOMString {
160 DOMString::from(self.mapping_type.clone())
161 }
162
163 fn Axes(&self, _cx: JSContext) -> RootedTraceableBox<HeapFloat64Array> {
165 self.axes
166 .get_typed_array()
167 .expect("Failed to get gamepad axes.")
168 }
169
170 fn Buttons(&self) -> DomRoot<GamepadButtonList> {
172 DomRoot::from_ref(&*self.buttons)
173 }
174
175 fn VibrationActuator(&self) -> DomRoot<GamepadHapticActuator> {
177 DomRoot::from_ref(&*self.vibration_actuator)
178 }
179
180 fn Hand(&self) -> GamepadHand {
182 self.hand
183 }
184
185 fn GetPose(&self) -> Option<DomRoot<GamepadPose>> {
187 self.pose.as_ref().map(|p| DomRoot::from_ref(&**p))
188 }
189}
190
191#[expect(dead_code)]
192impl Gamepad {
193 pub(crate) fn gamepad_id(&self) -> u32 {
194 self.gamepad_id
195 }
196
197 pub(crate) fn update_connected(&self, connected: bool, has_gesture: bool, can_gc: CanGc) {
198 if self.connected.get() == connected {
199 return;
200 }
201 self.connected.set(connected);
202
203 let event_type = if connected {
204 GamepadEventType::Connected
205 } else {
206 GamepadEventType::Disconnected
207 };
208
209 if has_gesture {
210 self.notify_event(event_type, can_gc);
211 }
212 }
213
214 pub(crate) fn index(&self) -> i32 {
215 self.index.get()
216 }
217
218 pub(crate) fn update_index(&self, index: i32) {
219 self.index.set(index);
220 }
221
222 pub(crate) fn update_timestamp(&self, timestamp: f64) {
223 self.timestamp.set(timestamp);
224 }
225
226 pub(crate) fn notify_event(&self, event_type: GamepadEventType, can_gc: CanGc) {
227 let event =
228 GamepadEvent::new_with_type(self.global().as_window(), event_type, self, can_gc);
229 event
230 .upcast::<Event>()
231 .fire(self.global().as_window().upcast::<EventTarget>(), can_gc);
232 }
233
234 fn init_axes(&self, can_gc: CanGc) {
237 let initial_axes: Vec<f64> = vec![
238 0., 0., 0., 0., ];
243 self.axes
244 .set_data(GlobalScope::get_cx(), &initial_axes, can_gc)
245 .expect("Failed to set axes data on gamepad.")
246 }
247
248 #[expect(unsafe_code)]
249 pub(crate) fn map_and_normalize_axes(&self, axis_index: usize, value: f64) {
251 let numerator = value - self.axis_bounds.0;
253 let denominator = self.axis_bounds.1 - self.axis_bounds.0;
254 if denominator != 0.0 && denominator.is_finite() {
255 let normalized_value: f64 = 2.0 * numerator / denominator - 1.0;
256 if normalized_value.is_finite() {
257 let mut axis_vec = self
258 .axes
259 .typed_array_to_option()
260 .expect("Axes have not been initialized!");
261 unsafe {
262 axis_vec.as_mut_slice()[axis_index] = normalized_value;
263 }
264 } else {
265 warn!("Axis value is not finite!");
266 }
267 } else {
268 warn!("Axis bounds difference is either 0 or non-finite!");
269 }
270 }
271
272 pub(crate) fn map_and_normalize_buttons(&self, button_index: usize, value: f64) {
274 let numerator = value - self.button_bounds.0;
276 let denominator = self.button_bounds.1 - self.button_bounds.0;
277 if denominator != 0.0 && denominator.is_finite() {
278 let normalized_value: f64 = numerator / denominator;
279 if normalized_value.is_finite() {
280 let pressed = normalized_value >= BUTTON_PRESS_THRESHOLD;
281 if let Some(button) = self.buttons.IndexedGetter(button_index as u32) {
283 button.update(pressed, pressed, normalized_value);
284 }
285 } else {
286 warn!("Button value is not finite!");
287 }
288 } else {
289 warn!("Button bounds difference is either 0 or non-finite!");
290 }
291 }
292
293 pub(crate) fn exposed(&self) -> bool {
295 self.exposed.get()
296 }
297
298 pub(crate) fn set_exposed(&self, exposed: bool) {
300 self.exposed.set(exposed);
301 }
302
303 pub(crate) fn vibration_actuator(&self) -> &GamepadHapticActuator {
304 &self.vibration_actuator
305 }
306}
307
308pub(crate) fn contains_user_gesture(update_type: GamepadUpdateType) -> bool {
310 match update_type {
311 GamepadUpdateType::Axis(_, value) => value.abs() > AXIS_TILT_THRESHOLD,
312 GamepadUpdateType::Button(_, value) => value > BUTTON_PRESS_THRESHOLD,
313 }
314}