1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dxrvirtualmouse_p.h"
5#include "qquick3dxrview_p.h"
6
7#include <QtQuick3D/private/qquick3dnode_p.h>
8#include <QtGui/QMouseEvent>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \qmltype XrVirtualMouse
14 \inherits Item
15 \inqmlmodule QtQuick3D.Xr
16 \brief Maps 3D controller input to mouse input in 2D items.
17
18 The XrVirtualMouse provides a way to interact with \l{Qt Quick 3D Scenes with 2D Content}{2D user interfaces}
19 in the 3D scene.
20
21 It is typically used like this:
22
23 \qml
24 // XrView { id: xrView
25 // XrController { id: rightController
26 XrInputAction {
27 id: rightTrigger
28 hand: XrInputAction.RightHand
29 actionId: [XrInputAction.TriggerPressed, XrInputAction.TriggerValue]
30 }
31 XrVirtualMouse {
32 view: xrView
33 source: rightController
34 leftMouseButton: rightTrigger.pressed
35 }
36 \endqml
37*/
38
39QQuick3DXrVirtualMouse::QQuick3DXrVirtualMouse(QObject *parent) : QObject(parent)
40{
41 m_scrollTimer = new QTimer(this);
42 m_scrollTimer->setInterval(m_scrollTimerInterval);
43 connect(sender: m_scrollTimer, SIGNAL(timeout()), receiver: this, SLOT(generateWheelEvent()));
44}
45
46/*!
47 \qmlproperty bool XrVirtualMouse::rightMouseButton
48 \brief Sets the state of the right mouse button.
49
50 When set to true, the right mouse button is pressed.
51*/
52
53bool QQuick3DXrVirtualMouse::rightMouseButton() const
54{
55 return m_rightMouseButton;
56}
57
58/*!
59 \qmlproperty bool XrVirtualMouse::leftMouseButton
60 \brief Sets the state of the left mouse button.
61
62 When set to true, the left mouse button is pressed.
63*/
64
65bool QQuick3DXrVirtualMouse::leftMouseButton() const
66{
67 return m_leftMouseButton;
68}
69
70/*!
71 \qmlproperty bool XrVirtualMouse::middleMouseButton
72 \brief Sets the state of the middle mouse button.
73
74 When set to true, the middle mouse button is pressed.
75*/
76
77bool QQuick3DXrVirtualMouse::middleMouseButton() const
78{
79 return m_middleMouseButton;
80}
81
82/*!
83 \qmlproperty float XrVirtualMouse::scrollWheelX
84 \brief Sets the horizontal scrolling speed.
85
86 Positive values scroll right and negative values scroll left.
87 Scroll speed increases relative to distance from zero.
88
89 \sa scrollPixelDelta
90*/
91
92float QQuick3DXrVirtualMouse::scrollWheelX() const
93{
94 return m_scrollWheelX;
95}
96
97/*!
98 \qmlproperty float XrVirtualMouse::scrollWheelY
99 \brief Sets the vertical scrolling speed.
100
101 Positive values scroll up and negative values scroll down.
102 Scroll speed increases relative to distance from zero.
103
104 \sa scrollPixelDelta
105*/
106
107float QQuick3DXrVirtualMouse::scrollWheelY() const
108{
109 return m_scrollWheelY;
110}
111
112/*!
113 \qmlproperty int XrVirtualMouse::scrollTimerInterval
114 \brief Defines time in milliseconds between scrolling events sent to the system.
115 \default 30
116*/
117
118int QQuick3DXrVirtualMouse::scrollTimerInterval() const
119{
120 return m_scrollTimerInterval;
121}
122
123/*!
124 \qmlproperty float XrVirtualMouse::scrollPixelDelta
125 \brief Defines the base distance scrolled with each scrolling event.
126 \default 15
127
128 This is the distance scrolled when the scrolling speed is 1.
129
130 \sa scrollWheelX, scrollWheelY
131*/
132
133int QQuick3DXrVirtualMouse::scrollPixelDelta() const
134{
135 return m_scrollPixelDelta;
136}
137
138/*!
139 \qmlproperty Node XrVirtualMouse::source
140 \brief The 3D node controlling the virtual mouse.
141
142 The \c source property is normally set to an \l XrController. Mouse events
143 are generated for the position where the \l{QtQuick3D::Node::forward}{forward vector} of
144 the \c source node intersects with a 2D item.
145*/
146
147QQuick3DNode *QQuick3DXrVirtualMouse::source() const
148{
149 return m_source;
150}
151
152/*!
153 \qmlproperty XrView XrVirtualMouse::view
154 \brief The XR view associated with the virtual mouse.
155 Holds the view in which the virtual mouse operates.
156*/
157
158QQuick3DXrView *QQuick3DXrVirtualMouse::view() const
159{
160 return m_view;
161}
162
163/*!
164 \qmlproperty bool XrVirtualMouse::enabled
165 \brief Indicates whether the virtual mouse is enabled.
166 When true, the virtual mouse sends mouse events to 2D objects in the scene.
167*/
168
169bool QQuick3DXrVirtualMouse::enabled() const
170{
171 return m_enabled;
172}
173
174void QQuick3DXrVirtualMouse::setRightMouseButton(bool rightMouseButton)
175{
176 if (m_rightMouseButton == rightMouseButton)
177 return;
178
179 m_rightMouseButton = rightMouseButton;
180 emit rightMouseButtonChanged(rightMouseButton: m_rightMouseButton);
181 if (m_rightMouseButton) {
182 // Press
183 generateEvent(type: QEvent::MouseButtonPress, button: Qt::RightButton);
184 } else {
185 // Release
186 generateEvent(type: QEvent::MouseButtonRelease, button: Qt::RightButton);
187 }
188}
189
190void QQuick3DXrVirtualMouse::setLeftMouseButton(bool leftMouseButton)
191{
192 if (m_leftMouseButton == leftMouseButton)
193 return;
194
195 m_leftMouseButton = leftMouseButton;
196 emit leftMouseButtonChanged(leftMouseButton: m_leftMouseButton);
197 if (m_leftMouseButton) {
198 // Press
199 generateEvent(type: QEvent::MouseButtonPress, button: Qt::LeftButton);
200 } else {
201 // Release
202 generateEvent(type: QEvent::MouseButtonRelease, button: Qt::LeftButton);
203 }
204}
205
206void QQuick3DXrVirtualMouse::setMiddleMouseButton(bool middleMouseButton)
207{
208 if (m_middleMouseButton == middleMouseButton)
209 return;
210
211 m_middleMouseButton = middleMouseButton;
212 emit middleMouseButtonChanged(middleMouseButton: m_middleMouseButton);
213 if (m_middleMouseButton) {
214 // Press
215 generateEvent(type: QEvent::MouseButtonPress, button: Qt::MiddleButton);
216 } else {
217 // Release
218 generateEvent(type: QEvent::MouseButtonRelease, button: Qt::MiddleButton);
219 }
220}
221
222void QQuick3DXrVirtualMouse::setScrollWheelX(float scrollWheelX)
223{
224 if (m_scrollWheelX == scrollWheelX)
225 return;
226
227 m_scrollWheelX = scrollWheelX;
228 emit scrollWheelXChanged(scrollWheelX: m_scrollWheelX);
229 if ((m_scrollTimer->isActive()) && (m_scrollWheelX == 0)) {
230 m_scrollTimer->stop();
231 } else if (!m_scrollTimer->isActive()) {
232 m_scrollTimer->start();
233 }
234}
235
236void QQuick3DXrVirtualMouse::setScrollWheelY(float scrollWheelY)
237{
238 if (m_scrollWheelY == scrollWheelY)
239 return;
240
241 m_scrollWheelY = scrollWheelY;
242 emit scrollWheelYChanged(scrollWheelY: m_scrollWheelY);
243 if ((m_scrollTimer->isActive()) && (m_scrollWheelY == 0)) {
244 m_scrollTimer->stop();
245 } else if (!m_scrollTimer->isActive()) {
246 m_scrollTimer->start();
247 }
248}
249
250void QQuick3DXrVirtualMouse::setScrollTimerInterval(int scrollTimerInterval)
251{
252 if (m_scrollTimerInterval == scrollTimerInterval)
253 return;
254
255 m_scrollTimerInterval = scrollTimerInterval;
256 m_scrollTimer->setInterval(m_scrollTimerInterval);
257
258 emit scrollTimerIntervalChanged(scrollTimerInterval: m_scrollTimerInterval);
259}
260
261void QQuick3DXrVirtualMouse::setScrollPixelDelta(int scrollPixelDelta)
262{
263 if (m_scrollPixelDelta == scrollPixelDelta)
264 return;
265
266 m_scrollPixelDelta = scrollPixelDelta;
267
268 emit scrollPixelDeltaChanged(scrollPixelDelta: m_scrollPixelDelta);
269}
270
271void QQuick3DXrVirtualMouse::setSource(QQuick3DNode *source)
272{
273 if (m_source == source)
274 return;
275
276 if (!source)
277 disconnect(sender: m_source, signal: &QQuick3DNode::sceneTransformChanged, receiver: this, slot: &QQuick3DXrVirtualMouse::moveEvent);
278
279 m_source = source;
280 emit sourceChanged(source: m_source);
281
282 if (m_source)
283 connect(sender: m_source, signal: &QQuick3DNode::sceneTransformChanged, context: this, slot: &QQuick3DXrVirtualMouse::moveEvent);
284}
285
286void QQuick3DXrVirtualMouse::setView(QQuick3DXrView *view)
287{
288 if (m_view == view)
289 return;
290
291 m_view = view;
292 emit viewChanged(view: m_view);
293}
294
295void QQuick3DXrVirtualMouse::setEnabled(bool enabled)
296{
297 if (m_enabled == enabled)
298 return;
299 m_enabled = enabled;
300 emit enabledChanged(enabled: m_enabled);
301
302 // Generate mouse release event if we disable when pressed
303 if (!m_enabled && (m_leftMouseButton || m_rightMouseButton || m_middleMouseButton)) {
304 Qt::MouseButton button = m_leftMouseButton ? Qt::LeftButton : m_rightMouseButton ? Qt::RightButton : Qt::MiddleButton;
305 m_leftMouseButton = m_rightMouseButton = m_middleMouseButton = false;
306 generateEvent(type: QEvent::MouseButtonRelease, button);
307 }
308}
309
310void QQuick3DXrVirtualMouse::moveEvent()
311{
312 generateEvent(type: QEvent::MouseMove, button: Qt::NoButton);
313}
314
315void QQuick3DXrVirtualMouse::generateWheelEvent()
316{
317 if (!m_view || !m_source || m_view->m_inDestructor || !m_enabled)
318 return;
319
320 // Get Ray
321 const QVector3D origin = m_source->scenePosition();
322 const QVector3D direction = m_source->forward();
323
324 float x = 0;
325 float y = 0;
326
327 if (m_scrollWheelX > 0) {
328 x = m_scrollPixelDelta * m_scrollWheelX * m_scrollWheelX;
329 } else if (m_scrollWheelX < 0) {
330 x = -m_scrollPixelDelta * m_scrollWheelX * m_scrollWheelX;
331 }
332
333 if (m_scrollWheelY > 0) {
334 y = m_scrollPixelDelta * m_scrollWheelY * m_scrollWheelY;
335 } else if (m_scrollWheelY < 0) {
336 y = -m_scrollPixelDelta * m_scrollWheelY * m_scrollWheelY;
337 }
338
339 QPoint pixelDelta = QPoint(x, y);
340 QPoint angleDelta = QPoint(x*8, y*8);
341
342 // Position will be generated by QtQuick3D
343 QWheelEvent *event = new QWheelEvent(QPointF(), QPointF(), pixelDelta, angleDelta,
344 Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
345
346 // Send to View with Ray
347 if (m_view->view3d()) // no internal view3D if XrView init failed but the object is still alive, handle this gracefully
348 m_view->view3d()->singlePointPick(event, origin, direction);
349
350 // Cleanup
351 delete event;
352}
353
354void QQuick3DXrVirtualMouse::generateEvent(QEvent::Type type, Qt::MouseButton button)
355{
356 if (!m_view || !m_source || m_view->m_inDestructor || !m_enabled)
357 return;
358
359 // Get Ray
360 const QVector3D origin = m_source->scenePosition();
361 const QVector3D direction = m_source->forward();
362
363 // Generate Pointer Event
364 Qt::MouseButtons buttons = Qt::NoButton;
365 if (m_leftMouseButton)
366 buttons |= Qt::LeftButton;
367 if (m_rightMouseButton)
368 buttons |= Qt::RightButton;
369 if (m_middleMouseButton)
370 buttons |= Qt::MiddleButton;
371
372 // Position will be generated by QtQuick3D
373 QMouseEvent *event = new QMouseEvent(type, QPointF(), QPointF(), button, buttons, Qt::NoModifier);
374
375 // Send to View with Ray
376 if (m_view->view3d()) // no internal view3D if XrView init failed but the object is still alive, handle this gracefully
377 m_view->view3d()->singlePointPick(event, origin, direction);
378
379 // Cleanup
380 delete event;
381}
382
383QT_END_NAMESPACE
384

source code of qtquick3d/src/xr/quick3dxr/qquick3dxrvirtualmouse.cpp