| 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 | * \nativetype 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 | |