Revision control
Copy as Markdown
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qquick3dxrvirtualmouse_p.h"
#include "qquick3dxrview_p.h"
#include <QtQuick3D/private/qquick3dnode_p.h>
#include <QtGui/QMouseEvent>
QT_BEGIN_NAMESPACE
/*!
\qmltype XrVirtualMouse
\inherits Item
\inqmlmodule QtQuick3D.Xr
\brief Maps 3D controller input to mouse input in 2D items.
The XrVirtualMouse provides a way to interact with \l{Qt Quick 3D Scenes with 2D Content}{2D user interfaces}
in the 3D scene.
It is typically used like this:
\qml
// XrView { id: xrView
// XrController { id: rightController
XrInputAction {
id: rightTrigger
hand: XrInputAction.RightHand
actionId: [XrInputAction.TriggerPressed, XrInputAction.TriggerValue]
}
XrVirtualMouse {
view: xrView
source: rightController
leftMouseButton: rightTrigger.pressed
}
\endqml
*/
QQuick3DXrVirtualMouse::QQuick3DXrVirtualMouse(QObject *parent) : QObject(parent)
{
m_scrollTimer = new QTimer(this);
m_scrollTimer->setInterval(m_scrollTimerInterval);
connect(m_scrollTimer, SIGNAL(timeout()), this, SLOT(generateWheelEvent()));
}
/*!
\qmlproperty bool XrVirtualMouse::rightMouseButton
\brief Sets the state of the right mouse button.
When set to true, the right mouse button is pressed.
*/
bool QQuick3DXrVirtualMouse::rightMouseButton() const
{
return m_rightMouseButton;
}
/*!
\qmlproperty bool XrVirtualMouse::leftMouseButton
\brief Sets the state of the left mouse button.
When set to true, the left mouse button is pressed.
*/
bool QQuick3DXrVirtualMouse::leftMouseButton() const
{
return m_leftMouseButton;
}
/*!
\qmlproperty bool XrVirtualMouse::middleMouseButton
\brief Sets the state of the middle mouse button.
When set to true, the middle mouse button is pressed.
*/
bool QQuick3DXrVirtualMouse::middleMouseButton() const
{
return m_middleMouseButton;
}
/*!
\qmlproperty real XrVirtualMouse::scrollWheelX
\brief Sets the horizontal scrolling speed.
Positive values scroll right and negative values scroll left.
Scroll speed increases relative to distance from zero.
\sa scrollPixelDelta
*/
float QQuick3DXrVirtualMouse::scrollWheelX() const
{
return m_scrollWheelX;
}
/*!
\qmlproperty real XrVirtualMouse::scrollWheelY
\brief Sets the vertical scrolling speed.
Positive values scroll up and negative values scroll down.
Scroll speed increases relative to distance from zero.
\sa scrollPixelDelta
*/
float QQuick3DXrVirtualMouse::scrollWheelY() const
{
return m_scrollWheelY;
}
/*!
\qmlproperty int XrVirtualMouse::scrollTimerInterval
\brief Defines time in milliseconds between scrolling events sent to the system.
\default 30
*/
int QQuick3DXrVirtualMouse::scrollTimerInterval() const
{
return m_scrollTimerInterval;
}
/*!
\qmlproperty real XrVirtualMouse::scrollPixelDelta
\brief Defines the base distance scrolled with each scrolling event.
\default 15
This is the distance scrolled when the scrolling speed is 1.
\sa scrollWheelX, scrollWheelY
*/
int QQuick3DXrVirtualMouse::scrollPixelDelta() const
{
return m_scrollPixelDelta;
}
/*!
\qmlproperty Node XrVirtualMouse::source
\brief The 3D node controlling the virtual mouse.
The \c source property is normally set to an \l XrController. Mouse events
are generated for the position where the \l{QtQuick3D::Node::forward}{forward vector} of
the \c source node intersects with a 2D item.
*/
QQuick3DNode *QQuick3DXrVirtualMouse::source() const
{
return m_source;
}
/*!
\qmlproperty XrView XrVirtualMouse::view
\brief The XR view associated with the virtual mouse.
Holds the view in which the virtual mouse operates.
*/
QQuick3DXrView *QQuick3DXrVirtualMouse::view() const
{
return m_view;
}
/*!
\qmlproperty bool XrVirtualMouse::enabled
\brief Indicates whether the virtual mouse is enabled.
When true, the virtual mouse sends mouse events to 2D objects in the scene.
*/
bool QQuick3DXrVirtualMouse::enabled() const
{
return m_enabled;
}
void QQuick3DXrVirtualMouse::setRightMouseButton(bool rightMouseButton)
{
if (m_rightMouseButton == rightMouseButton)
return;
m_rightMouseButton = rightMouseButton;
emit rightMouseButtonChanged(m_rightMouseButton);
if (m_rightMouseButton) {
// Press
generateEvent(QEvent::MouseButtonPress, Qt::RightButton);
} else {
// Release
generateEvent(QEvent::MouseButtonRelease, Qt::RightButton);
}
}
void QQuick3DXrVirtualMouse::setLeftMouseButton(bool leftMouseButton)
{
if (m_leftMouseButton == leftMouseButton)
return;
m_leftMouseButton = leftMouseButton;
emit leftMouseButtonChanged(m_leftMouseButton);
if (m_leftMouseButton) {
// Press
generateEvent(QEvent::MouseButtonPress, Qt::LeftButton);
} else {
// Release
generateEvent(QEvent::MouseButtonRelease, Qt::LeftButton);
}
}
void QQuick3DXrVirtualMouse::setMiddleMouseButton(bool middleMouseButton)
{
if (m_middleMouseButton == middleMouseButton)
return;
m_middleMouseButton = middleMouseButton;
emit middleMouseButtonChanged(m_middleMouseButton);
if (m_middleMouseButton) {
// Press
generateEvent(QEvent::MouseButtonPress, Qt::MiddleButton);
} else {
// Release
generateEvent(QEvent::MouseButtonRelease, Qt::MiddleButton);
}
}
void QQuick3DXrVirtualMouse::setScrollWheelX(float scrollWheelX)
{
if (m_scrollWheelX == scrollWheelX)
return;
m_scrollWheelX = scrollWheelX;
emit scrollWheelXChanged(m_scrollWheelX);
if ((m_scrollTimer->isActive()) && (m_scrollWheelX == 0)) {
m_scrollTimer->stop();
} else if (!m_scrollTimer->isActive()) {
m_scrollTimer->start();
}
}
void QQuick3DXrVirtualMouse::setScrollWheelY(float scrollWheelY)
{
if (m_scrollWheelY == scrollWheelY)
return;
m_scrollWheelY = scrollWheelY;
emit scrollWheelYChanged(m_scrollWheelY);
if ((m_scrollTimer->isActive()) && (m_scrollWheelY == 0)) {
m_scrollTimer->stop();
} else if (!m_scrollTimer->isActive()) {
m_scrollTimer->start();
}
}
void QQuick3DXrVirtualMouse::setScrollTimerInterval(int scrollTimerInterval)
{
if (m_scrollTimerInterval == scrollTimerInterval)
return;
m_scrollTimerInterval = scrollTimerInterval;
m_scrollTimer->setInterval(m_scrollTimerInterval);
emit scrollTimerIntervalChanged(m_scrollTimerInterval);
}
void QQuick3DXrVirtualMouse::setScrollPixelDelta(int scrollPixelDelta)
{
if (m_scrollPixelDelta == scrollPixelDelta)
return;
m_scrollPixelDelta = scrollPixelDelta;
emit scrollPixelDeltaChanged(m_scrollPixelDelta);
}
void QQuick3DXrVirtualMouse::setSource(QQuick3DNode *source)
{
if (m_source == source)
return;
if (!source)
disconnect(m_source, &QQuick3DNode::sceneTransformChanged, this, &QQuick3DXrVirtualMouse::moveEvent);
m_source = source;
emit sourceChanged(m_source);
if (m_source)
connect(m_source, &QQuick3DNode::sceneTransformChanged, this, &QQuick3DXrVirtualMouse::moveEvent);
}
void QQuick3DXrVirtualMouse::setView(QQuick3DXrView *view)
{
if (m_view == view)
return;
m_view = view;
emit viewChanged(m_view);
}
void QQuick3DXrVirtualMouse::setEnabled(bool enabled)
{
if (m_enabled == enabled)
return;
m_enabled = enabled;
emit enabledChanged(m_enabled);
// Generate mouse release event if we disable when pressed
if (!m_enabled && (m_leftMouseButton || m_rightMouseButton || m_middleMouseButton)) {
Qt::MouseButton button = m_leftMouseButton ? Qt::LeftButton : m_rightMouseButton ? Qt::RightButton : Qt::MiddleButton;
m_leftMouseButton = m_rightMouseButton = m_middleMouseButton = false;
generateEvent(QEvent::MouseButtonRelease, button);
}
}
void QQuick3DXrVirtualMouse::moveEvent()
{
generateEvent(QEvent::MouseMove, Qt::NoButton);
}
void QQuick3DXrVirtualMouse::generateWheelEvent()
{
if (!m_view || !m_source || m_view->m_inDestructor || !m_enabled)
return;
// Get Ray
const QVector3D origin = m_source->scenePosition();
const QVector3D direction = m_source->forward();
float x = 0;
float y = 0;
if (m_scrollWheelX > 0) {
x = m_scrollPixelDelta * m_scrollWheelX * m_scrollWheelX;
} else if (m_scrollWheelX < 0) {
x = -m_scrollPixelDelta * m_scrollWheelX * m_scrollWheelX;
}
if (m_scrollWheelY > 0) {
y = m_scrollPixelDelta * m_scrollWheelY * m_scrollWheelY;
} else if (m_scrollWheelY < 0) {
y = -m_scrollPixelDelta * m_scrollWheelY * m_scrollWheelY;
}
QPoint pixelDelta = QPoint(x, y);
QPoint angleDelta = QPoint(x*8, y*8);
// Position will be generated by QtQuick3D
QWheelEvent *event = new QWheelEvent(QPointF(), QPointF(), pixelDelta, angleDelta,
Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
// Send to View with Ray
if (m_view->view3d()) // no internal view3D if XrView init failed but the object is still alive, handle this gracefully
m_view->view3d()->singlePointPick(event, origin, direction);
// Cleanup
delete event;
}
void QQuick3DXrVirtualMouse::generateEvent(QEvent::Type type, Qt::MouseButton button)
{
if (!m_view || !m_source || m_view->m_inDestructor || !m_enabled)
return;
// Get Ray
const QVector3D origin = m_source->scenePosition();
const QVector3D direction = m_source->forward();
// Generate Pointer Event
Qt::MouseButtons buttons = Qt::NoButton;
if (m_leftMouseButton)
buttons |= Qt::LeftButton;
if (m_rightMouseButton)
buttons |= Qt::RightButton;
if (m_middleMouseButton)
buttons |= Qt::MiddleButton;
// Position will be generated by QtQuick3D
QMouseEvent *event = new QMouseEvent(type, QPointF(), QPointF(), button, buttons, Qt::NoModifier);
// Send to View with Ray
if (m_view->view3d()) // no internal view3D if XrView init failed but the object is still alive, handle this gracefully
m_view->view3d()->singlePointPick(event, origin, direction);
// Cleanup
delete event;
}
QT_END_NAMESPACE