1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qgraphsinputhandler_p.h"
5
6#include <QtQuick/private/qquickdraghandler_p.h>
7#include <QtQuick/private/qquickpinchhandler_p.h>
8#include <QtQuick/private/qquicktaphandler_p.h>
9#include <QtQuick/private/qquickwheelhandler_p.h>
10
11#include "qquickgraphsitem_p.h"
12#include "qgraphs3dlogging_p.h"
13
14QGraphsInputHandler::QGraphsInputHandler(QQuickItem *parent)
15 : QQuickItem(parent)
16 , m_zoomEnabled(true)
17 , m_zoomAtTarget(true)
18 , m_rotationEnabled(true)
19 , m_selectionEnabled(true)
20 , m_pendingPoint(QPoint())
21 , m_pinchDiff(.0f)
22 , m_graphsItem(nullptr)
23{
24 m_pinchHandler = new QQuickPinchHandler(this);
25 m_tapHandler = new QQuickTapHandler(this);
26
27 // This is to support QQuickGraphsItem's mouseMove signal.
28 setAcceptHoverEvents(true);
29 m_dragHandler = new QQuickDragHandler(this);
30 m_wheelHandler = new QQuickWheelHandler(this);
31 m_dragHandler->setAcceptedButtons(Qt::MouseButton::RightButton);
32 m_wheelHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse
33 | QInputDevice::DeviceType::TouchPad);
34 QObject::connect(sender: m_tapHandler, signal: &QQuickTapHandler::tapped, context: this, slot: &QGraphsInputHandler::onTapped);
35 QObject::connect(sender: m_dragHandler,
36 signal: &QQuickDragHandler::translationChanged,
37 context: this,
38 slot: &QGraphsInputHandler::onTranslationChanged);
39 QObject::connect(sender: m_dragHandler,
40 signal: &QQuickDragHandler::grabChanged,
41 context: this,
42 slot: &QGraphsInputHandler::onGrabChanged);
43 QObject::connect(sender: m_wheelHandler,
44 signal: &QQuickWheelHandler::wheel,
45 context: this,
46 slot: &QGraphsInputHandler::onWheel);
47 QObject::connect(sender: m_pinchHandler,
48 signal: &QQuickPinchHandler::scaleChanged,
49 context: this,
50 slot: &QGraphsInputHandler::onPinchScaleChanged);
51 QObject::connect(sender: m_pinchHandler,
52 signal: &QQuickPinchHandler::grabChanged,
53 context: this,
54 slot: &QGraphsInputHandler::onGrabChanged);
55}
56
57QGraphsInputHandler::~QGraphsInputHandler() {}
58
59void QGraphsInputHandler::setZoomEnabled(bool enable)
60{
61 if (m_zoomEnabled == enable) {
62 qCDebug(lcProperties3D) << __FUNCTION__
63 << "value is already set to:" << enable;
64 return;
65 }
66 m_zoomEnabled = enable;
67 if (m_graphsItem)
68 emit m_graphsItem->zoomEnabledChanged(enable);
69}
70
71bool QGraphsInputHandler::isZoomEnabled()
72{
73 return m_zoomEnabled;
74}
75
76void QGraphsInputHandler::setZoomAtTargetEnabled(bool enable)
77{
78 if (m_zoomAtTarget == enable) {
79 qCDebug(lcProperties3D) << __FUNCTION__
80 << "value is already set to:" << enable;
81 return;
82 }
83 m_zoomAtTarget = enable;
84 if (m_graphsItem)
85 emit m_graphsItem->zoomAtTargetEnabledChanged(enable);
86}
87
88bool QGraphsInputHandler::isZoomAtTargetEnabled()
89{
90 return m_zoomAtTarget;
91}
92
93void QGraphsInputHandler::setRotationEnabled(bool enable)
94{
95 if (m_rotationEnabled == enable) {
96 qCDebug(lcProperties3D) << __FUNCTION__
97 << "value is already set to:" << enable;
98 }
99 m_rotationEnabled = enable;
100 if (m_graphsItem)
101 emit m_graphsItem->rotationEnabledChanged(enable);
102}
103
104bool QGraphsInputHandler::isRotationEnabled()
105{
106 return m_rotationEnabled;
107}
108
109void QGraphsInputHandler::setSelectionEnabled(bool enable)
110{
111 if (m_selectionEnabled == enable) {
112 qCDebug(lcProperties3D) << __FUNCTION__
113 << "value is already set to:" << enable;
114 return;
115 }
116 m_selectionEnabled = enable;
117 if (m_graphsItem)
118 emit m_graphsItem->selectionEnabledChanged(enable);
119}
120
121bool QGraphsInputHandler::isSelectionEnabled()
122{
123 return m_selectionEnabled;
124}
125
126void QGraphsInputHandler::setDefaultInputHandler()
127{
128 setVisible(true);
129}
130
131void QGraphsInputHandler::unsetDefaultInputHandler()
132{
133 setVisible(false);
134}
135
136void QGraphsInputHandler::unsetDefaultTapHandler()
137{
138 QObject::disconnect(sender: m_tapHandler,
139 signal: &QQuickTapHandler::tapped,
140 receiver: this,
141 slot: &QGraphsInputHandler::onTapped);
142}
143
144void QGraphsInputHandler::unsetDefaultDragHandler()
145{
146 QObject::disconnect(sender: m_dragHandler,
147 signal: &QQuickDragHandler::translationChanged,
148 receiver: this,
149 slot: &QGraphsInputHandler::onTranslationChanged);
150}
151
152void QGraphsInputHandler::unsetDefaultWheelHandler()
153{
154 QObject::disconnect(sender: m_wheelHandler,
155 signal: &QQuickWheelHandler::wheel,
156 receiver: this,
157 slot: &QGraphsInputHandler::onWheel);
158}
159
160void QGraphsInputHandler::unsetDefaultPinchHandler()
161{
162 QObject::disconnect(sender: m_pinchHandler,
163 signal: &QQuickPinchHandler::scaleChanged,
164 receiver: this,
165 slot: &QGraphsInputHandler::onPinchScaleChanged);
166}
167
168void QGraphsInputHandler::setDragButton(Qt::MouseButtons button)
169{
170 m_dragHandler->setAcceptedButtons(button | Qt::MouseButton::RightButton);
171}
172
173void QGraphsInputHandler::setGraphsItem(QQuickGraphsItem *item)
174{
175 m_graphsItem = item;
176 QObject::connect(sender: m_tapHandler, signal: &QQuickTapHandler::tapped, context: item, slot: &QQuickGraphsItem::tapped);
177 QObject::connect(sender: m_tapHandler,
178 signal: &QQuickTapHandler::doubleTapped,
179 context: item,
180 slot: &QQuickGraphsItem::doubleTapped);
181 QObject::connect(sender: m_tapHandler,
182 signal: &QQuickTapHandler::longPressed,
183 context: item,
184 slot: &QQuickGraphsItem::longPressed);
185 QObject::connect(sender: m_dragHandler,
186 signal: &QQuickDragHandler::translationChanged,
187 context: item,
188 slot: &QQuickGraphsItem::dragged);
189 QObject::connect(sender: m_wheelHandler, signal: &QQuickWheelHandler::wheel, context: item, slot: &QQuickGraphsItem::wheel);
190 QObject::connect(sender: m_pinchHandler,
191 signal: &QQuickPinchHandler::scaleChanged,
192 context: item,
193 slot: &QQuickGraphsItem::pinch);
194 QObject::connect(sender: this, signal: &QGraphsInputHandler::mouseMove, context: item, slot: &QQuickGraphsItem::mouseMove);
195}
196
197void QGraphsInputHandler::onTapped()
198{
199 if (!m_selectionEnabled)
200 return;
201
202 if (m_graphsItem->isSlicingActive()) {
203 m_graphsItem->setSliceActivatedChanged(true);
204 m_graphsItem->update();
205 return;
206 }
207 m_graphsItem->doPicking(point: m_tapHandler->point().position());
208}
209
210void QGraphsInputHandler::onTranslationChanged(QVector2D delta)
211{
212 if (!m_rotationEnabled)
213 return;
214
215 if (m_dragHandler->centroid().pressedButtons().testFlag(flag: Qt::LeftButton))
216 return;
217
218 float rotationSpeed = 1.0f;
219#if !defined(Q_OS_IOS)
220 rotationSpeed = 10.0f;
221#endif
222 QQuickGraphsItem *item = m_graphsItem;
223 // Calculate mouse movement since last frame
224 float xRotation = item->cameraXRotation();
225 float yRotation = item->cameraYRotation();
226 // Apply to rotations
227 xRotation += (delta.x() / rotationSpeed);
228 yRotation += (delta.y() / rotationSpeed);
229 item->setCameraXRotation(xRotation);
230 item->setCameraYRotation(yRotation);
231}
232
233void QGraphsInputHandler::onGrabChanged(QPointingDevice::GrabTransition transition,
234 QEventPoint point)
235{
236 static QPointF pickPoint;
237
238 if (transition == QPointingDevice::GrabPassive) {
239 pickPoint = point.position().toPoint();
240 } else if (transition == QPointingDevice::GrabExclusive) {
241 if (m_dragHandler->centroid().pressedButtons().testFlag(flag: Qt::LeftButton))
242 m_graphsItem->doPicking(point: pickPoint);
243 } else if (transition == QPointingDevice::UngrabExclusive
244 || transition == QPointingDevice::UngrabPassive) {
245 setPosition(QPointF(.0f, .0f));
246 setScale(1.f);
247 setRotation(.0f);
248 emit m_graphsItem->selectedElementChanged(type: QtGraphs3D::ElementType::None);
249 pickPoint = QPointF();
250 }
251}
252
253void QGraphsInputHandler::onWheel(QQuickWheelEvent *event)
254{
255 if (!m_zoomEnabled) {
256 qCWarning(lcEvents3D, "%s zooming needs to be enabled",
257 qUtf8Printable(QLatin1String(__FUNCTION__)));
258 return;
259 }
260
261 int halfSizeZoomLevel = 50;
262 int oneToOneZoomLevel = 100;
263
264 int driftTowardCenterLevel = 175;
265 float wheelZoomDrift = 0.1f;
266
267 int nearZoomRangeDivider = 12;
268 int midZoomRangeDivider = 60;
269 int farZoomRangeDivider = 120;
270
271 if (m_graphsItem->isSlicingActive())
272 return;
273
274 // Adjust zoom level based on what zoom range we're in.
275 QQuickGraphsItem *item = m_graphsItem;
276 int zoomLevel = int(item->cameraZoomLevel());
277 const int minZoomLevel = int(item->minCameraZoomLevel());
278 const int maxZoomLevel = int(item->maxCameraZoomLevel());
279 if (zoomLevel > oneToOneZoomLevel)
280 zoomLevel += event->angleDelta().y() / nearZoomRangeDivider;
281 else if (zoomLevel > halfSizeZoomLevel)
282 zoomLevel += event->angleDelta().y() / midZoomRangeDivider;
283 else
284 zoomLevel += event->angleDelta().y() / farZoomRangeDivider;
285 zoomLevel = qBound(min: minZoomLevel, val: zoomLevel, max: maxZoomLevel);
286
287 if (m_zoomAtTarget) {
288 QVector3D targetPosition = item->graphPositionAt(point: QPoint(event->x(), event->y()));
289 float previousZoom = item->cameraZoomLevel();
290 item->setCameraZoomLevel(zoomLevel);
291
292 float diffAdj = 0.0f;
293
294 // If zooming in/out outside the graph, or zooming out after certain point,
295 // move towards the center.
296 if ((qAbs(t: targetPosition.x()) > 2.0f || qAbs(t: targetPosition.y()) > 2.0f
297 || qAbs(t: targetPosition.z()) > 2.0f)
298 || (previousZoom > zoomLevel && zoomLevel <= driftTowardCenterLevel)) {
299 targetPosition = QVector3D();
300 // Add some extra correction so that we actually reach the center
301 // eventually
302 diffAdj = wheelZoomDrift;
303 if (previousZoom > zoomLevel)
304 diffAdj *= 2.0f; // Correct towards center little more when zooming out
305 }
306
307 float zoomFraction = 1.0f - (previousZoom / zoomLevel);
308
309 // Adjust camera towards the zoom point, attempting to keep the cursor at
310 // same graph point
311 QVector3D oldTarget = item->cameraTargetPosition();
312 QVector3D origDiff = targetPosition - oldTarget;
313 QVector3D diff = origDiff * zoomFraction + (origDiff.normalized() * diffAdj);
314 if (diff.length() > origDiff.length())
315 diff = origDiff;
316 item->setCameraTargetPosition(oldTarget + diff);
317 } else {
318 item->setCameraZoomLevel(zoomLevel);
319 }
320
321 item->update();
322}
323
324void QGraphsInputHandler::onPinchScaleChanged(qreal delta)
325{
326 if (!m_zoomEnabled) {
327 qCWarning(lcEvents3D, "%s zooming needs to be enabled",
328 qUtf8Printable(QLatin1String(__FUNCTION__)));
329 return;
330 }
331
332 m_pinchDiff += (delta - 1.0f);
333 int driftTowardCenterLevel = 175;
334 float wheelZoomDrift = 0.1f;
335
336 QQuickGraphsItem *item = m_graphsItem;
337 int zoomLevel = int(item->cameraZoomLevel());
338 const int minZoomLevel = int(item->minCameraZoomLevel());
339 const int maxZoomLevel = int(item->maxCameraZoomLevel());
340 float zoomRate = qSqrt(v: qSqrt(v: zoomLevel));
341 if (m_pinchDiff > .0f)
342 zoomLevel += zoomRate;
343 else
344 zoomLevel -= zoomRate;
345 zoomLevel = qBound(min: minZoomLevel, val: zoomLevel, max: maxZoomLevel);
346 if (m_zoomAtTarget) {
347 QVector3D targetPosition = item->graphPositionAt(
348 point: m_pinchHandler->centroid().position().toPoint());
349 item->setCameraZoomLevel(zoomLevel);
350
351 float diffAdj = 0.0f;
352
353 // If zooming in/out outside the graph, or zooming out after certain point,
354 // move towards the center.
355 if ((qAbs(t: targetPosition.x()) > 2.0f || qAbs(t: targetPosition.y()) > 2.0f
356 || qAbs(t: targetPosition.z()) > 2.0f)
357 || (m_pinchDiff > .0f && zoomLevel <= driftTowardCenterLevel)) {
358 targetPosition = QVector3D();
359 // Add some extra correction so that we actually reach the center
360 // eventually
361 diffAdj = wheelZoomDrift;
362 if (m_pinchDiff > .0f)
363 diffAdj *= 2.0f; // Correct towards center little more when zooming out
364 }
365
366 // Adjust camera towards the zoom point, attempting to keep the cursor at
367 // same graph point
368 QVector3D oldTarget = item->cameraTargetPosition();
369 QVector3D origDiff = targetPosition - oldTarget;
370 QVector3D diff = origDiff * m_pinchDiff + (origDiff.normalized() * diffAdj);
371 if (diff.length() > origDiff.length())
372 diff = origDiff;
373 item->setCameraTargetPosition(oldTarget + diff);
374 } else {
375 item->setCameraZoomLevel(zoomLevel);
376 }
377 m_pinchDiff = .0f;
378}
379
380void QGraphsInputHandler::hoverMoveEvent(QHoverEvent *event)
381{
382 Q_EMIT mouseMove(mousePos: event->oldPos());
383}
384

source code of qtgraphs/src/graphs3d/input/qgraphsinputhandler.cpp