// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qgraphsinputhandler_p.h" #include #include #include #include #include "qquickgraphsitem_p.h" QGraphsInputHandler::QGraphsInputHandler(QQuickItem *parent) : QQuickItem(parent) , m_zoomEnabled(true) , m_zoomAtTarget(true) , m_rotationEnabled(true) , m_selectionEnabled(true) , m_pendingPoint(QPoint()) , m_pinchDiff(.0f) , m_graphsItem(nullptr) { m_pinchHandler = new QQuickPinchHandler(this); m_tapHandler = new QQuickTapHandler(this); // This is to support QQuickGraphsItem's mouseMove signal. setAcceptHoverEvents(true); m_dragHandler = new QQuickDragHandler(this); m_wheelHandler = new QQuickWheelHandler(this); m_dragHandler->setAcceptedButtons(Qt::MouseButton::RightButton); m_wheelHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse | QInputDevice::DeviceType::TouchPad); QObject::connect(m_tapHandler, &QQuickTapHandler::tapped, this, &QGraphsInputHandler::onTapped); QObject::connect(m_dragHandler, &QQuickDragHandler::translationChanged, this, &QGraphsInputHandler::onTranslationChanged); QObject::connect(m_dragHandler, &QQuickDragHandler::grabChanged, this, &QGraphsInputHandler::onGrabChanged); QObject::connect(m_wheelHandler, &QQuickWheelHandler::wheel, this, &QGraphsInputHandler::onWheel); QObject::connect(m_pinchHandler, &QQuickPinchHandler::scaleChanged, this, &QGraphsInputHandler::onPinchScaleChanged); QObject::connect(m_pinchHandler, &QQuickPinchHandler::grabChanged, this, &QGraphsInputHandler::onGrabChanged); } QGraphsInputHandler::~QGraphsInputHandler() {} void QGraphsInputHandler::setZoomEnabled(bool enable) { if (m_zoomEnabled != enable) { m_zoomEnabled = enable; if (m_graphsItem) emit m_graphsItem->zoomEnabledChanged(enable); } } bool QGraphsInputHandler::isZoomEnabled() { return m_zoomEnabled; } void QGraphsInputHandler::setZoomAtTargetEnabled(bool enable) { if (m_zoomAtTarget != enable) { m_zoomAtTarget = enable; if (m_graphsItem) emit m_graphsItem->zoomAtTargetEnabledChanged(enable); } } bool QGraphsInputHandler::isZoomAtTargetEnabled() { return m_zoomAtTarget; } void QGraphsInputHandler::setRotationEnabled(bool enable) { if (m_rotationEnabled != enable) { m_rotationEnabled = enable; if (m_graphsItem) emit m_graphsItem->rotationEnabledChanged(enable); } } bool QGraphsInputHandler::isRotationEnabled() { return m_rotationEnabled; } void QGraphsInputHandler::setSelectionEnabled(bool enable) { if (m_selectionEnabled != enable) { m_selectionEnabled = enable; if (m_graphsItem) emit m_graphsItem->selectionEnabledChanged(enable); } } bool QGraphsInputHandler::isSelectionEnabled() { return m_selectionEnabled; } void QGraphsInputHandler::setDefaultInputHandler() { setVisible(true); } void QGraphsInputHandler::unsetDefaultInputHandler() { setVisible(false); } void QGraphsInputHandler::unsetDefaultTapHandler() { QObject::disconnect(m_tapHandler, &QQuickTapHandler::tapped, this, &QGraphsInputHandler::onTapped); } void QGraphsInputHandler::unsetDefaultDragHandler() { QObject::disconnect(m_dragHandler, &QQuickDragHandler::translationChanged, this, &QGraphsInputHandler::onTranslationChanged); } void QGraphsInputHandler::unsetDefaultWheelHandler() { QObject::disconnect(m_wheelHandler, &QQuickWheelHandler::wheel, this, &QGraphsInputHandler::onWheel); } void QGraphsInputHandler::unsetDefaultPinchHandler() { QObject::disconnect(m_pinchHandler, &QQuickPinchHandler::scaleChanged, this, &QGraphsInputHandler::onPinchScaleChanged); } void QGraphsInputHandler::setDragButton(Qt::MouseButtons button) { m_dragHandler->setAcceptedButtons(button | Qt::MouseButton::RightButton); } void QGraphsInputHandler::setGraphsItem(QQuickGraphsItem *item) { m_graphsItem = item; QObject::connect(m_tapHandler, &QQuickTapHandler::tapped, item, &QQuickGraphsItem::tapped); QObject::connect(m_tapHandler, &QQuickTapHandler::doubleTapped, item, &QQuickGraphsItem::doubleTapped); QObject::connect(m_tapHandler, &QQuickTapHandler::longPressed, item, &QQuickGraphsItem::longPressed); QObject::connect(m_dragHandler, &QQuickDragHandler::translationChanged, item, &QQuickGraphsItem::dragged); QObject::connect(m_wheelHandler, &QQuickWheelHandler::wheel, item, &QQuickGraphsItem::wheel); QObject::connect(m_pinchHandler, &QQuickPinchHandler::scaleChanged, item, &QQuickGraphsItem::pinch); QObject::connect(this, &QGraphsInputHandler::mouseMove, item, &QQuickGraphsItem::mouseMove); } void QGraphsInputHandler::onTapped() { if (!m_selectionEnabled) return; if (m_graphsItem->isSlicingActive()) { m_graphsItem->setSliceActivatedChanged(true); m_graphsItem->update(); return; } m_graphsItem->doPicking(m_tapHandler->point().position()); } void QGraphsInputHandler::onTranslationChanged(QVector2D delta) { if (!m_rotationEnabled) return; if (m_dragHandler->centroid().pressedButtons().testFlag(Qt::LeftButton)) return; float rotationSpeed = 1.0f; #if !defined(Q_OS_IOS) rotationSpeed = 10.0f; #endif QQuickGraphsItem *item = m_graphsItem; // Calculate mouse movement since last frame float xRotation = item->cameraXRotation(); float yRotation = item->cameraYRotation(); // Apply to rotations xRotation += (delta.x() / rotationSpeed); yRotation += (delta.y() / rotationSpeed); item->setCameraXRotation(xRotation); item->setCameraYRotation(yRotation); } void QGraphsInputHandler::onGrabChanged(QPointingDevice::GrabTransition transition, QEventPoint point) { static QPointF pickPoint; if (transition == QPointingDevice::GrabPassive) { pickPoint = point.position().toPoint(); } else if (transition == QPointingDevice::GrabExclusive) { if (m_dragHandler->centroid().pressedButtons().testFlag(Qt::LeftButton)) m_graphsItem->doPicking(pickPoint); } else if (transition == QPointingDevice::UngrabExclusive || transition == QPointingDevice::UngrabPassive) { setPosition(QPointF(.0f, .0f)); setScale(1.f); setRotation(.0f); emit m_graphsItem->selectedElementChanged(QtGraphs3D::ElementType::None); pickPoint = QPointF(); } } void QGraphsInputHandler::onWheel(QQuickWheelEvent *event) { if (!m_zoomEnabled) return; int halfSizeZoomLevel = 50; int oneToOneZoomLevel = 100; int driftTowardCenterLevel = 175; float wheelZoomDrift = 0.1f; int nearZoomRangeDivider = 12; int midZoomRangeDivider = 60; int farZoomRangeDivider = 120; if (m_graphsItem->isSlicingActive()) return; // Adjust zoom level based on what zoom range we're in. QQuickGraphsItem *item = m_graphsItem; int zoomLevel = int(item->cameraZoomLevel()); const int minZoomLevel = int(item->minCameraZoomLevel()); const int maxZoomLevel = int(item->maxCameraZoomLevel()); if (zoomLevel > oneToOneZoomLevel) zoomLevel += event->angleDelta().y() / nearZoomRangeDivider; else if (zoomLevel > halfSizeZoomLevel) zoomLevel += event->angleDelta().y() / midZoomRangeDivider; else zoomLevel += event->angleDelta().y() / farZoomRangeDivider; zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel); if (m_zoomAtTarget) { QVector3D targetPosition = item->graphPositionAt(QPoint(event->x(), event->y())); float previousZoom = item->cameraZoomLevel(); item->setCameraZoomLevel(zoomLevel); float diffAdj = 0.0f; // If zooming in/out outside the graph, or zooming out after certain point, // move towards the center. if ((qAbs(targetPosition.x()) > 2.0f || qAbs(targetPosition.y()) > 2.0f || qAbs(targetPosition.z()) > 2.0f) || (previousZoom > zoomLevel && zoomLevel <= driftTowardCenterLevel)) { targetPosition = QVector3D(); // Add some extra correction so that we actually reach the center // eventually diffAdj = wheelZoomDrift; if (previousZoom > zoomLevel) diffAdj *= 2.0f; // Correct towards center little more when zooming out } float zoomFraction = 1.0f - (previousZoom / zoomLevel); // Adjust camera towards the zoom point, attempting to keep the cursor at // same graph point QVector3D oldTarget = item->cameraTargetPosition(); QVector3D origDiff = targetPosition - oldTarget; QVector3D diff = origDiff * zoomFraction + (origDiff.normalized() * diffAdj); if (diff.length() > origDiff.length()) diff = origDiff; item->setCameraTargetPosition(oldTarget + diff); } else { item->setCameraZoomLevel(zoomLevel); } item->update(); } void QGraphsInputHandler::onPinchScaleChanged(qreal delta) { if (!m_zoomEnabled) return; m_pinchDiff += (delta - 1.0f); int driftTowardCenterLevel = 175; float wheelZoomDrift = 0.1f; QQuickGraphsItem *item = m_graphsItem; int zoomLevel = int(item->cameraZoomLevel()); const int minZoomLevel = int(item->minCameraZoomLevel()); const int maxZoomLevel = int(item->maxCameraZoomLevel()); float zoomRate = qSqrt(qSqrt(zoomLevel)); if (m_pinchDiff > .0f) zoomLevel += zoomRate; else zoomLevel -= zoomRate; zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel); if (m_zoomAtTarget) { QVector3D targetPosition = item->graphPositionAt( m_pinchHandler->centroid().position().toPoint()); item->setCameraZoomLevel(zoomLevel); float diffAdj = 0.0f; // If zooming in/out outside the graph, or zooming out after certain point, // move towards the center. if ((qAbs(targetPosition.x()) > 2.0f || qAbs(targetPosition.y()) > 2.0f || qAbs(targetPosition.z()) > 2.0f) || (m_pinchDiff > .0f && zoomLevel <= driftTowardCenterLevel)) { targetPosition = QVector3D(); // Add some extra correction so that we actually reach the center // eventually diffAdj = wheelZoomDrift; if (m_pinchDiff > .0f) diffAdj *= 2.0f; // Correct towards center little more when zooming out } // Adjust camera towards the zoom point, attempting to keep the cursor at // same graph point QVector3D oldTarget = item->cameraTargetPosition(); QVector3D origDiff = targetPosition - oldTarget; QVector3D diff = origDiff * m_pinchDiff + (origDiff.normalized() * diffAdj); if (diff.length() > origDiff.length()) diff = origDiff; item->setCameraTargetPosition(oldTarget + diff); } else { item->setCameraZoomLevel(zoomLevel); } m_pinchDiff = .0f; } void QGraphsInputHandler::hoverMoveEvent(QHoverEvent *event) { Q_EMIT mouseMove(event->oldPos()); }