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 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | static const int halfSizeZoomLevel = 50; |
11 | static const int oneToOneZoomLevel = 100; |
12 | static const int driftTowardCenterLevel = 175; |
13 | static const float wheelZoomDrift = 0.1f; |
14 | |
15 | static const int nearZoomRangeDivider = 12; |
16 | static const int midZoomRangeDivider = 60; |
17 | static 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 | */ |
105 | Q3DInputHandler::Q3DInputHandler(QObject *parent) : |
106 | QAbstract3DInputHandler(parent), |
107 | d_ptr(new Q3DInputHandlerPrivate(this)) |
108 | { |
109 | } |
110 | |
111 | /*! |
112 | * Destroys the input handler. |
113 | */ |
114 | Q3DInputHandler::~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 | */ |
123 | void 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 | */ |
167 | void 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 | */ |
186 | void 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 | */ |
218 | void 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 | */ |
260 | void 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 | |
268 | bool 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 | */ |
281 | void 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 | |
289 | bool 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 | */ |
302 | void 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 | |
310 | bool 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 | */ |
324 | void 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 | |
332 | bool Q3DInputHandler::isZoomAtTargetEnabled() const |
333 | { |
334 | return d_ptr->m_zoomAtTargetEnabled; |
335 | } |
336 | |
337 | Q3DInputHandlerPrivate::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 | |
353 | Q3DInputHandlerPrivate::~Q3DInputHandlerPrivate() |
354 | { |
355 | } |
356 | |
357 | void 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 | |
374 | void 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 | |
412 | QT_END_NAMESPACE |
413 | |