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