| 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 | |