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