1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "datavisualizationglobal_p.h"
5#include "q3dinputhandler_p.h"
6#include "abstract3dcontroller_p.h"
7
8QT_BEGIN_NAMESPACE
9
10static const int halfSizeZoomLevel = 50;
11static const int oneToOneZoomLevel = 100;
12static const int driftTowardCenterLevel = 175;
13static const float wheelZoomDrift = 0.1f;
14
15static const int nearZoomRangeDivider = 12;
16static const int midZoomRangeDivider = 60;
17static const int farZoomRangeDivider = 120;
18
19[[maybe_unused]] static const float rotationSpeed = 100.0f;
20
21/*!
22 * \class Q3DInputHandler
23 * \inmodule QtDataVisualization
24 * \brief Basic wheel mouse based input handler.
25 * \since QtDataVisualization 1.0
26 *
27 * Q3DInputHandler is the basic input handler for wheel mouse type of input devices.
28 *
29 * Default input handler has the following functionalty:
30 * \table
31 * \header
32 * \li Mouse action
33 * \li Action
34 * \row
35 * \li Drag with right button pressed
36 * \li Rotate graph within limits set for Q3DCamera.
37 * \row
38 * \li Left click
39 * \li Select item under cursor or remove selection if none.
40 * May open the secondary view depending on the
41 * \l {QAbstract3DGraph::selectionMode}{selection mode}.
42 * \row
43 * \li Mouse wheel
44 * \li Zoom in/out within the allowable zoom range set for Q3DCamera.
45 * \row
46 * \li Left click on the primary view when the secondary view is visible
47 * \li Closes the secondary view.
48 * \note Secondary view is available only for Q3DBars and Q3DSurface graphs.
49 * \endtable
50 *
51 * Rotation, zoom, and selection can each be individually disabled using
52 * corresponding properties of this class.
53 */
54
55/*!
56 * \qmltype InputHandler3D
57 * \inqmlmodule QtDataVisualization
58 * \since QtDataVisualization 1.2
59 * \ingroup datavisualization_qml
60 * \instantiates Q3DInputHandler
61 * \brief Basic wheel mouse based input handler.
62 *
63 * InputHandler3D is the basic input handler for wheel mouse type of input devices.
64 *
65 * See Q3DInputHandler documentation for more details.
66 */
67
68/*!
69 * \qmlproperty bool InputHandler3D::rotationEnabled
70 * \since QtDataVisualization 1.2
71 *
72 * Defines whether this input handler allows graph rotation.
73 * Defaults to \c{true}.
74 */
75
76/*!
77 * \qmlproperty bool InputHandler3D::zoomEnabled
78 * \since QtDataVisualization 1.2
79 *
80 * Defines whether this input handler allows graph zooming.
81 * Defaults to \c{true}.
82 */
83
84/*!
85 * \qmlproperty bool InputHandler3D::selectionEnabled
86 * \since QtDataVisualization 1.2
87 *
88 * Defines whether this input handler allows selection from the graph.
89 * Defaults to \c{true}.
90 */
91
92/*!
93 * \qmlproperty bool InputHandler3D::zoomAtTargetEnabled
94 * \since QtDataVisualization 1.2
95 *
96 * Defines whether zooming changes the camera target to the position of the input
97 * at the time of the zoom.
98 * Defaults to \c{true}.
99 */
100
101/*!
102 * Constructs the basic mouse input handler. An optional \a parent parameter can be given
103 * and is then passed to QObject constructor.
104 */
105Q3DInputHandler::Q3DInputHandler(QObject *parent) :
106 QAbstract3DInputHandler(parent),
107 d_ptr(new Q3DInputHandlerPrivate(this))
108{
109}
110
111/*!
112 * Destroys the input handler.
113 */
114Q3DInputHandler::~Q3DInputHandler()
115{
116}
117
118// Input event listeners
119/*!
120 * Override this to change handling of mouse press events.
121 * Mouse press event is given in the \a event and the mouse position in \a mousePos.
122 */
123void Q3DInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos)
124{
125#if defined(Q_OS_IOS)
126 Q_UNUSED(event);
127 Q_UNUSED(mousePos);
128#else
129 if (Qt::LeftButton == event->button()) {
130 if (isSelectionEnabled()) {
131 if (scene()->isSlicingActive()) {
132 if (scene()->isPointInPrimarySubView(point: mousePos))
133 setInputView(InputViewOnPrimary);
134 else if (scene()->isPointInSecondarySubView(point: mousePos))
135 setInputView(InputViewOnSecondary);
136 else
137 setInputView(InputViewNone);
138 } else {
139 // update mouse positions to prevent jumping when releasing or repressing a button
140 setInputPosition(mousePos);
141 scene()->setSelectionQueryPosition(mousePos);
142 setInputView(InputViewOnPrimary);
143 d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting;
144 }
145 }
146 } else if (Qt::MiddleButton == event->button()) {
147 if (isRotationEnabled()) {
148 // reset rotations
149 setInputPosition(QPoint(0, 0));
150 }
151 } else if (Qt::RightButton == event->button()) {
152 if (isRotationEnabled()) {
153 // disable rotating when in slice view
154 if (!scene()->isSlicingActive())
155 d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating;
156 // update mouse positions to prevent jumping when releasing or repressing a button
157 setInputPosition(mousePos);
158 }
159 }
160#endif
161}
162
163/*!
164 * Override this to change handling of mouse release events.
165 * Mouse release event is given in the \a event and the mouse position in \a mousePos.
166 */
167void Q3DInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos)
168{
169 Q_UNUSED(event);
170#if defined(Q_OS_IOS)
171 Q_UNUSED(mousePos);
172#else
173 if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState) {
174 // update mouse positions to prevent jumping when releasing or repressing a button
175 setInputPosition(mousePos);
176 }
177 d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone;
178 setInputView(InputViewNone);
179#endif
180}
181
182/*!
183 * Override this to change handling of mouse move events.
184 * Mouse move event is given in the \a event and the mouse position in \a mousePos.
185 */
186void Q3DInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
187{
188 Q_UNUSED(event);
189#if defined(Q_OS_IOS)
190 Q_UNUSED(mousePos);
191#else
192 if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState
193 && isRotationEnabled()) {
194 // Calculate mouse movement since last frame
195 float xRotation = scene()->activeCamera()->xRotation();
196 float yRotation = scene()->activeCamera()->yRotation();
197 float mouseMoveX = float(inputPosition().x() - mousePos.x())
198 / (scene()->viewport().width() / rotationSpeed);
199 float mouseMoveY = float(inputPosition().y() - mousePos.y())
200 / (scene()->viewport().height() / rotationSpeed);
201 // Apply to rotations
202 xRotation -= mouseMoveX;
203 yRotation -= mouseMoveY;
204 scene()->activeCamera()->setXRotation(xRotation);
205 scene()->activeCamera()->setYRotation(yRotation);
206
207 setPreviousInputPos(inputPosition());
208 setInputPosition(mousePos);
209 }
210#endif
211}
212
213#if QT_CONFIG(wheelevent)
214/*!
215 * Override this to change handling of wheel events.
216 * The wheel event is given in the \a event.
217 */
218void Q3DInputHandler::wheelEvent(QWheelEvent *event)
219{
220 if (isZoomEnabled()) {
221 // disable zooming if in slice view
222 if (scene()->isSlicingActive())
223 return;
224
225 // Adjust zoom level based on what zoom range we're in.
226 Q3DCamera *camera = scene()->activeCamera();
227 int zoomLevel = int(camera->zoomLevel());
228 const int minZoomLevel = int(camera->minZoomLevel());
229 const int maxZoomLevel = int(camera->maxZoomLevel());
230 if (zoomLevel > oneToOneZoomLevel)
231 zoomLevel += event->angleDelta().y() / nearZoomRangeDivider;
232 else if (zoomLevel > halfSizeZoomLevel)
233 zoomLevel += event->angleDelta().y() / midZoomRangeDivider;
234 else
235 zoomLevel += event->angleDelta().y() / farZoomRangeDivider;
236 zoomLevel = qBound(min: minZoomLevel, val: zoomLevel, max: maxZoomLevel);
237
238 if (isZoomAtTargetEnabled()) {
239 scene()->setGraphPositionQuery(event->position().toPoint());
240 d_ptr->m_zoomAtTargetPending = true;
241 // If zoom at target is enabled, we don't want to zoom yet, as that causes
242 // jitter. Instead, we zoom next frame, when we apply the camera position.
243 d_ptr->m_requestedZoomLevel = zoomLevel;
244 d_ptr->m_driftMultiplier = wheelZoomDrift;
245 } else {
246 camera->setZoomLevel(zoomLevel);
247 }
248 }
249}
250#endif
251
252/*!
253 * \property Q3DInputHandler::rotationEnabled
254 * \since QtDataVisualization 1.2
255 *
256 * \brief Whether this input handler allows graph rotation.
257 *
258 * Defaults to \c{true}.
259 */
260void Q3DInputHandler::setRotationEnabled(bool enable)
261{
262 if (d_ptr->m_rotationEnabled != enable) {
263 d_ptr->m_rotationEnabled = enable;
264 emit rotationEnabledChanged(enable);
265 }
266}
267
268bool Q3DInputHandler::isRotationEnabled() const
269{
270 return d_ptr->m_rotationEnabled;
271}
272
273/*!
274 * \property Q3DInputHandler::zoomEnabled
275 * \since QtDataVisualization 1.2
276 *
277 * \brief Whether this input handler allows graph zooming.
278 *
279 * Defaults to \c{true}.
280 */
281void Q3DInputHandler::setZoomEnabled(bool enable)
282{
283 if (d_ptr->m_zoomEnabled != enable) {
284 d_ptr->m_zoomEnabled = enable;
285 emit zoomEnabledChanged(enable);
286 }
287}
288
289bool Q3DInputHandler::isZoomEnabled() const
290{
291 return d_ptr->m_zoomEnabled;
292}
293
294/*!
295 * \property Q3DInputHandler::selectionEnabled
296 * \since QtDataVisualization 1.2
297 *
298 * \brief Whether this input handler allows selection from the graph.
299 *
300 * Defaults to \c{true}.
301 */
302void Q3DInputHandler::setSelectionEnabled(bool enable)
303{
304 if (d_ptr->m_selectionEnabled != enable) {
305 d_ptr->m_selectionEnabled = enable;
306 emit selectionEnabledChanged(enable);
307 }
308}
309
310bool Q3DInputHandler::isSelectionEnabled() const
311{
312 return d_ptr->m_selectionEnabled;
313}
314
315/*!
316 * \property Q3DInputHandler::zoomAtTargetEnabled
317 * \since QtDataVisualization 1.2
318 *
319 * \brief Whether zooming should change the camera target so that the zoomed point
320 * of the graph stays at the same location after the zoom.
321 *
322 * Defaults to \c{true}.
323 */
324void Q3DInputHandler::setZoomAtTargetEnabled(bool enable)
325{
326 if (d_ptr->m_zoomAtTargetEnabled != enable) {
327 d_ptr->m_zoomAtTargetEnabled = enable;
328 emit zoomAtTargetEnabledChanged(enable);
329 }
330}
331
332bool Q3DInputHandler::isZoomAtTargetEnabled() const
333{
334 return d_ptr->m_zoomAtTargetEnabled;
335}
336
337Q3DInputHandlerPrivate::Q3DInputHandlerPrivate(Q3DInputHandler *q)
338 : q_ptr(q),
339 m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone),
340 m_rotationEnabled(true),
341 m_zoomEnabled(true),
342 m_selectionEnabled(true),
343 m_zoomAtTargetEnabled(true),
344 m_zoomAtTargetPending(false),
345 m_controller(0),
346 m_requestedZoomLevel(0.0f),
347 m_driftMultiplier(0.0f)
348{
349 QObject::connect(sender: q, signal: &QAbstract3DInputHandler::sceneChanged,
350 context: this, slot: &Q3DInputHandlerPrivate::handleSceneChange);
351}
352
353Q3DInputHandlerPrivate::~Q3DInputHandlerPrivate()
354{
355}
356
357void Q3DInputHandlerPrivate::handleSceneChange(Q3DScene *scene)
358{
359 if (scene) {
360 if (m_controller) {
361 QObject::disconnect(sender: m_controller, signal: &Abstract3DController::queriedGraphPositionChanged,
362 receiver: this, slot: &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange);
363 }
364
365 m_controller = qobject_cast<Abstract3DController *>(object: scene->parent());
366
367 if (m_controller) {
368 QObject::connect(sender: m_controller, signal: &Abstract3DController::queriedGraphPositionChanged,
369 context: this, slot: &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange);
370 }
371 }
372}
373
374void Q3DInputHandlerPrivate::handleQueriedGraphPositionChange()
375{
376 if (m_zoomAtTargetPending) {
377 // Check if the zoom point is on graph
378 QVector3D newTarget = m_controller->queriedGraphPosition();
379 float currentZoom = m_requestedZoomLevel;
380 float previousZoom = q_ptr->scene()->activeCamera()->zoomLevel();
381 q_ptr->scene()->activeCamera()->setZoomLevel(currentZoom);
382 float diffAdj = 0.0f;
383
384 // If zooming in/out outside the graph, or zooming out after certain point,
385 // move towards the center.
386 if ((qAbs(t: newTarget.x()) > 1.0f
387 || qAbs(t: newTarget.y()) > 1.0f
388 || qAbs(t: newTarget.z()) > 1.0f)
389 || (previousZoom > currentZoom && currentZoom <= driftTowardCenterLevel)) {
390 newTarget = zeroVector;
391 // Add some extra correction so that we actually reach the center eventually
392 diffAdj = m_driftMultiplier;
393 if (previousZoom > currentZoom)
394 diffAdj *= 2.0f; // Correct towards center little more when zooming out
395 }
396
397 float zoomFraction = 1.0f - (previousZoom / currentZoom);
398
399 // Adjust camera towards the zoom point, attempting to keep the cursor at same graph point
400 QVector3D oldTarget = q_ptr->scene()->activeCamera()->target();
401 QVector3D origDiff = newTarget - oldTarget;
402 QVector3D diff = origDiff * zoomFraction + (origDiff.normalized() * diffAdj);
403 if (diff.length() > origDiff.length())
404 diff = origDiff;
405 q_ptr->scene()->activeCamera()->setTarget(oldTarget + diff);
406
407 if (q_ptr->scene()->selectionQueryPosition() == Q3DScene::invalidSelectionPoint())
408 m_zoomAtTargetPending = false;
409 }
410}
411
412QT_END_NAMESPACE
413

source code of qtdatavis3d/src/datavisualization/input/q3dinputhandler.cpp