script/dom/webxr/
fakexrinputcontroller.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use base::generic_channel::GenericSender;
6use dom_struct::dom_struct;
7use webxr_api::{
8    Handedness, InputId, MockButton, MockButtonType, MockDeviceMsg, MockInputMsg, SelectEvent,
9    SelectKind, TargetRayMode,
10};
11
12use crate::conversions::Convert;
13use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::FakeXRRigidTransformInit;
14use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::{
15    FakeXRButtonStateInit, FakeXRButtonType, FakeXRInputControllerMethods,
16};
17use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
18    XRHandedness, XRTargetRayMode,
19};
20use crate::dom::bindings::error::{Error, Fallible};
21use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
22use crate::dom::bindings::root::DomRoot;
23use crate::dom::bindings::str::DOMString;
24use crate::dom::fakexrdevice::get_origin;
25use crate::dom::globalscope::GlobalScope;
26use crate::script_runtime::CanGc;
27
28#[dom_struct]
29pub(crate) struct FakeXRInputController {
30    reflector: Reflector,
31    #[no_trace]
32    sender: GenericSender<MockDeviceMsg>,
33    #[ignore_malloc_size_of = "defined in webxr-api"]
34    #[no_trace]
35    id: InputId,
36}
37
38impl FakeXRInputController {
39    pub(crate) fn new_inherited(
40        sender: GenericSender<MockDeviceMsg>,
41        id: InputId,
42    ) -> FakeXRInputController {
43        FakeXRInputController {
44            reflector: Reflector::new(),
45            sender,
46            id,
47        }
48    }
49
50    pub(crate) fn new(
51        global: &GlobalScope,
52        sender: GenericSender<MockDeviceMsg>,
53        id: InputId,
54        can_gc: CanGc,
55    ) -> DomRoot<FakeXRInputController> {
56        reflect_dom_object(
57            Box::new(FakeXRInputController::new_inherited(sender, id)),
58            global,
59            can_gc,
60        )
61    }
62
63    fn send_message(&self, msg: MockInputMsg) {
64        let _ = self
65            .sender
66            .send(MockDeviceMsg::MessageInputSource(self.id, msg));
67    }
68}
69
70impl FakeXRInputControllerMethods<crate::DomTypeHolder> for FakeXRInputController {
71    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setpointerorigin>
72    fn SetPointerOrigin(&self, origin: &FakeXRRigidTransformInit, _emulated: bool) -> Fallible<()> {
73        self.send_message(MockInputMsg::SetPointerOrigin(Some(get_origin(origin)?)));
74        Ok(())
75    }
76
77    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setgriporigin>
78    fn SetGripOrigin(&self, origin: &FakeXRRigidTransformInit, _emulated: bool) -> Fallible<()> {
79        self.send_message(MockInputMsg::SetGripOrigin(Some(get_origin(origin)?)));
80        Ok(())
81    }
82
83    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-cleargriporigin>
84    fn ClearGripOrigin(&self) {
85        self.send_message(MockInputMsg::SetGripOrigin(None))
86    }
87
88    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-disconnect>
89    fn Disconnect(&self) {
90        self.send_message(MockInputMsg::Disconnect)
91    }
92
93    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-reconnect>
94    fn Reconnect(&self) {
95        self.send_message(MockInputMsg::Reconnect)
96    }
97
98    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-startselection>
99    fn StartSelection(&self) {
100        self.send_message(MockInputMsg::TriggerSelect(
101            SelectKind::Select,
102            SelectEvent::Start,
103        ))
104    }
105
106    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-endselection>
107    fn EndSelection(&self) {
108        self.send_message(MockInputMsg::TriggerSelect(
109            SelectKind::Select,
110            SelectEvent::End,
111        ))
112    }
113
114    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-simulateselect>
115    fn SimulateSelect(&self) {
116        self.send_message(MockInputMsg::TriggerSelect(
117            SelectKind::Select,
118            SelectEvent::Select,
119        ))
120    }
121
122    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-sethandedness>
123    fn SetHandedness(&self, handedness: XRHandedness) {
124        let h = match handedness {
125            XRHandedness::None => Handedness::None,
126            XRHandedness::Left => Handedness::Left,
127            XRHandedness::Right => Handedness::Right,
128        };
129        self.send_message(MockInputMsg::SetHandedness(h));
130    }
131
132    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-settargetraymode>
133    fn SetTargetRayMode(&self, target_ray_mode: XRTargetRayMode) {
134        let t = match target_ray_mode {
135            XRTargetRayMode::Gaze => TargetRayMode::Gaze,
136            XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer,
137            XRTargetRayMode::Screen => TargetRayMode::Screen,
138            XRTargetRayMode::Transient_pointer => TargetRayMode::TransientPointer,
139        };
140        self.send_message(MockInputMsg::SetTargetRayMode(t));
141    }
142
143    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setprofiles>
144    fn SetProfiles(&self, profiles: Vec<DOMString>) {
145        let t = profiles.into_iter().map(String::from).collect();
146        self.send_message(MockInputMsg::SetProfiles(t));
147    }
148
149    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setsupportedbuttons>
150    fn SetSupportedButtons(&self, supported_buttons: Vec<FakeXRButtonStateInit>) {
151        let supported = init_to_mock_buttons(&supported_buttons);
152        self.send_message(MockInputMsg::SetSupportedButtons(supported));
153    }
154
155    /// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-updatebuttonstate>
156    fn UpdateButtonState(&self, button_state: &FakeXRButtonStateInit) -> Fallible<()> {
157        // https://immersive-web.github.io/webxr-test-api/#validate-a-button-state
158        if (button_state.pressed || *button_state.pressedValue > 0.0) && !button_state.touched {
159            return Err(Error::Type("Pressed button must also be touched".into()));
160        }
161        if *button_state.pressedValue < 0.0 {
162            return Err(Error::Type("Pressed value must be non-negative".into()));
163        }
164
165        // TODO: Steps 3-5 of updateButtonState
166        // Passing the one WPT test that utilizes this will require additional work
167        // to specify gamepad button/axes list lengths, as well as passing that info
168        // to the constructor of XRInputSource
169
170        Ok(())
171    }
172}
173
174impl Convert<MockButtonType> for FakeXRButtonType {
175    fn convert(self) -> MockButtonType {
176        match self {
177            FakeXRButtonType::Grip => MockButtonType::Grip,
178            FakeXRButtonType::Touchpad => MockButtonType::Touchpad,
179            FakeXRButtonType::Thumbstick => MockButtonType::Thumbstick,
180            FakeXRButtonType::Optional_button => MockButtonType::OptionalButton,
181            FakeXRButtonType::Optional_thumbstick => MockButtonType::OptionalThumbstick,
182        }
183    }
184}
185
186/// <https://immersive-web.github.io/webxr-test-api/#parse-supported-buttons>
187pub(crate) fn init_to_mock_buttons(buttons: &[FakeXRButtonStateInit]) -> Vec<MockButton> {
188    let supported: Vec<MockButton> = buttons
189        .iter()
190        .map(|b| MockButton {
191            button_type: b.buttonType.convert(),
192            pressed: b.pressed,
193            touched: b.touched,
194            pressed_value: *b.pressedValue,
195            x_value: *b.xValue,
196            y_value: *b.yValue,
197        })
198        .collect();
199    supported
200}