1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Data Visualization module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 or (at your option) any later version |
20 | ** approved by the KDE Free Qt Foundation. The licenses are as published by |
21 | ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | |
30 | #include "qtouch3dinputhandler_p.h" |
31 | #include <QtCore/QTimer> |
32 | #include <QtCore/qmath.h> |
33 | |
34 | QT_BEGIN_NAMESPACE_DATAVISUALIZATION |
35 | |
36 | static const float maxTapAndHoldJitter = 20.0f; |
37 | static const int maxPinchJitter = 10; |
38 | #if defined (Q_OS_ANDROID) || defined(Q_OS_IOS) |
39 | static const int maxSelectionJitter = 10; |
40 | #else |
41 | static const int maxSelectionJitter = 5; |
42 | #endif |
43 | static const int tapAndHoldTime = 250; |
44 | static const float rotationSpeed = 200.0f; |
45 | static const float touchZoomDrift = 0.02f; |
46 | |
47 | /*! |
48 | * \class QTouch3DInputHandler |
49 | * \inmodule QtDataVisualization |
50 | * \brief Basic touch display based input handler. |
51 | * \since QtDataVisualization 1.0 |
52 | * |
53 | * QTouch3DInputHandler is the basic input handler for touch screen devices. |
54 | * |
55 | * Default touch input handler has the following functionalty: |
56 | * \table |
57 | * \header |
58 | * \li Gesture |
59 | * \li Action |
60 | * \row |
61 | * \li Touch-And-Move |
62 | * \li Rotate graph within limits set for Q3DCamera |
63 | * \row |
64 | * \li Tap |
65 | * \li Select the item tapped or remove selection if none. |
66 | * May open the secondary view depending on the |
67 | * \l {QAbstract3DGraph::selectionMode}{selection mode}. |
68 | * \row |
69 | * \li Tap-And-Hold |
70 | * \li Same as tap. |
71 | * \row |
72 | * \li Pinch |
73 | * \li Zoom in/out within the allowable zoom range set for Q3DCamera. |
74 | * \row |
75 | * \li Tap on the primary view when the secondary view is visible |
76 | * \li Closes the secondary view. |
77 | * \note Secondary view is available only for Q3DBars and Q3DSurface graphs. |
78 | * \endtable |
79 | * |
80 | * Rotation, zoom, and selection can each be individually disabled using |
81 | * corresponding Q3DInputHandler properties. |
82 | */ |
83 | |
84 | /*! |
85 | * \qmltype TouchInputHandler3D |
86 | * \inqmlmodule QtDataVisualization |
87 | * \since QtDataVisualization 1.2 |
88 | * \ingroup datavisualization_qml |
89 | * \instantiates QTouch3DInputHandler |
90 | * \inherits InputHandler3D |
91 | * \brief Basic touch display based input handler. |
92 | * |
93 | * TouchInputHandler3D is the basic input handler for touch screen devices. |
94 | * |
95 | * See QTouch3DInputHandler documentation for more details. |
96 | */ |
97 | |
98 | /*! |
99 | * Constructs the basic touch display input handler. An optional \a parent parameter can be given |
100 | * and is then passed to QObject constructor. |
101 | */ |
102 | QTouch3DInputHandler::QTouch3DInputHandler(QObject *parent) |
103 | : Q3DInputHandler(parent), |
104 | d_ptr(new QTouch3DInputHandlerPrivate(this)) |
105 | { |
106 | } |
107 | |
108 | /*! |
109 | * Destroys the input handler. |
110 | */ |
111 | QTouch3DInputHandler::~QTouch3DInputHandler() |
112 | { |
113 | } |
114 | |
115 | /*! |
116 | * Override this to change handling of touch events. |
117 | * Touch event is given in the \a event. |
118 | */ |
119 | void QTouch3DInputHandler::touchEvent(QTouchEvent *event) |
120 | { |
121 | QList<QTouchEvent::TouchPoint> points; |
122 | points = event->touchPoints(); |
123 | |
124 | if (!scene()->isSlicingActive() && points.count() == 2) { |
125 | d_ptr->m_holdTimer->stop(); |
126 | QPointF distance = points.at(i: 0).pos() - points.at(i: 1).pos(); |
127 | QPoint midPoint = ((points.at(i: 0).pos() + points.at(i: 1).pos()) / 2.0).toPoint(); |
128 | d_ptr->handlePinchZoom(distance: distance.manhattanLength(), pos: midPoint); |
129 | } else if (points.count() == 1) { |
130 | QPointF pointerPos = points.at(i: 0).pos(); |
131 | if (event->type() == QEvent::TouchBegin) { |
132 | // Flush input state |
133 | d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone; |
134 | if (scene()->isSlicingActive()) { |
135 | if (isSelectionEnabled()) { |
136 | if (scene()->isPointInPrimarySubView(point: pointerPos.toPoint())) |
137 | setInputView(InputViewOnPrimary); |
138 | else if (scene()->isPointInSecondarySubView(point: pointerPos.toPoint())) |
139 | setInputView(InputViewOnSecondary); |
140 | else |
141 | setInputView(InputViewNone); |
142 | } |
143 | } else { |
144 | // Handle possible tap-and-hold selection |
145 | if (isSelectionEnabled()) { |
146 | d_ptr->m_startHoldPos = pointerPos; |
147 | d_ptr->m_touchHoldPos = d_ptr->m_startHoldPos; |
148 | d_ptr->m_holdTimer->start(); |
149 | setInputView(InputViewOnPrimary); |
150 | } |
151 | // Start rotating |
152 | if (isRotationEnabled()) { |
153 | d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; |
154 | setInputPosition(pointerPos.toPoint()); |
155 | setInputView(InputViewOnPrimary); |
156 | } |
157 | } |
158 | } else if (event->type() == QEvent::TouchEnd) { |
159 | setInputView(InputViewNone); |
160 | d_ptr->m_holdTimer->stop(); |
161 | // Handle possible selection |
162 | if (!scene()->isSlicingActive() |
163 | && QAbstract3DInputHandlerPrivate::InputStatePinching |
164 | != d_ptr->m_inputState) { |
165 | d_ptr->handleSelection(position: pointerPos); |
166 | } |
167 | } else if (event->type() == QEvent::TouchUpdate) { |
168 | if (!scene()->isSlicingActive()) { |
169 | d_ptr->m_touchHoldPos = pointerPos; |
170 | // Handle rotation |
171 | d_ptr->handleRotation(position: pointerPos); |
172 | } |
173 | } |
174 | } else { |
175 | d_ptr->m_holdTimer->stop(); |
176 | } |
177 | } |
178 | |
179 | QTouch3DInputHandlerPrivate::QTouch3DInputHandlerPrivate(QTouch3DInputHandler *q) |
180 | : Q3DInputHandlerPrivate(q), |
181 | q_ptr(q), |
182 | m_holdTimer(0), |
183 | m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone) |
184 | { |
185 | m_holdTimer = new QTimer(); |
186 | m_holdTimer->setSingleShot(true); |
187 | m_holdTimer->setInterval(tapAndHoldTime); |
188 | connect(sender: m_holdTimer, signal: &QTimer::timeout, receiver: this, slot: &QTouch3DInputHandlerPrivate::handleTapAndHold); |
189 | } |
190 | |
191 | QTouch3DInputHandlerPrivate::~QTouch3DInputHandlerPrivate() |
192 | { |
193 | m_holdTimer->stop(); |
194 | delete m_holdTimer; |
195 | } |
196 | |
197 | void QTouch3DInputHandlerPrivate::handlePinchZoom(float distance, const QPoint &pos) |
198 | { |
199 | if (q_ptr->isZoomEnabled()) { |
200 | int newDistance = distance; |
201 | int prevDist = q_ptr->prevDistance(); |
202 | if (prevDist > 0 && qAbs(t: prevDist - newDistance) < maxPinchJitter) |
203 | return; |
204 | m_inputState = QAbstract3DInputHandlerPrivate::InputStatePinching; |
205 | Q3DCamera *camera = q_ptr->scene()->activeCamera(); |
206 | int zoomLevel = int(camera->zoomLevel()); |
207 | const int minZoomLevel = int(camera->minZoomLevel()); |
208 | const int maxZoomLevel = int(camera->maxZoomLevel()); |
209 | float zoomRate = qSqrt(v: qSqrt(v: zoomLevel)); |
210 | if (newDistance > prevDist) |
211 | zoomLevel += zoomRate; |
212 | else |
213 | zoomLevel -= zoomRate; |
214 | zoomLevel = qBound(min: minZoomLevel, val: zoomLevel, max: maxZoomLevel); |
215 | |
216 | if (q_ptr->isZoomAtTargetEnabled()) { |
217 | q_ptr->scene()->setGraphPositionQuery(pos); |
218 | m_zoomAtTargetPending = true; |
219 | // If zoom at target is enabled, we don't want to zoom yet, as that causes |
220 | // jitter. Instead, we zoom next frame, when we apply the camera position. |
221 | m_requestedZoomLevel = zoomLevel; |
222 | m_driftMultiplier = touchZoomDrift; |
223 | } else { |
224 | camera->setZoomLevel(zoomLevel); |
225 | } |
226 | |
227 | q_ptr->setPrevDistance(newDistance); |
228 | } |
229 | } |
230 | |
231 | void QTouch3DInputHandlerPrivate::handleTapAndHold() |
232 | { |
233 | if (q_ptr->isSelectionEnabled()) { |
234 | QPointF distance = m_startHoldPos - m_touchHoldPos; |
235 | if (distance.manhattanLength() < maxTapAndHoldJitter) { |
236 | q_ptr->setInputPosition(m_touchHoldPos.toPoint()); |
237 | q_ptr->scene()->setSelectionQueryPosition(m_touchHoldPos.toPoint()); |
238 | m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; |
239 | } |
240 | } |
241 | } |
242 | |
243 | void QTouch3DInputHandlerPrivate::handleSelection(const QPointF &position) |
244 | { |
245 | if (q_ptr->isSelectionEnabled()) { |
246 | QPointF distance = m_startHoldPos - position; |
247 | if (distance.manhattanLength() < maxSelectionJitter) { |
248 | m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; |
249 | q_ptr->scene()->setSelectionQueryPosition(position.toPoint()); |
250 | } else { |
251 | m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone; |
252 | q_ptr->setInputView(QAbstract3DInputHandler::InputViewNone); |
253 | } |
254 | q_ptr->setPreviousInputPos(position.toPoint()); |
255 | } |
256 | } |
257 | |
258 | void QTouch3DInputHandlerPrivate::handleRotation(const QPointF &position) |
259 | { |
260 | if (q_ptr->isRotationEnabled() |
261 | && QAbstract3DInputHandlerPrivate::InputStateRotating == m_inputState) { |
262 | Q3DScene *scene = q_ptr->scene(); |
263 | Q3DCamera *camera = scene->activeCamera(); |
264 | float xRotation = camera->xRotation(); |
265 | float yRotation = camera->yRotation(); |
266 | QPointF inputPos = q_ptr->inputPosition(); |
267 | float mouseMoveX = float(inputPos.x() - position.x()) |
268 | / (scene->viewport().width() / rotationSpeed); |
269 | float mouseMoveY = float(inputPos.y() - position.y()) |
270 | / (scene->viewport().height() / rotationSpeed); |
271 | xRotation -= mouseMoveX; |
272 | yRotation -= mouseMoveY; |
273 | camera->setXRotation(xRotation); |
274 | camera->setYRotation(yRotation); |
275 | |
276 | q_ptr->setPreviousInputPos(inputPos.toPoint()); |
277 | q_ptr->setInputPosition(position.toPoint()); |
278 | } |
279 | } |
280 | |
281 | QT_END_NAMESPACE_DATAVISUALIZATION |
282 | |