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