mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Implement non-XR Gamepad discovery and input (#31200)
* Create embedder event to send to constellation * Handle gamepad message in constellation, send to script thread * Handle GamepadEvent in script thread and dispatch event to document * Add missing Clones, fix event * Add gamepad task source * Adjust GamepadIndex type, remove unused imports * Add internal getter for gamepads list * Update gamepad new methods * Handle gamepad connect and disconnect events * Proto will be none, no need for HandleObject * Initialize buttons and axes to standard mapping * Adjust update type index types * Update GamepadButton update function * Adjust Gamepad mapping comments to match spec, add update logic * Amend comment * Update button and axis inputs on Updated event * Add GilRs as gamepad backend in servoshell * Add spec links, queue gamepad updates on task source * ./mach fmt * Fix comment length * Split out button init, update spec comments * Move gamepad event handling from document to global * Map and normalize axes/button values * Use std::time for gamepad timestamp * Adjust gamepad handling in event loop * Move button press/touch check into map+normalize function - Small change but is more in line with spec * ./mach fmt * Update comment spec links and warning messages * Doc comments -> regular comments * Add window event handlers for gamepad connect/disconnect * Adjust gamepad disconnect behavior * Add missing TODO's, adjust gamepad/gamepadbutton list methods and formatting * Update button handling from gilrs, add comments * Enable gamepad pref during WPT tests and update expectations * Update WPT expectations in meta-legacy-layout
This commit is contained in:
parent
1cc546c4fc
commit
c999d4546c
27 changed files with 695 additions and 208 deletions
|
@ -9,9 +9,10 @@ use js::typedarray::{Float64, Float64Array};
|
|||
|
||||
use super::bindings::typedarrays::HeapTypedArray;
|
||||
use crate::dom::bindings::codegen::Bindings::GamepadBinding::{GamepadHand, GamepadMethods};
|
||||
use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::reflector::{DomObject, Reflector};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::event::Event;
|
||||
|
@ -19,8 +20,13 @@ use crate::dom::eventtarget::EventTarget;
|
|||
use crate::dom::gamepadbuttonlist::GamepadButtonList;
|
||||
use crate::dom::gamepadevent::{GamepadEvent, GamepadEventType};
|
||||
use crate::dom::gamepadpose::GamepadPose;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::script_runtime::JSContext;
|
||||
|
||||
// This value is for determining when to consider a non-digital button "pressed".
|
||||
// Like Gecko and Chromium it derives from the XInput trigger threshold.
|
||||
const BUTTON_PRESS_THRESHOLD: f64 = 30.0 / 255.0;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct Gamepad {
|
||||
reflector_: Reflector,
|
||||
|
@ -36,10 +42,10 @@ pub struct Gamepad {
|
|||
pose: Option<Dom<GamepadPose>>,
|
||||
#[ignore_malloc_size_of = "Defined in rust-webvr"]
|
||||
hand: GamepadHand,
|
||||
axis_bounds: (f64, f64),
|
||||
button_bounds: (f64, f64),
|
||||
}
|
||||
|
||||
// TODO: support gamepad discovery
|
||||
#[allow(dead_code)]
|
||||
impl Gamepad {
|
||||
fn new_inherited(
|
||||
gamepad_id: u32,
|
||||
|
@ -51,6 +57,8 @@ impl Gamepad {
|
|||
buttons: &GamepadButtonList,
|
||||
pose: Option<&GamepadPose>,
|
||||
hand: GamepadHand,
|
||||
axis_bounds: (f64, f64),
|
||||
button_bounds: (f64, f64),
|
||||
) -> Gamepad {
|
||||
Self {
|
||||
reflector_: Reflector::new(),
|
||||
|
@ -64,8 +72,54 @@ impl Gamepad {
|
|||
buttons: Dom::from_ref(buttons),
|
||||
pose: pose.map(Dom::from_ref),
|
||||
hand: hand,
|
||||
axis_bounds: axis_bounds,
|
||||
button_bounds: button_bounds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
gamepad_id: u32,
|
||||
id: String,
|
||||
axis_bounds: (f64, f64),
|
||||
button_bounds: (f64, f64),
|
||||
) -> DomRoot<Gamepad> {
|
||||
Self::new_with_proto(global, gamepad_id, id, axis_bounds, button_bounds)
|
||||
}
|
||||
|
||||
/// When we construct a new gamepad, we initialize the number of buttons and
|
||||
/// axes corresponding to the "standard" gamepad mapping.
|
||||
/// The spec says UAs *may* do this for fingerprint mitigation, and it also
|
||||
/// happens to simplify implementation
|
||||
/// <https://www.w3.org/TR/gamepad/#fingerprinting-mitigation>
|
||||
fn new_with_proto(
|
||||
global: &GlobalScope,
|
||||
gamepad_id: u32,
|
||||
id: String,
|
||||
axis_bounds: (f64, f64),
|
||||
button_bounds: (f64, f64),
|
||||
) -> DomRoot<Gamepad> {
|
||||
let button_list = GamepadButtonList::init_buttons(global);
|
||||
let gamepad = reflect_dom_object_with_proto(
|
||||
Box::new(Gamepad::new_inherited(
|
||||
gamepad_id,
|
||||
id,
|
||||
0,
|
||||
false,
|
||||
0.,
|
||||
String::from("standard"),
|
||||
&button_list,
|
||||
None,
|
||||
GamepadHand::_empty,
|
||||
axis_bounds,
|
||||
button_bounds,
|
||||
)),
|
||||
global,
|
||||
None,
|
||||
);
|
||||
gamepad.init_axes();
|
||||
gamepad
|
||||
}
|
||||
}
|
||||
|
||||
impl GamepadMethods for Gamepad {
|
||||
|
@ -117,7 +171,6 @@ impl GamepadMethods for Gamepad {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: support gamepad discovery
|
||||
#[allow(dead_code)]
|
||||
impl Gamepad {
|
||||
pub fn gamepad_id(&self) -> u32 {
|
||||
|
@ -143,10 +196,73 @@ impl Gamepad {
|
|||
self.index.set(index);
|
||||
}
|
||||
|
||||
pub fn update_timestamp(&self, timestamp: f64) {
|
||||
self.timestamp.set(timestamp);
|
||||
}
|
||||
|
||||
pub fn notify_event(&self, event_type: GamepadEventType) {
|
||||
let event = GamepadEvent::new_with_type(&self.global(), event_type, &self);
|
||||
event
|
||||
.upcast::<Event>()
|
||||
.fire(self.global().as_window().upcast::<EventTarget>());
|
||||
}
|
||||
|
||||
/// Initialize the number of axes in the "standard" gamepad mapping.
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-initializing-axes>
|
||||
fn init_axes(&self) {
|
||||
let initial_axes: Vec<f64> = vec![
|
||||
0., // Horizontal axis for left stick (negative left/positive right)
|
||||
0., // Vertical axis for left stick (negative up/positive down)
|
||||
0., // Horizontal axis for right stick (negative left/positive right)
|
||||
0., // Vertical axis for right stick (negative up/positive down)
|
||||
];
|
||||
self.axes
|
||||
.set_data(GlobalScope::get_cx(), &initial_axes)
|
||||
.expect("Failed to set axes data on gamepad.")
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-map-and-normalize-axes>
|
||||
pub fn map_and_normalize_axes(&self, axis_index: usize, value: f64) {
|
||||
// Let normalizedValue be 2 (logicalValue − logicalMinimum) / (logicalMaximum − logicalMinimum) − 1.
|
||||
let numerator = value - self.axis_bounds.0;
|
||||
let denominator = self.axis_bounds.1 - self.axis_bounds.0;
|
||||
if denominator != 0.0 && denominator.is_finite() {
|
||||
let normalized_value: f64 = 2.0 * numerator / denominator - 1.0;
|
||||
if normalized_value.is_finite() {
|
||||
let mut axis_vec = self
|
||||
.axes
|
||||
.internal_to_option()
|
||||
.expect("Axes have not been initialized!");
|
||||
unsafe {
|
||||
axis_vec.as_mut_slice()[axis_index] = normalized_value;
|
||||
}
|
||||
} else {
|
||||
warn!("Axis value is not finite!");
|
||||
}
|
||||
} else {
|
||||
warn!("Axis bounds difference is either 0 or non-finite!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-map-and-normalize-buttons>
|
||||
pub fn map_and_normalize_buttons(&self, button_index: usize, value: f64) {
|
||||
// Let normalizedValue be (logicalValue − logicalMinimum) / (logicalMaximum − logicalMinimum).
|
||||
let numerator = value - self.button_bounds.0;
|
||||
let denominator = self.button_bounds.1 - self.button_bounds.0;
|
||||
if denominator != 0.0 && denominator.is_finite() {
|
||||
let normalized_value: f64 = numerator / denominator;
|
||||
if normalized_value.is_finite() {
|
||||
let pressed = normalized_value >= BUTTON_PRESS_THRESHOLD;
|
||||
// TODO: Determine a way of getting touch capability for button
|
||||
if let Some(button) = self.buttons.IndexedGetter(button_index as u32) {
|
||||
button.update(pressed, /*touched*/ pressed, normalized_value);
|
||||
}
|
||||
} else {
|
||||
warn!("Button value is not finite!");
|
||||
}
|
||||
} else {
|
||||
warn!("Button bounds difference is either 0 or non-finite!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue