script/dom/webxr/
xrinputsource.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 embedder_traits::GamepadSupportedHapticEffects;
7use js::jsapi::Heap;
8use js::jsval::{JSVal, UndefinedValue};
9use js::rust::MutableHandleValue;
10use script_bindings::conversions::SafeToJSValConvertible;
11use webxr_api::{Handedness, InputFrame, InputId, InputSource, TargetRayMode};
12
13use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
14    XRHandedness, XRInputSourceMethods, XRTargetRayMode,
15};
16use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
17use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
18use crate::dom::gamepad::Gamepad;
19use crate::dom::globalscope::GlobalScope;
20use crate::dom::window::Window;
21use crate::dom::xrhand::XRHand;
22use crate::dom::xrsession::XRSession;
23use crate::dom::xrspace::XRSpace;
24use crate::realms::enter_realm;
25use crate::script_runtime::{CanGc, JSContext};
26
27#[dom_struct]
28pub(crate) struct XRInputSource {
29    reflector: Reflector,
30    session: Dom<XRSession>,
31    #[ignore_malloc_size_of = "Defined in rust-webxr"]
32    #[no_trace]
33    info: InputSource,
34    target_ray_space: MutNullableDom<XRSpace>,
35    grip_space: MutNullableDom<XRSpace>,
36    hand: MutNullableDom<XRHand>,
37    #[ignore_malloc_size_of = "mozjs"]
38    profiles: Heap<JSVal>,
39    gamepad: DomRoot<Gamepad>,
40}
41
42impl XRInputSource {
43    pub(crate) fn new_inherited(
44        window: &Window,
45        session: &XRSession,
46        info: InputSource,
47        can_gc: CanGc,
48    ) -> XRInputSource {
49        // <https://www.w3.org/TR/webxr-gamepads-module-1/#gamepad-differences>
50        let gamepad = Gamepad::new(
51            window,
52            0,
53            "".into(),
54            "xr-standard".into(),
55            (-1.0, 1.0),
56            (0.0, 1.0),
57            GamepadSupportedHapticEffects {
58                supports_dual_rumble: false,
59                supports_trigger_rumble: false,
60            },
61            true,
62            can_gc,
63        );
64        XRInputSource {
65            reflector: Reflector::new(),
66            session: Dom::from_ref(session),
67            info,
68            target_ray_space: Default::default(),
69            grip_space: Default::default(),
70            hand: Default::default(),
71            profiles: Heap::default(),
72            gamepad,
73        }
74    }
75
76    pub(crate) fn new(
77        window: &Window,
78        session: &XRSession,
79        info: InputSource,
80        can_gc: CanGc,
81    ) -> DomRoot<XRInputSource> {
82        let source = reflect_dom_object(
83            Box::new(XRInputSource::new_inherited(window, session, info, can_gc)),
84            window,
85            can_gc,
86        );
87
88        let _ac = enter_realm(window);
89        let cx = GlobalScope::get_cx();
90        rooted!(in(*cx) let mut profiles = UndefinedValue());
91        source
92            .info
93            .profiles
94            .safe_to_jsval(cx, profiles.handle_mut());
95        source.profiles.set(profiles.get());
96        source
97    }
98
99    pub(crate) fn id(&self) -> InputId {
100        self.info.id
101    }
102
103    pub(crate) fn session(&self) -> &XRSession {
104        &self.session
105    }
106
107    pub(crate) fn update_gamepad_state(&self, frame: InputFrame) {
108        frame
109            .button_values
110            .iter()
111            .enumerate()
112            .for_each(|(i, value)| {
113                self.gamepad.map_and_normalize_buttons(i, *value as f64);
114            });
115        frame.axis_values.iter().enumerate().for_each(|(i, value)| {
116            self.gamepad.map_and_normalize_axes(i, *value as f64);
117        });
118    }
119
120    pub(crate) fn gamepad(&self) -> &DomRoot<Gamepad> {
121        &self.gamepad
122    }
123}
124
125impl XRInputSourceMethods<crate::DomTypeHolder> for XRInputSource {
126    /// <https://immersive-web.github.io/webxr/#dom-xrinputsource-handedness>
127    fn Handedness(&self) -> XRHandedness {
128        match self.info.handedness {
129            Handedness::None => XRHandedness::None,
130            Handedness::Left => XRHandedness::Left,
131            Handedness::Right => XRHandedness::Right,
132        }
133    }
134
135    /// <https://immersive-web.github.io/webxr/#dom-xrinputsource-targetraymode>
136    fn TargetRayMode(&self) -> XRTargetRayMode {
137        match self.info.target_ray_mode {
138            TargetRayMode::Gaze => XRTargetRayMode::Gaze,
139            TargetRayMode::TrackedPointer => XRTargetRayMode::Tracked_pointer,
140            TargetRayMode::Screen => XRTargetRayMode::Screen,
141            TargetRayMode::TransientPointer => XRTargetRayMode::Transient_pointer,
142        }
143    }
144
145    /// <https://immersive-web.github.io/webxr/#dom-xrinputsource-targetrayspace>
146    fn TargetRaySpace(&self) -> DomRoot<XRSpace> {
147        self.target_ray_space.or_init(|| {
148            let global = self.global();
149            XRSpace::new_inputspace(&global, &self.session, self, false, CanGc::note())
150        })
151    }
152
153    /// <https://immersive-web.github.io/webxr/#dom-xrinputsource-gripspace>
154    fn GetGripSpace(&self) -> Option<DomRoot<XRSpace>> {
155        if self.info.supports_grip {
156            Some(self.grip_space.or_init(|| {
157                let global = self.global();
158                XRSpace::new_inputspace(&global, &self.session, self, true, CanGc::note())
159            }))
160        } else {
161            None
162        }
163    }
164    // https://immersive-web.github.io/webxr/#dom-xrinputsource-profiles
165    fn Profiles(&self, _cx: JSContext, mut retval: MutableHandleValue) {
166        retval.set(self.profiles.get())
167    }
168
169    /// <https://www.w3.org/TR/webxr/#dom-xrinputsource-skiprendering>
170    fn SkipRendering(&self) -> bool {
171        // Servo is not currently supported anywhere that would allow for skipped
172        // controller rendering.
173        false
174    }
175
176    /// <https://www.w3.org/TR/webxr-gamepads-module-1/#xrinputsource-interface>
177    fn GetGamepad(&self) -> Option<DomRoot<Gamepad>> {
178        Some(DomRoot::from_ref(&*self.gamepad))
179    }
180
181    // https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md
182    fn GetHand(&self) -> Option<DomRoot<XRHand>> {
183        self.info.hand_support.as_ref().map(|hand| {
184            self.hand
185                .or_init(|| XRHand::new(&self.global(), self, hand.clone(), CanGc::note()))
186        })
187    }
188}