// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qquick3dxrvirtualmouse_p.h" #include "qquick3dxrview_p.h" #include #include 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