1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qtouch3dinputhandler_p.h"
5#include "abstract3dcontroller_p.h"
6#include <QtCore/QTimer>
7#include <QtCore/qmath.h>
8
9QT_BEGIN_NAMESPACE
10
11static const float maxTapAndHoldJitter = 20.0f;
12static const int maxPinchJitter = 10;
13#if defined (Q_OS_ANDROID) || defined(Q_OS_IOS)
14static const int maxSelectionJitter = 10;
15#else
16static const int maxSelectionJitter = 5;
17#endif
18static const int tapAndHoldTime = 250;
19static const float rotationSpeed = 200.0f;
20static const float touchZoomDrift = 0.02f;
21
22/*!
23 * \class QTouch3DInputHandler
24 * \inmodule QtGraphs
25 * \brief Basic touch display based input handler.
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 QtGraphs
61 * \ingroup graphs_qml
62 * \instantiates QTouch3DInputHandler
63 * \inherits InputHandler3D
64 * \brief Basic touch display based input handler.
65 *
66 * TouchInputHandler3D is the basic input handler for touch screen devices.
67 *
68 * See QTouch3DInputHandler documentation for more details.
69 */
70
71/*!
72 * Constructs the basic touch display input handler. An optional \a parent parameter can be given
73 * and is then passed to QObject constructor.
74 */
75QTouch3DInputHandler::QTouch3DInputHandler(QObject *parent)
76 : Q3DInputHandler(new QTouch3DInputHandlerPrivate(this), parent)
77{
78}
79
80/*!
81 * Destroys the input handler.
82 */
83QTouch3DInputHandler::~QTouch3DInputHandler()
84{
85}
86
87/*!
88 * Override this to change handling of touch events.
89 * Touch event is given in the \a event.
90 */
91void QTouch3DInputHandler::touchEvent(QTouchEvent *event)
92{
93 Q_D(QTouch3DInputHandler);
94 QList<QTouchEvent::TouchPoint> points;
95 points = event->points();
96
97 if (!scene()->isSlicingActive() && points.size() == 2) {
98 d->m_holdTimer->stop();
99 QPointF distance = points.at(i: 0).position() - points.at(i: 1).position();
100 QPoint midPoint = ((points.at(i: 0).position() + points.at(i: 1).position()) / 2.0).toPoint();
101 d->handlePinchZoom(distance: distance.manhattanLength(), pos: midPoint);
102 } else if (points.size() == 1) {
103 QPointF pointerPos = points.at(i: 0).position();
104 if (event->type() == QEvent::TouchBegin) {
105 // Flush input state
106 d->m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone;
107 if (scene()->isSlicingActive()) {
108 if (isSelectionEnabled()) {
109 if (scene()->isPointInPrimarySubView(point: pointerPos.toPoint()))
110 setInputView(InputViewOnPrimary);
111 else if (scene()->isPointInSecondarySubView(point: pointerPos.toPoint()))
112 setInputView(InputViewOnSecondary);
113 else
114 setInputView(InputViewNone);
115 }
116 } else {
117 // Handle possible tap-and-hold selection
118 if (isSelectionEnabled()) {
119 d->m_startHoldPos = pointerPos;
120 d->m_touchHoldPos = d->m_startHoldPos;
121 d->m_holdTimer->start();
122 setInputView(InputViewOnPrimary);
123 }
124 // Start rotating
125 if (isRotationEnabled()) {
126 d->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating;
127 setInputPosition(position: pointerPos.toPoint());
128 setInputView(InputViewOnPrimary);
129 }
130 }
131 } else if (event->type() == QEvent::TouchEnd) {
132 setInputView(InputViewNone);
133 d->m_holdTimer->stop();
134 // Handle possible selection
135 if (!scene()->isSlicingActive()
136 && QAbstract3DInputHandlerPrivate::InputStatePinching
137 != d->m_inputState) {
138 d->handleSelection(position: pointerPos);
139 }
140 } else if (event->type() == QEvent::TouchUpdate) {
141 if (!scene()->isSlicingActive()) {
142 d->m_touchHoldPos = pointerPos;
143 // Handle rotation
144 d->handleRotation(position: pointerPos);
145 }
146 }
147 } else {
148 d->m_holdTimer->stop();
149 }
150}
151
152QTouch3DInputHandlerPrivate::QTouch3DInputHandlerPrivate(QTouch3DInputHandler *q)
153 : Q3DInputHandlerPrivate(q),
154 m_holdTimer(0)
155{
156 m_holdTimer = new QTimer();
157 m_holdTimer->setSingleShot(true);
158 m_holdTimer->setInterval(tapAndHoldTime);
159 connect(sender: m_holdTimer, signal: &QTimer::timeout, context: this, slot: &QTouch3DInputHandlerPrivate::handleTapAndHold);
160}
161
162QTouch3DInputHandlerPrivate::~QTouch3DInputHandlerPrivate()
163{
164 m_holdTimer->stop();
165 delete m_holdTimer;
166}
167
168void QTouch3DInputHandlerPrivate::handlePinchZoom(float distance, const QPoint &pos)
169{
170 Q_Q(QTouch3DInputHandler);
171 if (q->isZoomEnabled()) {
172 int newDistance = distance;
173 int prevDist = q->prevDistance();
174 if (prevDist > 0 && qAbs(t: prevDist - newDistance) < maxPinchJitter)
175 return;
176 m_inputState = QAbstract3DInputHandlerPrivate::InputStatePinching;
177 Q3DCamera *camera = q->scene()->activeCamera();
178 int zoomLevel = int(camera->zoomLevel());
179 const int minZoomLevel = int(camera->minZoomLevel());
180 const int maxZoomLevel = int(camera->maxZoomLevel());
181 float zoomRate = qSqrt(v: qSqrt(v: zoomLevel));
182 if (newDistance > prevDist)
183 zoomLevel += zoomRate;
184 else
185 zoomLevel -= zoomRate;
186 zoomLevel = qBound(min: minZoomLevel, val: zoomLevel, max: maxZoomLevel);
187
188 if (q->isZoomAtTargetEnabled()) {
189 q->scene()->setGraphPositionQuery(pos);
190 m_controller->setGraphPositionQueryPending(true);
191 m_zoomAtTargetPending = true;
192 // If zoom at target is enabled, we don't want to zoom yet, as that causes
193 // jitter. Instead, we zoom next frame, when we apply the camera position.
194 m_requestedZoomLevel = zoomLevel;
195 m_driftMultiplier = touchZoomDrift;
196 } else {
197 camera->setZoomLevel(zoomLevel);
198 }
199
200 q->setPrevDistance(newDistance);
201 }
202}
203
204void QTouch3DInputHandlerPrivate::handleTapAndHold()
205{
206 Q_Q(QTouch3DInputHandler);
207 if (q->isSelectionEnabled()) {
208 QPointF distance = m_startHoldPos - m_touchHoldPos;
209 if (distance.manhattanLength() < maxTapAndHoldJitter) {
210 m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting;
211 q->setInputPosition(position: m_touchHoldPos.toPoint());
212 q->scene()->setSelectionQueryPosition(m_touchHoldPos.toPoint());
213 }
214 }
215}
216
217void QTouch3DInputHandlerPrivate::handleSelection(const QPointF &position)
218{
219 Q_Q(QTouch3DInputHandler);
220 if (q->isSelectionEnabled()) {
221 QPointF distance = m_startHoldPos - position;
222 if (distance.manhattanLength() < maxSelectionJitter) {
223 m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting;
224 q->scene()->setSelectionQueryPosition(position.toPoint());
225 } else {
226 m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone;
227 q->setInputView(QAbstract3DInputHandler::InputViewNone);
228 }
229 q->setPreviousInputPos(position.toPoint());
230 }
231}
232
233void QTouch3DInputHandlerPrivate::handleRotation(const QPointF &position)
234{
235 Q_Q(QTouch3DInputHandler);
236 if (q->isRotationEnabled()
237 && QAbstract3DInputHandlerPrivate::InputStateRotating == m_inputState) {
238 Q3DScene *scene = q->scene();
239 Q3DCamera *camera = scene->activeCamera();
240 float xRotation = camera->xRotation();
241 float yRotation = camera->yRotation();
242 QPointF inputPos = q->inputPosition();
243 float mouseMoveX = float(inputPos.x() - position.x())
244 / (scene->viewport().width() / rotationSpeed);
245 float mouseMoveY = float(inputPos.y() - position.y())
246 / (scene->viewport().height() / rotationSpeed);
247 xRotation -= mouseMoveX;
248 yRotation -= mouseMoveY;
249 camera->setXRotation(xRotation);
250 camera->setYRotation(yRotation);
251
252 q->setPreviousInputPos(inputPos.toPoint());
253 q->setInputPosition(position: position.toPoint());
254 }
255}
256
257QT_END_NAMESPACE
258

source code of qtgraphs/src/graphs/input/qtouch3dinputhandler.cpp