diff options
Diffstat (limited to 'src/quick/handlers')
-rw-r--r-- | src/quick/handlers/handlers.pri | 20 | ||||
-rw-r--r-- | src/quick/handlers/qquickdraghandler.cpp | 253 | ||||
-rw-r--r-- | src/quick/handlers/qquickdraghandler_p.h | 137 | ||||
-rw-r--r-- | src/quick/handlers/qquickhandlersmodule.cpp | 104 | ||||
-rw-r--r-- | src/quick/handlers/qquickhandlersmodule_p.h | 68 | ||||
-rw-r--r-- | src/quick/handlers/qquickmultipointhandler.cpp | 314 | ||||
-rw-r--r-- | src/quick/handlers/qquickmultipointhandler_p.h | 118 | ||||
-rw-r--r-- | src/quick/handlers/qquickpinchhandler.cpp | 483 | ||||
-rw-r--r-- | src/quick/handlers/qquickpinchhandler_p.h | 173 | ||||
-rw-r--r-- | src/quick/handlers/qquickpointerdevicehandler.cpp | 196 | ||||
-rw-r--r-- | src/quick/handlers/qquickpointerdevicehandler_p.h | 96 | ||||
-rw-r--r-- | src/quick/handlers/qquickpointerhandler.cpp | 311 | ||||
-rw-r--r-- | src/quick/handlers/qquickpointerhandler_p.h | 125 | ||||
-rw-r--r-- | src/quick/handlers/qquicksinglepointhandler.cpp | 475 | ||||
-rw-r--r-- | src/quick/handlers/qquicksinglepointhandler_p.h | 154 | ||||
-rw-r--r-- | src/quick/handlers/qquicktaphandler.cpp | 363 | ||||
-rw-r--r-- | src/quick/handlers/qquicktaphandler_p.h | 132 |
17 files changed, 3522 insertions, 0 deletions
diff --git a/src/quick/handlers/handlers.pri b/src/quick/handlers/handlers.pri new file mode 100644 index 0000000000..9e32b9278c --- /dev/null +++ b/src/quick/handlers/handlers.pri @@ -0,0 +1,20 @@ +HEADERS += \ + $$PWD/qquickdraghandler_p.h \ + $$PWD/qquickhandlersmodule_p.h \ + $$PWD/qquickmultipointhandler_p.h \ + $$PWD/qquickpinchhandler_p.h \ + $$PWD/qquickpointerdevicehandler_p.h \ + $$PWD/qquickpointerhandler_p.h \ + $$PWD/qquicksinglepointhandler_p.h \ + $$PWD/qquicktaphandler_p.h \ + +SOURCES += \ + $$PWD/qquickdraghandler.cpp \ + $$PWD/qquickhandlersmodule.cpp \ + $$PWD/qquickmultipointhandler.cpp \ + $$PWD/qquickpinchhandler.cpp \ + $$PWD/qquickpointerdevicehandler.cpp \ + $$PWD/qquickpointerhandler.cpp \ + $$PWD/qquicksinglepointhandler.cpp \ + $$PWD/qquicktaphandler.cpp \ + diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp new file mode 100644 index 0000000000..12f1a29bca --- /dev/null +++ b/src/quick/handlers/qquickdraghandler.cpp @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickdraghandler_p.h" +#include <private/qquickwindow_p.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype DragHandler + \instantiates QQuickDragHandler + \inherits SinglePointHandler + \inqmlmodule Qt.labs.handlers + \ingroup qtquick-handlers + \brief Handler for dragging + + DragHandler is a handler that is used to interactively move an Item. + Like other Pointer Handlers, by default it is fully functional, and + manipulates its \l target. + + \snippet pointerHandlers/dragHandler.qml 0 + + It has properties to restrict the range of dragging. + + If it is declared within one Item but is assigned a different \l target, + then it handles events within the bounds of the \l parent Item but + manipulates the \c target Item instead: + + \snippet pointerHandlers/dragHandlerDifferentTarget.qml 0 + + A third way to use it is to set \l target to \c null and react to property + changes in some other way: + + \snippet pointerHandlers/dragHandlerNullTarget.qml 0 + + At this time, drag-and-drop is not yet supported. + + \sa Drag, MouseArea +*/ + +QQuickDragHandler::QQuickDragHandler(QObject *parent) + : QQuickSinglePointHandler(parent) +{ +} + +QQuickDragHandler::~QQuickDragHandler() +{ +} + +bool QQuickDragHandler::wantsEventPoint(QQuickEventPoint *point) +{ + // If we've already been interested in a point, stay interested, even if it has strayed outside bounds. + return ((point->state() != QQuickEventPoint::Pressed && this->point().id() == point->pointId()) + || QQuickSinglePointHandler::wantsEventPoint(point)); +} + +void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) +{ + if (grabber == this && stateChange == QQuickEventPoint::GrabExclusive) + // In case the grab got handled over from another grabber, we might not get the Press + initializeTargetStartPos(point); + enforceConstraints(); + QQuickSinglePointHandler::onGrabChanged(grabber, stateChange, point); +} + +void QQuickDragHandler::onActiveChanged() +{ + if (!active()) + m_targetStartPos = QPointF(); +} + +void QQuickDragHandler::handleEventPoint(QQuickEventPoint *point) +{ + point->setAccepted(); + switch (point->state()) { + case QQuickEventPoint::Pressed: + initializeTargetStartPos(point); + setPassiveGrab(point); + break; + case QQuickEventPoint::Updated: { + QPointF delta = point->scenePosition() - point->scenePressPosition(); + if (!m_xAxis.enabled()) + delta.setX(0); + if (!m_yAxis.enabled()) + delta.setY(0); + if (active()) { + setTranslation(QVector2D(delta)); + if (target() && target()->parentItem()) { + QPointF pos = target()->parentItem()->mapFromScene(m_targetStartPos + delta); + enforceAxisConstraints(&pos); + moveTarget(pos, point); + } + } else if (!point->exclusiveGrabber() && + ((m_xAxis.enabled() && QQuickWindowPrivate::dragOverThreshold(delta.x(), Qt::XAxis, point)) || + (m_yAxis.enabled() && QQuickWindowPrivate::dragOverThreshold(delta.y(), Qt::YAxis, point)))) { + setExclusiveGrab(point); + if (auto parent = parentItem()) { + if (point->pointerEvent()->asPointerTouchEvent()) + parent->setKeepTouchGrab(true); + // tablet and mouse are treated the same by Item's legacy event handling, and + // touch becomes synth-mouse for Flickable, so we need to prevent stealing + // mouse grab too, whenever dragging occurs in an enabled direction + parent->setKeepMouseGrab(true); + } + } + } break; + default: + break; + } +} + +void QQuickDragHandler::enforceConstraints() +{ + if (!target() || !target()->parentItem()) + return; + QPointF pos = target()->position(); + QPointF copy(pos); + enforceAxisConstraints(&pos); + if (pos != copy) + target()->setPosition(pos); +} + +void QQuickDragHandler::enforceAxisConstraints(QPointF *localPos) +{ + if (m_xAxis.enabled()) + localPos->setX(qBound(m_xAxis.minimum(), localPos->x(), m_xAxis.maximum())); + if (m_yAxis.enabled()) + localPos->setY(qBound(m_yAxis.minimum(), localPos->y(), m_yAxis.maximum())); +} + +void QQuickDragHandler::initializeTargetStartPos(QQuickEventPoint *point) +{ + if (target() && target()->parentItem() && m_targetStartPos.isNull()) { // prefer the m_targetStartPos we got when it got Pressed. + m_targetStartPos = target()->parentItem()->mapToScene(target()->position()); + if (!target()->contains(point->position())) { + // If pressed outside of target item, move the target item so that the touchpoint is in its center, + // while still respecting the axis constraints. + const QPointF center = target()->parentItem()->mapFromScene(point->scenePosition()); + const QPointF pointCenteredInItemPos = target()->parentItem()->mapToScene(center - QPointF(target()->width(), target()->height())/2); + if (m_xAxis.enabled()) + m_targetStartPos.setX(pointCenteredInItemPos.x()); + if (m_yAxis.enabled()) + m_targetStartPos.setY(pointCenteredInItemPos.y()); + } + } +} + +void QQuickDragHandler::setTranslation(const QVector2D &trans) +{ + if (trans == m_translation) // fuzzy compare? + return; + m_translation = trans; + emit translationChanged(); +} + +/*! + \qmlpropertygroup QtQuick::DragHandler::xAxis + \qmlpropertygroup QtQuick::DragHandler::yAxis + \qmlproperty real QtQuick::DragHandler::DragAxis::minimum + \qmlproperty real QtQuick::DragHandler::DragAxis::maximum + \qmlproperty real QtQuick::DragHandler::DragAxis::enabled + + \c xAxis and yAxis control the constraints for horizontal and vertical + dragging, respectively. + + \value minimum + The minimum acceptable value of \l {Item::x}{x} or \l {Item::y}{y} + to be applied to the \l target + \value maximum + The maximum acceptable value of \l {Item::x}{x} or \l {Item::y}{y} + to be applied to the \l target + \value enabled + Whether dragging in this direction is allowed at all +*/ +QQuickDragAxis::QQuickDragAxis() + : m_minimum(-DBL_MAX) + , m_maximum(DBL_MAX) + , m_enabled(true) +{ +} + +void QQuickDragAxis::setMinimum(qreal minimum) +{ + if (m_minimum == minimum) + return; + + m_minimum = minimum; + emit minimumChanged(); +} + +void QQuickDragAxis::setMaximum(qreal maximum) +{ + if (m_maximum == maximum) + return; + + m_maximum = maximum; + emit maximumChanged(); +} + +void QQuickDragAxis::setEnabled(bool enabled) +{ + if (m_enabled == enabled) + return; + + m_enabled = enabled; + emit enabledChanged(); +} + +/*! + \readonly + \qmlproperty QVector2D QtQuick::DragHandler::translation + + The translation since the gesture began. +*/ + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickdraghandler_p.h b/src/quick/handlers/qquickdraghandler_p.h new file mode 100644 index 0000000000..d10084c654 --- /dev/null +++ b/src/quick/handlers/qquickdraghandler_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDRAGHANDLER_H +#define QQUICKDRAGHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquicksinglepointhandler_p.h" + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickDragAxis : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal minimum READ minimum WRITE setMinimum NOTIFY minimumChanged) + Q_PROPERTY(qreal maximum READ maximum WRITE setMaximum NOTIFY maximumChanged) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + +public: + QQuickDragAxis(); + + qreal minimum() const { return m_minimum; } + void setMinimum(qreal minimum); + + qreal maximum() const { return m_maximum; } + void setMaximum(qreal maximum); + + bool enabled() const { return m_enabled; } + void setEnabled(bool enabled); + +signals: + void minimumChanged(); + void maximumChanged(); + void enabledChanged(); + +private: + qreal m_minimum; + qreal m_maximum; + bool m_enabled; +}; + +class Q_AUTOTEST_EXPORT QQuickDragHandler : public QQuickSinglePointHandler +{ + Q_OBJECT + Q_PROPERTY(QQuickDragAxis * xAxis READ xAxis CONSTANT) + Q_PROPERTY(QQuickDragAxis * yAxis READ yAxis CONSTANT) + Q_PROPERTY(QVector2D translation READ translation NOTIFY translationChanged) + +public: + explicit QQuickDragHandler(QObject *parent = 0); + ~QQuickDragHandler(); + + void handleEventPoint(QQuickEventPoint *point) override; + + QQuickDragAxis *xAxis() { return &m_xAxis; } + QQuickDragAxis *yAxis() { return &m_yAxis; } + + QVector2D translation() const { return m_translation; } + void setTranslation(const QVector2D &trans); + + Q_INVOKABLE void enforceConstraints(); + +Q_SIGNALS: +// void gestureStarted(QQuickGestureEvent *gesture); + void translationChanged(); + +protected: + bool wantsEventPoint(QQuickEventPoint *point) override; + void onActiveChanged() override; + void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) override; + +private: + void ungrab(); + void enforceAxisConstraints(QPointF *localPos); + void initializeTargetStartPos(QQuickEventPoint *point); + +private: + QPointF m_targetStartPos; + QVector2D m_translation; + QQuickDragAxis m_xAxis; + QQuickDragAxis m_yAxis; + + friend class QQuickDragAxis; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickDragHandler) +QML_DECLARE_TYPE(QQuickDragAxis) + +#endif // QQUICKDRAGHANDLER_H diff --git a/src/quick/handlers/qquickhandlersmodule.cpp b/src/quick/handlers/qquickhandlersmodule.cpp new file mode 100644 index 0000000000..8472c6a062 --- /dev/null +++ b/src/quick/handlers/qquickhandlersmodule.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickhandlersmodule_p.h" +#include "qquickpointerhandler_p.h" +#include "qquickdraghandler_p.h" +#include "qquickpinchhandler_p.h" +#include "qquicktaphandler_p.h" + +static void initResources() +{ +#ifdef QT_STATIC + Q_INIT_RESOURCE(qmake_Qt_labs_handlers); +#endif +} + +QT_BEGIN_NAMESPACE + +static QQmlPrivate::AutoParentResult handler_autoParent(QObject *obj, QObject *parent) +{ + if (qmlobject_cast<QQuickItem *>(parent)) { + QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(obj); + if (handler) { + handler->setParent(parent); + return QQmlPrivate::Parented; + } + } + return QQmlPrivate::IncompatibleObject; +} + +static void qt_quickhandlers_defineModule(const char *uri, int major, int minor) +{ + QQmlPrivate::RegisterAutoParent autoparent = { 0, &handler_autoParent }; + QQmlPrivate::qmlregister(QQmlPrivate::AutoParentRegistration, &autoparent); + qmlRegisterUncreatableType<QQuickPointerEvent>(uri, major, minor, "PointerEvent", + QQuickPointerHandler::tr("PointerEvent is only available as a parameter of several signals in PointerHandler")); + qmlRegisterUncreatableType<QQuickEventPoint>(uri, major, minor, "EventPoint", + QQuickPointerHandler::tr("EventPoint is only available as a member of PointerEvent")); + qmlRegisterUncreatableType<QQuickEventTouchPoint>(uri, major, minor, "EventTouchPoint", + QQuickPointerHandler::tr("EventTouchPoint is only available as a member of PointerEvent")); + qmlRegisterUncreatableType<QQuickPointerDevice>(uri, major, minor, "PointerDevice", + QQuickPointerHandler::tr("PointerDevice is only available as a property of PointerEvent")); + qRegisterMetaType<QPointingDeviceUniqueId>("QPointingDeviceUniqueId"); + qmlRegisterUncreatableType<QPointingDeviceUniqueId>(uri, major, minor, "PointingDeviceUniqueId", + QQuickPointerHandler::tr("PointingDeviceUniqueId is only available as a property of PointerEvent")); + + qmlRegisterUncreatableType<QQuickPointerHandler>(uri,major,minor,"PointerHandler", + QQuickPointerHandler::tr("PointerHandler is an abstract base class")); + qmlRegisterType<QQuickDragHandler>(uri,major,minor,"DragHandler"); + qmlRegisterUncreatableType<QQuickDragAxis>(uri, major, minor, "DragAxis", + QQuickDragHandler::tr("DragAxis is only available as a grouped property of DragHandler")); + qmlRegisterType<QQuickPinchHandler>(uri,major,minor,"PinchHandler"); + qmlRegisterType<QQuickTapHandler>(uri,major,minor,"TapHandler"); + qRegisterMetaType<QQuickHandlerPoint>(); +} + +void QQuickHandlersModule::defineModule() +{ + initResources(); + + const char uri[] = "Qt.labs.handlers"; + int majorVersion = 1; + int minorVersion = 0; + + qt_quickhandlers_defineModule(uri, majorVersion, minorVersion); +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickhandlersmodule_p.h b/src/quick/handlers/qquickhandlersmodule_p.h new file mode 100644 index 0000000000..7eb8d39b98 --- /dev/null +++ b/src/quick/handlers/qquickhandlersmodule_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKHANDLERSMODULE_P_H +#define QQUICKHANDLERSMODULE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qqml.h> +#include <private/qtquickglobal_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICK_PRIVATE_EXPORT QQuickHandlersModule +{ +public: + static void defineModule(); +}; + +QT_END_NAMESPACE + +#endif // QQUICKHANDLERSMODULE_P_H + diff --git a/src/quick/handlers/qquickmultipointhandler.cpp b/src/quick/handlers/qquickmultipointhandler.cpp new file mode 100644 index 0000000000..ff4914394c --- /dev/null +++ b/src/quick/handlers/qquickmultipointhandler.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmultipointhandler_p.h" +#include <private/qquickitem_p.h> +#include <QLineF> +#include <QMouseEvent> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MultiPointHandler + \since 5.10 + \preliminary + \instantiates QQuickMultiPointHandler + \inherits PointerDeviceHandler + \inqmlmodule Qt.labs.handlers + \ingroup qtquick-handlers + \brief Abstract handler for multi-point Pointer Events. + + An intermediate class (not registered as a QML type) + for any type of handler which requires and acts upon a specific number + of multiple touchpoints. +*/ +QQuickMultiPointHandler::QQuickMultiPointHandler(QObject *parent, int minimumPointCount) + : QQuickPointerDeviceHandler(parent) + , m_minimumPointCount(minimumPointCount) + , m_maximumPointCount(-1) + , m_pointDistanceThreshold(0) +{ +} + +QQuickMultiPointHandler::~QQuickMultiPointHandler() +{ +} + +bool QQuickMultiPointHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) + return false; + + if (event->asPointerNativeGestureEvent()) + return true; + + if (sameAsCurrentPoints(event)) + return true; + + const QVector<QQuickEventPoint *> candidatePoints = eligiblePoints(event); + const bool ret = (candidatePoints.size() >= minimumPointCount() && candidatePoints.size() <= maximumPointCount()); + if (ret) + m_currentPoints = candidatePoints; + return ret; +} + +QVector<QQuickEventPoint *> QQuickMultiPointHandler::eligiblePoints(QQuickPointerEvent *event) +{ + QVector<QQuickEventPoint *> ret; + int c = event->pointCount(); + QRectF parentBounds = parentItem()->mapRectToScene(parentItem()->boundingRect()) + .marginsAdded(QMarginsF(m_pointDistanceThreshold, m_pointDistanceThreshold, m_pointDistanceThreshold, m_pointDistanceThreshold)); + // If one or more points are newly pressed or released, all non-released points are candidates for this handler. + // In other cases however, do not steal the grab: that is, if a point has a grabber, + // it's not a candidate for this handler. + bool stealingAllowed = event->isPressEvent() || event->isReleaseEvent(); + for (int i = 0; i < c; ++i) { + QQuickEventPoint *p = event->point(i); + if (!stealingAllowed) { + QObject *exclusiveGrabber = p->exclusiveGrabber(); + if (exclusiveGrabber && exclusiveGrabber != this) + continue; + } + if (p->state() != QQuickEventPoint::Released && parentBounds.contains(p->scenePosition())) + ret << p; + } + return ret; +} + +/*! + \qmlproperty int MultiPointHandler::minimumPointCount + + The minimum number of touchpoints required to activate this handler. + + If a smaller number of touchpoints are in contact with the + \l {PointerHandler::parent}{parent}, they will be ignored. + + Any ignored points are eligible to activate other Pointer Handlers that + have different constraints, on the same Item or on other Items. + + The default value is 2. +*/ +void QQuickMultiPointHandler::setMinimumPointCount(int c) +{ + if (m_minimumPointCount == c) + return; + + m_minimumPointCount = c; + emit minimumPointCountChanged(); + if (m_maximumPointCount < 0) + emit maximumPointCountChanged(); +} + +/*! + \qmlproperty int MultiPointHandler::maximumPointCount + + The maximum number of touchpoints this handler can utilize. + + If a larger number of touchpoints are in contact with the + \l {PointerHandler::parent}{parent}, the required number of points will be + chosen in the order that they are pressed, and the remaining points will + be ignored. + + Any ignored points are eligible to activate other Pointer Handlers that + have different constraints, on the same Item or on other Items. + + The default value is the same as \l minimumPointCount. +*/ +void QQuickMultiPointHandler::setMaximumPointCount(int maximumPointCount) +{ + if (m_maximumPointCount == maximumPointCount) + return; + + m_maximumPointCount = maximumPointCount; + emit maximumPointCountChanged(); +} + +/*! + \qmlproperty real MultiPointHandler::pointDistanceThreshold + + The margin beyond the bounds of the \l {PointerHandler::parent}{parent} + item within which a touch point can activate this handler. For example, on + a PinchHandler where the \l {PointerHandler::target}{target} is also the + \c parent, it's useful to set this to a distance at least half the width + of a typical user's finger, so that if the \c parent has been scaled down + to a very small size, the pinch gesture is still possible. + + The default value is 0. + + \image pointDistanceThreshold.png +*/ +void QQuickMultiPointHandler::setPointDistanceThreshold(qreal pointDistanceThreshold) +{ + if (m_pointDistanceThreshold == pointDistanceThreshold) + return; + + m_pointDistanceThreshold = pointDistanceThreshold; + emit pointDistanceThresholdChanged(); +} + +bool QQuickMultiPointHandler::sameAsCurrentPoints(QQuickPointerEvent *event) +{ + bool ret = true; + int c = event->pointCount(); + if (c != m_currentPoints.size()) + return false; + // TODO optimize: either ensure the points are sorted, + // or use std::equal with a predicate + for (int i = 0; ret && i < c; ++i) { + if (event->point(i)->state() == QQuickEventPoint::Released) + return false; + bool found = false; + int pointId = event->point(i)->pointId(); + for (QQuickEventPoint *o : qAsConst(m_currentPoints)) + if (o && pointId == o->pointId()) + found = true; + if (!found) + ret = false; + } + return ret; +} + +// TODO make templates for these functions somehow? +QPointF QQuickMultiPointHandler::touchPointCentroid() +{ + QPointF ret; + if (Q_UNLIKELY(m_currentPoints.size() == 0)) + return ret; + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) + ret += point->scenePosition(); + return ret / m_currentPoints.size(); +} + +QVector2D QQuickMultiPointHandler::touchPointCentroidVelocity() +{ + QVector2D ret; + if (Q_UNLIKELY(m_currentPoints.size() == 0)) + return ret; + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) + ret += point->velocity(); + return ret / m_currentPoints.size(); +} + +qreal QQuickMultiPointHandler::averageTouchPointDistance(const QPointF &ref) +{ + qreal ret = 0; + if (Q_UNLIKELY(m_currentPoints.size() == 0)) + return ret; + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) + ret += QVector2D(point->scenePosition() - ref).length(); + return ret / m_currentPoints.size(); +} + +qreal QQuickMultiPointHandler::averageStartingDistance(const QPointF &ref) +{ + // TODO cache it in setActive()? + qreal ret = 0; + if (Q_UNLIKELY(m_currentPoints.size() == 0)) + return ret; + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) + ret += QVector2D(point->sceneGrabPosition() - ref).length(); + return ret / m_currentPoints.size(); +} + +QVector<QQuickMultiPointHandler::PointData> QQuickMultiPointHandler::angles(const QPointF &ref) const +{ + QVector<PointData> angles; + angles.reserve(m_currentPoints.count()); + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) { + qreal angle = QLineF(ref, point->scenePosition()).angle(); + angles.append(PointData(point->pointId(), -angle)); // convert to clockwise, to be consistent with QQuickItem::rotation + } + return angles; +} + +qreal QQuickMultiPointHandler::averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles) +{ + qreal avgAngleDelta = 0; + int numSamples = 0; + + auto oldBegin = old.constBegin(); + + for (PointData newData : newAngles) { + quint64 id = newData.id; + auto it = std::find_if(oldBegin, old.constEnd(), [id] (PointData pd) { return pd.id == id; }); + qreal angleD = 0; + if (it != old.constEnd()) { + PointData oldData = *it; + // We might rotate from 359 degrees to 1 degree. However, this + // should be interpreted as a rotation of +2 degrees instead of + // -358 degrees. Therefore, we call remainder() to translate the angle + // to be in the range [-180, 180] (-350 to +10 etc) + angleD = remainder(newData.angle - oldData.angle, qreal(360)); + // optimization: narrow down the O(n^2) search to optimally O(n) + // if both vectors have the same points and they are in the same order + if (it == oldBegin) + ++oldBegin; + numSamples++; + } + avgAngleDelta += angleD; + } + if (numSamples > 1) + avgAngleDelta /= numSamples; + + return avgAngleDelta; +} + +void QQuickMultiPointHandler::acceptPoints(const QVector<QQuickEventPoint *> &points) +{ + for (QQuickEventPoint* point : points) + point->setAccepted(); +} + +bool QQuickMultiPointHandler::grabPoints(QVector<QQuickEventPoint *> points) +{ + bool canGrab = true; + for (QQuickEventPoint* point : points) { + auto grabber = point->grabberItem(); + if (grabber && (grabber->keepMouseGrab() || grabber->keepTouchGrab())) + canGrab = false; + } + if (canGrab) { + for (QQuickEventPoint* point : points) + setExclusiveGrab(point); + } + return canGrab; +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickmultipointhandler_p.h b/src/quick/handlers/qquickmultipointhandler_p.h new file mode 100644 index 0000000000..05c3876246 --- /dev/null +++ b/src/quick/handlers/qquickmultipointhandler_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOINTERMULTIHANDLER_H +#define QQUICKPOINTERMULTIHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickitem.h" +#include "qevent.h" +#include "qquickpointerdevicehandler_p.h" + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickMultiPointHandler : public QQuickPointerDeviceHandler +{ + Q_OBJECT + Q_PROPERTY(int minimumPointCount READ minimumPointCount WRITE setMinimumPointCount NOTIFY minimumPointCountChanged) + Q_PROPERTY(int maximumPointCount READ maximumPointCount WRITE setMaximumPointCount NOTIFY maximumPointCountChanged) + Q_PROPERTY(qreal pointDistanceThreshold READ pointDistanceThreshold WRITE setPointDistanceThreshold NOTIFY pointDistanceThresholdChanged) + +public: + explicit QQuickMultiPointHandler(QObject *parent = 0, int minimumPointCount = 2); + ~QQuickMultiPointHandler(); + + int minimumPointCount() const { return m_minimumPointCount; } + void setMinimumPointCount(int c); + + int maximumPointCount() const { return m_maximumPointCount >= 0 ? m_maximumPointCount : m_minimumPointCount; } + void setMaximumPointCount(int maximumPointCount); + + qreal pointDistanceThreshold() const { return m_pointDistanceThreshold; } + void setPointDistanceThreshold(qreal pointDistanceThreshold); + +signals: + void minimumPointCountChanged(); + void maximumPointCountChanged(); + void pointDistanceThresholdChanged(); + +protected: + struct PointData { + PointData() : id(0), angle(0) {} + PointData(quint64 id, qreal angle) : id(id), angle(angle) {} + quint64 id; + qreal angle; + }; + + bool wantsPointerEvent(QQuickPointerEvent *event) override; + bool sameAsCurrentPoints(QQuickPointerEvent *event); + QVector<QQuickEventPoint *> eligiblePoints(QQuickPointerEvent *event); + QPointF touchPointCentroid(); + QVector2D touchPointCentroidVelocity(); + qreal averageTouchPointDistance(const QPointF &ref); + qreal averageStartingDistance(const QPointF &ref); + qreal averageTouchPointAngle(const QPointF &ref); + qreal averageStartingAngle(const QPointF &ref); + QVector<PointData> angles(const QPointF &ref) const; + static qreal averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles); + void acceptPoints(const QVector<QQuickEventPoint *> &points); + bool grabPoints(QVector<QQuickEventPoint *> points); + +protected: + QVector<QQuickEventPoint *> m_currentPoints; + int m_minimumPointCount; + int m_maximumPointCount; + qreal m_pointDistanceThreshold; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickMultiPointHandler) + +#endif // QQUICKPOINTERMULTIHANDLER_H diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp new file mode 100644 index 0000000000..84c4e912d1 --- /dev/null +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -0,0 +1,483 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpinchhandler_p.h" +#include <QtQuick/qquickwindow.h> +#include <private/qsgadaptationlayer_p.h> +#include <private/qquickitem_p.h> +#include <private/qguiapplication_p.h> +#include <private/qquickwindow_p.h> +#include <QEvent> +#include <QMouseEvent> +#include <QDebug> +#include <qpa/qplatformnativeinterface.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch") + +/*! + \qmltype PinchHandler + \instantiates QQuickPinchHandler + \inherits MultiPointHandler + \inqmlmodule Qt.labs.handlers + \ingroup qtquick-handlers + \brief Handler for pinch gestures + + PinchHandler is a handler that interprets a multi-finger gesture to + interactively rotate, zoom, and drag an Item. Like other Pointer Handlers, + by default it is fully functional, and manipulates its \l target, + which is the Item within which it is declared. + + \snippet pointerHandlers/pinchHandler.qml 0 + + It has properties to restrict the range of dragging, rotation, and zoom. + + If it is declared within one Item but is assigned a different \l target, it + handles events within the bounds of the outer Item but manipulates the + \c target Item instead: + + \snippet pointerHandlers/pinchHandlerDifferentTarget.qml 0 + + A third way to use it is to set \l target to \c null and react to property + changes in some other way: + + \snippet pointerHandlers/pinchHandlerNullTarget.qml 0 + + \image touchpoints-pinchhandler.png + + \sa PinchArea +*/ + +QQuickPinchHandler::QQuickPinchHandler(QObject *parent) + : QQuickMultiPointHandler(parent, 2) + , m_activeScale(1) + , m_activeRotation(0) + , m_activeTranslation(0,0) + , m_minimumScale(-qInf()) + , m_maximumScale(qInf()) + , m_minimumRotation(-qInf()) + , m_maximumRotation(qInf()) + , m_minimumX(-qInf()) + , m_maximumX(qInf()) + , m_minimumY(-qInf()) + , m_maximumY(qInf()) + , m_pinchOrigin(PinchCenter) + , m_startScale(1) + , m_startRotation(0) +{ +} + +QQuickPinchHandler::~QQuickPinchHandler() +{ +} + +/*! + \qmlproperty real QtQuick::PinchHandler::minimumScale + + The minimum acceptable \l {Item::scale}{scale} to be applied + to the \l target. +*/ +void QQuickPinchHandler::setMinimumScale(qreal minimumScale) +{ + if (m_minimumScale == minimumScale) + return; + + m_minimumScale = minimumScale; + emit minimumScaleChanged(); +} + +/*! + \qmlproperty real QtQuick::PinchHandler::maximumScale + + The maximum acceptable \l {Item::scale}{scale} to be applied + to the \l target. +*/ +void QQuickPinchHandler::setMaximumScale(qreal maximumScale) +{ + if (m_maximumScale == maximumScale) + return; + + m_maximumScale = maximumScale; + emit maximumScaleChanged(); +} + +/*! + \qmlproperty real QtQuick::PinchHandler::minimumRotation + + The minimum acceptable \l {Item::rotation}{rotation} to be applied + to the \l target. +*/ +void QQuickPinchHandler::setMinimumRotation(qreal minimumRotation) +{ + if (m_minimumRotation == minimumRotation) + return; + + m_minimumRotation = minimumRotation; + emit minimumRotationChanged(); +} + +/*! + \qmlproperty real QtQuick::PinchHandler::maximumRotation + + The maximum acceptable \l {Item::rotation}{rotation} to be applied + to the \l target. +*/ +void QQuickPinchHandler::setMaximumRotation(qreal maximumRotation) +{ + if (m_maximumRotation == maximumRotation) + return; + + m_maximumRotation = maximumRotation; + emit maximumRotationChanged(); +} + +/*! + \qmlproperty real QtQuick::PinchHandler::pinchOrigin + + The point to be held in place, around which the \l target is scaled and + rotated. + + \value FirstPoint + the first touch point, wherever the first finger is pressed + \value PinchCenter + the centroid between all the touch points at the time when the + PinchHandler becomes \l active + \value TargetCenter + the center of the \l target +*/ +void QQuickPinchHandler::setPinchOrigin(QQuickPinchHandler::PinchOrigin pinchOrigin) +{ + if (m_pinchOrigin == pinchOrigin) + return; + + m_pinchOrigin = pinchOrigin; + emit pinchOriginChanged(); +} + +/*! + \qmlproperty real QtQuick::PinchHandler::minimumX + + The minimum acceptable x coordinate of the centroid +*/ +void QQuickPinchHandler::setMinimumX(qreal minX) +{ + if (m_minimumX == minX) + return; + m_minimumX = minX; + emit minimumXChanged(); +} + +/*! + \qmlproperty real QtQuick::PinchHandler::maximumX + + The maximum acceptable x coordinate of the centroid +*/ +void QQuickPinchHandler::setMaximumX(qreal maxX) +{ + if (m_maximumX == maxX) + return; + m_maximumX = maxX; + emit maximumXChanged(); +} + +/*! + \qmlproperty real QtQuick::PinchHandler::minimumY + + The minimum acceptable y coordinate of the centroid +*/ +void QQuickPinchHandler::setMinimumY(qreal minY) +{ + if (m_minimumY == minY) + return; + m_minimumY = minY; + emit minimumYChanged(); +} + +/*! + \qmlproperty real QtQuick::PinchHandler::maximumY + + The maximum acceptable y coordinate of the centroid +*/ +void QQuickPinchHandler::setMaximumY(qreal maxY) +{ + if (m_maximumY == maxY) + return; + m_maximumY = maxY; + emit maximumYChanged(); +} + +bool QQuickPinchHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickMultiPointHandler::wantsPointerEvent(event)) + return false; + + if (minimumPointCount() == 2) { + if (const auto gesture = event->asPointerNativeGestureEvent()) { + switch (gesture->type()) { + case Qt::BeginNativeGesture: + case Qt::EndNativeGesture: + case Qt::ZoomNativeGesture: + case Qt::RotateNativeGesture: + return parentContains(event->point(0)); + default: + return false; + } + } + } + + return true; +} + +/*! + \qmlproperty int QtQuick::PinchHandler::minimumTouchPoints + + The pinch begins when this number of fingers are pressed. + Until then, PinchHandler tracks the positions of any pressed fingers, + but if it's an insufficient number, it does not scale or rotate + its \l target, and the \l active property will remain false. +*/ + +/*! + \qmlproperty bool QtQuick::PinchHandler::active + + This property is true when all the constraints (epecially \l minimumTouchPoints) + are satisfied and the \l target, if any, is being manipulated. +*/ + +void QQuickPinchHandler::onActiveChanged() +{ + if (active()) { + m_startMatrix = QMatrix4x4(); + m_startCentroid = touchPointCentroid(); + m_startAngles = angles(m_startCentroid); + m_startDistance = averageTouchPointDistance(m_startCentroid); + m_activeRotation = 0; + m_activeTranslation = QVector2D(); + if (const QQuickItem *t = target()) { + m_startScale = t->scale(); // TODO incompatible with independent x/y scaling + m_startRotation = t->rotation(); + QVector3D xformOrigin(t->transformOriginPoint()); + m_startMatrix.translate(t->x(), t->y()); + m_startMatrix.translate(xformOrigin); + m_startMatrix.scale(m_startScale); + m_startMatrix.rotate(m_startRotation, 0, 0, -1); + m_startMatrix.translate(-xformOrigin); + } else { + m_startScale = 1; + m_startRotation = 0; + } + qCInfo(lcPinchHandler) << "activated with starting scale" << m_startScale << "rotation" << m_startRotation; + } else { + qCInfo(lcPinchHandler) << "deactivated with scale" << m_activeScale << "rotation" << m_activeRotation; + } +} + +void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + if (Q_UNLIKELY(lcPinchHandler().isDebugEnabled())) { + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) + qCDebug(lcPinchHandler) << point->state() << point->sceneGrabPosition() << "->" << point->scenePosition(); + } + + qreal dist = 0; + if (const auto gesture = event->asPointerNativeGestureEvent()) { + switch (gesture->type()) { + case Qt::EndNativeGesture: + m_activeScale = 1; + m_activeRotation = 0; + m_activeTranslation = QVector2D(); + m_centroid = QPointF(); + m_centroidVelocity = QVector2D(); + setActive(false); + emit updated(); + return; + case Qt::ZoomNativeGesture: + m_activeScale *= 1 + gesture->value(); + break; + case Qt::RotateNativeGesture: + m_activeRotation += gesture->value(); + break; + default: + // Nothing of interest (which is unexpected, because wantsPointerEvent() should have returned false) + return; + } + if (!active()) { + m_centroid = gesture->point(0)->scenePosition(); + setActive(true); + m_startCentroid = m_centroid; + // Native gestures for 2-finger pinch do not allow dragging, so + // the centroid won't move during the gesture, and translation stays at zero + m_centroidVelocity = QVector2D(); + m_activeTranslation = QVector2D(); + } + } else { + bool containsReleasedPoints = event->isReleaseEvent(); + if (!active() && !containsReleasedPoints) { + // Verify that at least one of the points has moved beyond threshold needed to activate the handler + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) { + if (QQuickWindowPrivate::dragOverThreshold(point)) { + if (grabPoints(m_currentPoints)) + setActive(true); + break; + } + } + if (!active()) + return; + } + // TODO check m_pinchOrigin: right now it acts like it's set to PinchCenter + m_centroid = touchPointCentroid(); + m_centroidVelocity = touchPointCentroidVelocity(); + // avoid mapping the minima and maxima, as they might have unmappable values + // such as -inf/+inf. Because of this we perform the bounding to min/max in local coords. + // 1. scale + dist = averageTouchPointDistance(m_centroid); + m_activeScale = dist / m_startDistance; + m_activeScale = qBound(m_minimumScale/m_startScale, m_activeScale, m_maximumScale/m_startScale); + + // 2. rotate + QVector<PointData> newAngles = angles(m_centroid); + const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles); + m_activeRotation += angleDelta; + m_startAngles = std::move(newAngles); + + if (!containsReleasedPoints) + acceptPoints(m_currentPoints); + } + + QPointF centroidParentPos; + QRectF bounds(m_minimumX, m_minimumY, m_maximumX - m_minimumX, m_maximumY - m_minimumY); + if (target() && target()->parentItem()) { + centroidParentPos = target()->parentItem()->mapFromScene(m_centroid); + centroidParentPos = QPointF(qBound(bounds.left(), centroidParentPos.x(), bounds.right()), + qBound(bounds.top(), centroidParentPos.y(), bounds.bottom())); + } + const qreal totalRotation = m_startRotation + m_activeRotation; + const qreal rotation = qBound(m_minimumRotation, totalRotation, m_maximumRotation); + m_activeRotation += (rotation - totalRotation); //adjust for the potential bounding above + const qreal scale = m_startScale * m_activeScale; + + if (target() && target()->parentItem()) { + // 3. Drag/translate + const QPointF centroidStartParentPos = target()->parentItem()->mapFromScene(m_startCentroid); + m_activeTranslation = QVector2D(centroidParentPos - centroidStartParentPos); + + // apply rotation + scaling around the centroid - then apply translation. + QMatrix4x4 mat; + + const QVector3D centroidParentVector(centroidParentPos); + mat.translate(centroidParentVector); + mat.rotate(m_activeRotation, 0, 0, 1); + mat.scale(m_activeScale); + mat.translate(-centroidParentVector); + mat.translate(QVector3D(m_activeTranslation)); + + mat = mat * m_startMatrix; + + QPointF xformOriginPoint = target()->transformOriginPoint(); + QPointF pos = mat * xformOriginPoint; + pos -= xformOriginPoint; + + target()->setPosition(pos); + target()->setRotation(rotation); + target()->setScale(scale); + + // TODO some translation inadvertently happens; try to hold the chosen pinch origin in place + } else { + m_activeTranslation = QVector2D(m_centroid - m_startCentroid); + } + + qCDebug(lcPinchHandler) << "centroid" << m_startCentroid << "->" << m_centroid + << ", distance" << m_startDistance << "->" << dist + << ", startScale" << m_startScale << "->" << scale + << ", activeRotation" << m_activeRotation + << ", rotation" << rotation + << " from " << event->device()->type(); + + emit updated(); +} + +/*! + \readonly + \qmlproperty QPointF QtQuick::PinchHandler::centroid + + A point exactly in the middle of the currently-pressed touch points. + If \l pinchOrigin is set to \c PinchCenter, the \l target will be rotated + around this point. +*/ + +/*! + \readonly + \qmlproperty QVector2D QtQuick::PinchHandler::centroidVelocity + + The average velocity of the \l centroid: a vector representing the speed + and direction of movement of the whole group of touchpoints, in logical + pixels per second. +*/ + +/*! + \readonly + \qmlproperty real QtQuick::PinchHandler::scale + + The scale factor. It is 1.0 when the gesture begins, increases as the + touchpoints are spread apart, and decreases as the touchpoints are brought + together. If \l target is not null, this will be automatically applied to its + \l {Item::scale}{scale}. Otherwise, bindings can be used to do arbitrary + things with this value. +*/ + +/*! + \readonly + \qmlproperty real QtQuick::PinchHandler::rotation + + The rotation of the pinch gesture in degrees, with positive values clockwise. + It is 0 when the gesture begins. If \l target is not null, this will be + automatically applied to its \l {Item::rotation}{rotation}. Otherwise, + bindings can be used to do arbitrary things with this value. +*/ + +/*! + \readonly + \qmlproperty QVector2D QtQuick::PinchHandler::translation + + The translation of the gesture \l centroid. It is \c (0, 0) when the + gesture begins. +*/ + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpinchhandler_p.h b/src/quick/handlers/qquickpinchhandler_p.h new file mode 100644 index 0000000000..7d6b7d9509 --- /dev/null +++ b/src/quick/handlers/qquickpinchhandler_p.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPINCHHANDLER_H +#define QQUICKPINCHHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickitem.h" +#include "qevent.h" +#include "qquickmultipointhandler_p.h" +#include <private/qquicktranslate_p.h> + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickPinchHandler : public QQuickMultiPointHandler +{ + Q_OBJECT + Q_PROPERTY(qreal minimumScale READ minimumScale WRITE setMinimumScale NOTIFY minimumScaleChanged) + Q_PROPERTY(qreal maximumScale READ maximumScale WRITE setMaximumScale NOTIFY maximumScaleChanged) + Q_PROPERTY(qreal minimumRotation READ minimumRotation WRITE setMinimumRotation NOTIFY minimumRotationChanged) + Q_PROPERTY(qreal maximumRotation READ maximumRotation WRITE setMaximumRotation NOTIFY maximumRotationChanged) + Q_PROPERTY(PinchOrigin pinchOrigin READ pinchOrigin WRITE setPinchOrigin NOTIFY pinchOriginChanged) + Q_PROPERTY(QPointF centroid READ centroid NOTIFY updated) + Q_PROPERTY(QVector2D centroidVelocity READ centroidVelocity NOTIFY updated) + Q_PROPERTY(qreal scale READ scale NOTIFY updated) + Q_PROPERTY(qreal rotation READ rotation NOTIFY updated) + Q_PROPERTY(QVector2D translation READ translation NOTIFY updated) + Q_PROPERTY(qreal minimumX READ minimumX WRITE setMinimumX NOTIFY minimumXChanged) + Q_PROPERTY(qreal maximumX READ maximumX WRITE setMaximumX NOTIFY maximumXChanged) + Q_PROPERTY(qreal minimumY READ minimumY WRITE setMinimumY NOTIFY minimumYChanged) + Q_PROPERTY(qreal maximumY READ maximumY WRITE setMaximumY NOTIFY maximumYChanged) + +public: + enum PinchOrigin { + FirstPoint, PinchCenter, TargetCenter + }; + Q_ENUM(PinchOrigin) + + explicit QQuickPinchHandler(QObject *parent = 0); + ~QQuickPinchHandler(); + + qreal minimumScale() const { return m_minimumScale; } + void setMinimumScale(qreal minimumScale); + + qreal maximumScale() const { return m_maximumScale; } + void setMaximumScale(qreal maximumScale); + + qreal minimumRotation() const { return m_minimumRotation; } + void setMinimumRotation(qreal minimumRotation); + + qreal maximumRotation() const { return m_maximumRotation; } + void setMaximumRotation(qreal maximumRotation); + + PinchOrigin pinchOrigin() const { return m_pinchOrigin; } + void setPinchOrigin(PinchOrigin pinchOrigin); + + QVector2D translation() const { return m_activeTranslation; } + qreal scale() const { return m_activeScale; } + qreal rotation() const { return m_activeRotation; } + QPointF centroid() const { return m_centroid; } + QVector2D centroidVelocity() const { return m_centroidVelocity; } + + qreal minimumX() const { return m_minimumX; } + void setMinimumX(qreal minX); + qreal maximumX() const { return m_maximumX; } + void setMaximumX(qreal maxX); + qreal minimumY() const { return m_minimumY; } + void setMinimumY(qreal minY); + qreal maximumY() const { return m_maximumY; } + void setMaximumY(qreal maxY); + +signals: + void minimumScaleChanged(); + void maximumScaleChanged(); + void minimumRotationChanged(); + void maximumRotationChanged(); + void minimumXChanged(); + void maximumXChanged(); + void minimumYChanged(); + void maximumYChanged(); + void pinchOriginChanged(); + void updated(); + +protected: + bool wantsPointerEvent(QQuickPointerEvent *event) override; + void onActiveChanged() override; + void handlePointerEventImpl(QQuickPointerEvent *event) override; + +private: + // properties + qreal m_activeScale; + qreal m_activeRotation; + QVector2D m_activeTranslation; + QPointF m_centroid; + QVector2D m_centroidVelocity; + + qreal m_minimumScale; + qreal m_maximumScale; + + qreal m_minimumRotation; + qreal m_maximumRotation; + + qreal m_minimumX; + qreal m_maximumX; + qreal m_minimumY; + qreal m_maximumY; + + PinchOrigin m_pinchOrigin; + + // internal + qreal m_startScale; + qreal m_startRotation; + QPointF m_startCentroid; + qreal m_startDistance; + QPointF m_startPos; + + QVector<PointData> m_startAngles; + QMatrix4x4 m_startMatrix; + QQuickMatrix4x4 m_transform; + +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPinchHandler) + +#endif // QQUICKPINCHHANDLER_H diff --git a/src/quick/handlers/qquickpointerdevicehandler.cpp b/src/quick/handlers/qquickpointerdevicehandler.cpp new file mode 100644 index 0000000000..1521b58d57 --- /dev/null +++ b/src/quick/handlers/qquickpointerdevicehandler.cpp @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpointerdevicehandler_p.h" +#include <private/qquickitem_p.h> +#include <QMouseEvent> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype PointerDeviceHandler + \qmlabstract + \since 5.10 + \preliminary + \instantiates QQuickPointerDeviceHandler + \inherits PointerHandler + \inqmlmodule Qt.labs.handlers + \ingroup qtquick-handlers + \brief Abstract handler for pointer events with device-specific constraints. + + An intermediate class (not registered as a QML type) for handlers which + allow filtering based on device type, pointer type, or keyboard modifiers. +*/ +QQuickPointerDeviceHandler::QQuickPointerDeviceHandler(QObject *parent) + : QQuickPointerHandler(parent) + , m_acceptedDevices(QQuickPointerDevice::AllDevices) + , m_acceptedPointerTypes(QQuickPointerDevice::AllPointerTypes) + , m_acceptedModifiers(Qt::KeyboardModifierMask) +{ +} + +QQuickPointerDeviceHandler::~QQuickPointerDeviceHandler() +{ +} + +/*! + \qmlproperty int PointerDeviceHandler::acceptedDevices + + The types of pointing devices that can activate this Pointer Handler. + + By default, this property is set to \l PointerDevice.AllDevices. + If you set it to an OR combination of device types, it will ignore events + from non-matching devices. + + For example, a control could be made to respond to mouse and stylus clicks + in one way, and touchscreen taps in another way, with two handlers: + + \qml + Item { + TapHandler { + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + onTapped: console.log("clicked") + } + TapHandler { + acceptedDevices: PointerDevice.TouchScreen + onTapped: console.log("tapped") + } + } + \endqml +*/ +void QQuickPointerDeviceHandler::setAcceptedDevices(QQuickPointerDevice::DeviceTypes acceptedDevices) +{ + if (m_acceptedDevices == acceptedDevices) + return; + + m_acceptedDevices = acceptedDevices; + emit acceptedDevicesChanged(); +} + +/*! + \qmlproperty int PointerDeviceHandler::acceptedPointerTypes + + The types of pointing instruments (finger, stylus, eraser, etc.) + that can activate this Pointer Handler. + + By default, this property is set to \l PointerDevice.AllPointerTypes. + If you set it to an OR combination of device types, it will ignore events + from non-matching events. + + For example, a control could be made to respond to mouse, touch, and stylus clicks + in some way, but delete itself if tapped with an eraser tool on a graphics tablet, + with two handlers: + + \qml + Rectangle { + id: rect + TapHandler { + acceptedPointerTypes: PointerDevice.GenericPointer | PointerDevice.Finger | PointerDevice.Pen + onTapped: console.log("clicked") + } + TapHandler { + acceptedPointerTypes: PointerDevice.Eraser + onTapped: rect.destroy() + } + } + \endqml +*/ +void QQuickPointerDeviceHandler::setAcceptedPointerTypes(QQuickPointerDevice::PointerTypes acceptedPointerTypes) +{ + if (m_acceptedPointerTypes == acceptedPointerTypes) + return; + + m_acceptedPointerTypes = acceptedPointerTypes; + emit acceptedPointerTypesChanged(); +} + +/*! + \qmlproperty int PointerDeviceHandler::acceptedModifiers + + If this property is set, it will require the given keyboard modifiers to + be pressed in order to react to pointer events, and otherwise ignore them. + + If this property is set to \c Qt.KeyboardModifierMask (the default value), + then the PointerHandler ignores the modifier keys. + + For example, an \l [QML] Item could have two handlers of the same type, + one of which is enabled only if the required keyboard modifiers are + pressed: + + \qml + Item { + TapHandler { + acceptedModifiers: Qt.ControlModifier + onTapped: console.log("control-tapped") + } + TapHandler { + acceptedModifiers: Qt.NoModifier + onTapped: console.log("tapped") + } + } + \endqml +*/ +void QQuickPointerDeviceHandler::setAcceptedModifiers(Qt::KeyboardModifiers acceptedModifiers) +{ + if (m_acceptedModifiers == acceptedModifiers) + return; + + m_acceptedModifiers = acceptedModifiers; + emit acceptedModifiersChanged(); +} + +bool QQuickPointerDeviceHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickPointerHandler::wantsPointerEvent(event)) + return false; + qCDebug(lcPointerHandlerDispatch) << objectName() + << "checking device type" << m_acceptedDevices + << "pointer type" << m_acceptedPointerTypes + << "modifiers" << m_acceptedModifiers; + if ((event->device()->type() & m_acceptedDevices) == 0) + return false; + if ((event->device()->pointerType() & m_acceptedPointerTypes) == 0) + return false; + if (m_acceptedModifiers != Qt::KeyboardModifierMask && event->modifiers() != m_acceptedModifiers) + return false; + return true; +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpointerdevicehandler_p.h b/src/quick/handlers/qquickpointerdevicehandler_p.h new file mode 100644 index 0000000000..9e30fa0be4 --- /dev/null +++ b/src/quick/handlers/qquickpointerdevicehandler_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOINTERDEVICEHANDLER_H +#define QQUICKPOINTERDEVICEHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickpointerhandler_p.h" + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickPointerDeviceHandler : public QQuickPointerHandler +{ + Q_OBJECT + Q_PROPERTY(QQuickPointerDevice::DeviceTypes acceptedDevices READ acceptedDevices WRITE setAcceptedDevices NOTIFY acceptedDevicesChanged) + Q_PROPERTY(QQuickPointerDevice::PointerTypes acceptedPointerTypes READ acceptedPointerTypes WRITE setAcceptedPointerTypes NOTIFY acceptedPointerTypesChanged) + Q_PROPERTY(Qt::KeyboardModifiers acceptedModifiers READ acceptedModifiers WRITE setAcceptedModifiers NOTIFY acceptedModifiersChanged) + +public: + explicit QQuickPointerDeviceHandler(QObject *parent = 0); + ~QQuickPointerDeviceHandler(); + + QQuickPointerDevice::DeviceTypes acceptedDevices() const { return m_acceptedDevices; } + QQuickPointerDevice::PointerTypes acceptedPointerTypes() const { return m_acceptedPointerTypes; } + Qt::KeyboardModifiers acceptedModifiers() const { return m_acceptedModifiers; } + +public slots: + void setAcceptedDevices(QQuickPointerDevice::DeviceTypes acceptedDevices); + void setAcceptedPointerTypes(QQuickPointerDevice::PointerTypes acceptedPointerTypes); + void setAcceptedModifiers(Qt::KeyboardModifiers acceptedModifiers); + +Q_SIGNALS: + void acceptedDevicesChanged(); + void acceptedPointerTypesChanged(); + void acceptedModifiersChanged(); + +protected: + bool wantsPointerEvent(QQuickPointerEvent *event) override; + +protected: + QQuickPointerDevice::DeviceTypes m_acceptedDevices; + QQuickPointerDevice::PointerTypes m_acceptedPointerTypes; + Qt::KeyboardModifiers m_acceptedModifiers; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPointerDeviceHandler) + +#endif // QQUICKPOINTERDEVICEHANDLER_H diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp new file mode 100644 index 0000000000..c32bec665c --- /dev/null +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -0,0 +1,311 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpointerhandler_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPointerHandlerDispatch, "qt.quick.handler.dispatch") +Q_LOGGING_CATEGORY(lcPointerHandlerActive, "qt.quick.handler.active") + +/*! + \qmltype PointerHandler + \qmlabstract + \since 5.10 + \preliminary + \instantiates QQuickPointerHandler + \inqmlmodule Qt.labs.handlers + \ingroup qtquick-handlers + \brief Abstract handler for pointer events. + + PointerHandler is the base class handler (not registered as a QML type) for + pointer events without regard to source (touch, mouse or graphics tablet). +*/ + +QQuickPointerHandler::QQuickPointerHandler(QObject *parent) + : QObject(parent) + , m_currentEvent(nullptr) + , m_target(nullptr) + , m_enabled(true) + , m_active(false) + , m_targetExplicitlySet(false) + , m_hadKeepMouseGrab(false) + , m_hadKeepTouchGrab(false) +{ +} + +QQuickPointerHandler::~QQuickPointerHandler() +{ + QQuickItem *parItem = parentItem(); + if (parItem) { + QQuickItemPrivate *p = QQuickItemPrivate::get(parItem); + p->extra.value().pointerHandlers.removeOne(this); + } +} + +/*! + Notification that the grab has changed in some way which is relevant to this handler. + The \a grabber (subject) will be the PointerHandler whose state is changing, + or null if the state change regards an Item. + The \a stateChange (verb) tells what happened. + The \a point (object) is the point that was grabbed or ungrabbed. + EventPoint has the sole responsibility to call this function. + The PointerHandler must react in whatever way is appropriate, and must + emit the relevant signals (for the benefit of QML code). + A subclass is allowed to override this virtual function, but must always + call its parent class's implementation in addition to (usually after) + whatever custom behavior it implements. +*/ +void QQuickPointerHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) +{ + qCDebug(lcPointerHandlerDispatch) << point << stateChange << grabber; + Q_ASSERT(point); + if (grabber == this) { + bool wasCanceled = false; + emit grabChanged(point); + switch (stateChange) { + case QQuickEventPoint::GrabPassive: + case QQuickEventPoint::GrabExclusive: + break; + case QQuickEventPoint::CancelGrabPassive: + case QQuickEventPoint::CancelGrabExclusive: + wasCanceled = true; // the grab was stolen by something else + Q_FALLTHROUGH(); + case QQuickEventPoint::UngrabPassive: + case QQuickEventPoint::UngrabExclusive: + setActive(false); + point->setAccepted(false); + if (auto par = parentItem()) { + par->setKeepMouseGrab(m_hadKeepMouseGrab); + par->setKeepTouchGrab(m_hadKeepTouchGrab); + } + case QQuickEventPoint::OverrideGrabPassive: + // Passive grab is still there, but we won't receive point updates right now. + // No need to notify about this. + return; + } + if (wasCanceled) + emit canceled(point); + else + emit grabChanged(point); + } +} + +/*! + \internal + Acquire or give up a passive grab of the given \a point, according to the \a grab state. + + Unlike the exclusive grab, multiple PointerHandlers can have passive grabs + simultaneously. This means that each of them will receive further events + when the \a point moves, and when it is finally released. Typically a + PointerHandler should acquire a passive grab as soon as a point is pressed, + if the handler's constraints do not clearly rule out any interest in that + point. For example, DragHandler needs a passive grab in order to watch the + movement of a point to see whether it will be dragged past the drag + threshold. When a handler is actively manipulating its \l target (that is, + when \l active is true), it may be able to do its work with only a passive + grab, or it may acquire an exclusive grab if the gesture clearly must not + be interpreted in another way by another handler. +*/ +void QQuickPointerHandler::setPassiveGrab(QQuickEventPoint *point, bool grab) +{ + qCDebug(lcPointerHandlerDispatch) << point << grab; + if (grab) { + point->setGrabberPointerHandler(this, false); + } else { + point->removePassiveGrabber(this); + } +} + +void QQuickPointerHandler::setExclusiveGrab(QQuickEventPoint *point, bool grab) +{ + // TODO m_hadKeepMouseGrab m_hadKeepTouchGrab + qCDebug(lcPointerHandlerDispatch) << point << grab; + // Don't allow one handler to cancel another's grab, unless it is stealing it for itself + if (!grab && point->grabberPointerHandler() != this) + return; + point->setGrabberPointerHandler(grab ? this : nullptr, true); +} + +/*! + \internal + Cancel any existing grab of the given \a point. +*/ +void QQuickPointerHandler::cancelAllGrabs(QQuickEventPoint *point) +{ + qCDebug(lcPointerHandlerDispatch) << point; + point->cancelAllGrabs(this); +} + +QPointF QQuickPointerHandler::eventPos(const QQuickEventPoint *point) const +{ + return (target() ? target()->mapFromScene(point->scenePosition()) : point->scenePosition()); +} + +bool QQuickPointerHandler::parentContains(const QQuickEventPoint *point) const +{ + if (point) { + if (QQuickItem *par = parentItem()) + return par->contains(par->mapFromScene(point->scenePosition())); + } + return false; +} + +/*! + \qmlproperty bool QtQuick::PointerHandler::enabled + + If a PointerHandler is disabled, it will reject all events + and no signals will be emitted. +*/ +void QQuickPointerHandler::setEnabled(bool enabled) +{ + if (m_enabled == enabled) + return; + + m_enabled = enabled; + emit enabledChanged(); +} + +/*! + \qmlproperty Item QtQuick::PointerHandler::target + + The Item which this handler will manipulate. + + By default, it is the same as the \l parent: the Item within which + the handler is declared. However, it can sometimes be useful to set the + target to a different Item, in order to handle events within one item + but manipulate another; or to \c null, to disable the default behavior + and do something else instead. +*/ +void QQuickPointerHandler::setTarget(QQuickItem *target) +{ + m_targetExplicitlySet = true; + if (m_target == target) + return; + + m_target = target; + emit targetChanged(); +} + +QQuickItem *QQuickPointerHandler::target() const +{ + if (!m_targetExplicitlySet) + return parentItem(); + return m_target; +} + +void QQuickPointerHandler::handlePointerEvent(QQuickPointerEvent *event) +{ + bool wants = wantsPointerEvent(event); + qCDebug(lcPointerHandlerDispatch) << metaObject()->className() << objectName() + << "on" << parentItem()->metaObject()->className() << parentItem()->objectName() + << (wants ? "WANTS" : "DECLINES") << event; + if (wants) { + handlePointerEventImpl(event); + } else { + setActive(false); + int pCount = event->pointCount(); + for (int i = 0; i < pCount; ++i) { + QQuickEventPoint *pt = event->point(i); + if (pt->grabberPointerHandler() == this && pt->state() != QQuickEventPoint::Stationary) + pt->cancelExclusiveGrab(); + } + } + event->device()->eventDeliveryTargets().append(this); +} + +bool QQuickPointerHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + Q_UNUSED(event) + return m_enabled; +} + +/*! + \readonly + \qmlproperty bool QtQuick::PointerHandler::active + + This holds true whenever this PointerHandler has taken sole responsibility + for handing one or more EventPoints, by successfully taking an exclusive + grab of those points. This means that it is keeping its properties + up-to-date according to the movements of those Event Points and actively + manipulating its \l target (if any). +*/ +void QQuickPointerHandler::setActive(bool active) +{ + if (m_active != active) { + qCDebug(lcPointerHandlerActive) << this << m_active << "->" << active; + m_active = active; + onActiveChanged(); + emit activeChanged(); + } +} + +void QQuickPointerHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + m_currentEvent = event; +} + +/*! + \readonly + \qmlproperty Item QtQuick::PointerHandler::parent + + The \l Item which is the scope of the handler; the Item in which it was declared. + The handler will handle events on behalf of this Item, which means a + pointer event is relevant if at least one of its event points occurs within + the Item's interior. Initially \l target() is the same, but target() + can be reassigned. + + \sa QQuick::PointerHandler::target(), QObject::parent() +*/ + +/*! + \qmlsignal QtQuick::PointerHandler::grabChanged(EventPoint point) + + This signal is emitted when this handler has acquired or relinquished a + passive or exclusive grab of the given \a point. +*/ + +/*! + \qmlsignal QtQuick::PointerHandler::canceled(EventPoint point) + + If this handler has already grabbed the given \a point, this signal is + emitted when the grab is stolen by a different Pointer Handler or Item. +*/ + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpointerhandler_p.h b/src/quick/handlers/qquickpointerhandler_p.h new file mode 100644 index 0000000000..24a058275d --- /dev/null +++ b/src/quick/handlers/qquickpointerhandler_p.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOINTERHANDLER_H +#define QQUICKPOINTERHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qevent.h" + +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcPointerHandlerDispatch) + +class Q_QUICK_PRIVATE_EXPORT QQuickPointerHandler : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool active READ active NOTIFY activeChanged) + Q_PROPERTY(QQuickItem * target READ target WRITE setTarget NOTIFY targetChanged) + Q_PROPERTY(QQuickItem * parent READ parentItem CONSTANT) + +public: + explicit QQuickPointerHandler(QObject *parent = 0); + virtual ~QQuickPointerHandler(); + +public: + bool enabled() const { return m_enabled; } + void setEnabled(bool enabled); + + bool active() const { return m_active; } + + QQuickItem *target() const; + void setTarget(QQuickItem *target); + + QQuickItem * parentItem() const { return static_cast<QQuickItem *>(QObject::parent()); } + + void handlePointerEvent(QQuickPointerEvent *event); + +Q_SIGNALS: + void enabledChanged(); + void activeChanged(); + void targetChanged(); + void grabChanged(QQuickEventPoint *point); + void canceled(QQuickEventPoint *point); + +protected: + QQuickPointerEvent *currentEvent() { return m_currentEvent; } + virtual bool wantsPointerEvent(QQuickPointerEvent *event); + virtual void handlePointerEventImpl(QQuickPointerEvent *event); + void setActive(bool active); + virtual void onActiveChanged() { } + virtual void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point); + void setPassiveGrab(QQuickEventPoint *point, bool grab = true); + void setExclusiveGrab(QQuickEventPoint *point, bool grab = true); + void cancelAllGrabs(QQuickEventPoint *point); + QPointF eventPos(const QQuickEventPoint *point) const; + bool parentContains(const QQuickEventPoint *point) const; + +private: + QQuickPointerEvent *m_currentEvent; + QQuickItem *m_target; + bool m_enabled : 1; + bool m_active : 1; + bool m_targetExplicitlySet : 1; + bool m_hadKeepMouseGrab : 1; // some handlers override target()->setKeepMouseGrab(); this remembers previous state + bool m_hadKeepTouchGrab : 1; // some handlers override target()->setKeepTouchGrab(); this remembers previous state + + friend class QQuickEventPoint; + friend class QQuickWindowPrivate; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPointerHandler) + +#endif // QQUICKPOINTERHANDLER_H diff --git a/src/quick/handlers/qquicksinglepointhandler.cpp b/src/quick/handlers/qquicksinglepointhandler.cpp new file mode 100644 index 0000000000..dee168a8e4 --- /dev/null +++ b/src/quick/handlers/qquicksinglepointhandler.cpp @@ -0,0 +1,475 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicksinglepointhandler_p.h" + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(DBG_TOUCH_TARGET) + +/*! + \qmltype SinglePointHandler + \qmlabstract + \preliminary + \instantiates QQuickSinglePointHandler + \inherits PointerDeviceHandler + \inqmlmodule Qt.labs.handlers + \ingroup qtquick-handlers + \brief Abstract handler for single-point Pointer Events. + + An intermediate class (not registered as a QML type) + for the most common handlers: those which expect only a single point. + wantsPointerEvent() will choose the first point which is inside the + \l target item, and return true as long as the event contains that point. + Override handleEventPoint() to implement a single-point handler. +*/ + +QQuickSinglePointHandler::QQuickSinglePointHandler(QObject *parent) + : QQuickPointerDeviceHandler(parent) + , m_acceptedButtons(Qt::LeftButton) + , m_ignoreAdditionalPoints(false) +{ +} + +bool QQuickSinglePointHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) + return false; + if (event->device()->pointerType() != QQuickPointerDevice::Finger && + (event->buttons() & m_acceptedButtons) == 0 && (event->button() & m_acceptedButtons) == 0) + return false; + + if (m_pointInfo.m_id) { + // We already know which one we want, so check whether it's there. + // It's expected to be an update or a release. + // If we no longer want it, cancel the grab. + int candidatePointCount = 0; + QQuickEventPoint *point = nullptr; + int c = event->pointCount(); + for (int i = 0; i < c; ++i) { + QQuickEventPoint *p = event->point(i); + if (wantsEventPoint(p)) { + ++candidatePointCount; + if (p->pointId() == m_pointInfo.m_id) + point = p; + } + } + if (point) { + if (candidatePointCount == 1 || (candidatePointCount > 1 && m_ignoreAdditionalPoints)) { + point->setAccepted(); + return true; + } else { + point->cancelAllGrabs(this); + } + } else { + qCWarning(DBG_TOUCH_TARGET) << this << "pointId" << hex << m_pointInfo.m_id + << "is missing from current event, but was neither canceled nor released"; + return false; + } + } else { + // We have not yet chosen a point; choose the first one for which wantsEventPoint() returns true. + int candidatePointCount = 0; + int c = event->pointCount(); + QQuickEventPoint *chosen = nullptr; + for (int i = 0; i < c; ++i) { + QQuickEventPoint *p = event->point(i); + if (!p->exclusiveGrabber() && wantsEventPoint(p)) { + if (!chosen) + chosen = p; + ++candidatePointCount; + } + } + if (chosen && candidatePointCount == 1) { + m_pointInfo.m_id = chosen->pointId(); + chosen->setAccepted(); + } + } + return m_pointInfo.m_id; +} + +void QQuickSinglePointHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + QQuickPointerDeviceHandler::handlePointerEventImpl(event); + QQuickEventPoint *currentPoint = event->pointById(m_pointInfo.m_id); + Q_ASSERT(currentPoint); + if (!m_pointInfo.m_id || !currentPoint->isAccepted()) { + reset(); + } else { + if (event->asPointerTouchEvent()) { + QQuickEventTouchPoint *tp = static_cast<QQuickEventTouchPoint *>(currentPoint); + m_pointInfo.m_uniqueId = tp->uniqueId(); + m_pointInfo.m_rotation = tp->rotation(); + m_pointInfo.m_pressure = tp->pressure(); + m_pointInfo.m_ellipseDiameters = tp->ellipseDiameters(); + } else if (event->asPointerTabletEvent()) { + // TODO + } else { + m_pointInfo.m_uniqueId = event->device()->uniqueId(); + m_pointInfo.m_rotation = 0; + m_pointInfo.m_pressure = event->buttons() ? 1 : 0; + m_pointInfo.m_ellipseDiameters = QSizeF(); + } + m_pointInfo.m_position = currentPoint->position(); + m_pointInfo.m_scenePosition = currentPoint->scenePosition(); + if (currentPoint->state() == QQuickEventPoint::Updated) + m_pointInfo.m_velocity = currentPoint->velocity(); + handleEventPoint(currentPoint); + switch (currentPoint->state()) { + case QQuickEventPoint::Pressed: + m_pointInfo.m_pressPosition = currentPoint->position(); + m_pointInfo.m_scenePressPosition = currentPoint->scenePosition(); + m_pointInfo.m_pressedButtons = event->buttons(); + break; + case QQuickEventPoint::Released: + setExclusiveGrab(currentPoint, false); + reset(); + break; + default: + m_pointInfo.m_pressedButtons = event->buttons(); + break; + } + emit pointChanged(); + } +} + +bool QQuickSinglePointHandler::wantsEventPoint(QQuickEventPoint *point) +{ + return parentContains(point); +} + +void QQuickSinglePointHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) +{ + if (grabber != this) + return; + switch (stateChange) { + case QQuickEventPoint::GrabExclusive: + m_pointInfo.m_sceneGrabPosition = point->sceneGrabPosition(); + setActive(true); + QQuickPointerHandler::onGrabChanged(grabber, stateChange, point); + break; + case QQuickEventPoint::GrabPassive: + m_pointInfo.m_sceneGrabPosition = point->sceneGrabPosition(); + QQuickPointerHandler::onGrabChanged(grabber, stateChange, point); + break; + case QQuickEventPoint::OverrideGrabPassive: + return; // don't emit + case QQuickEventPoint::UngrabPassive: + case QQuickEventPoint::UngrabExclusive: + case QQuickEventPoint::CancelGrabPassive: + case QQuickEventPoint::CancelGrabExclusive: + // the grab is lost or relinquished, so the point is no longer relevant + QQuickPointerHandler::onGrabChanged(grabber, stateChange, point); + reset(); + break; + } + emit singlePointGrabChanged(); +} + +void QQuickSinglePointHandler::setIgnoreAdditionalPoints(bool v) +{ + m_ignoreAdditionalPoints = v; +} + +void QQuickSinglePointHandler::moveTarget(QPointF pos, QQuickEventPoint *point) +{ + target()->setPosition(pos); + m_pointInfo.m_scenePosition = point->scenePosition(); + m_pointInfo.m_position = target()->mapFromScene(m_pointInfo.m_scenePosition); +} + +/*! + \qmlproperty int QtQuick::SinglePointHandler::acceptedButtons + + The mouse buttons which can activate this Pointer Handler. + + By default, this property is set to \l Qt.LeftButton. + It can be set to an OR combination of mouse buttons, and will ignore events + from other buttons. + + For example, a control could be made to respond to left and right clicks + in different ways, with two handlers: + + \qml + Item { + TapHandler { + onTapped: console.log("left clicked") + } + TapHandler { + acceptedButtons: Qt.RightButton + onTapped: console.log("right clicked") + } + } + \endqml + + \note Tapping on a touchscreen or tapping the stylus on a graphics tablet + emulates clicking the left mouse button. This behavior can be altered via + \l {PointerDeviceHandler::acceptedDevices}{acceptedDevices} or + \l {PointerDeviceHandler::acceptedPointerTypes}{acceptedPointerTypes}. +*/ +void QQuickSinglePointHandler::setAcceptedButtons(Qt::MouseButtons buttons) +{ + if (m_acceptedButtons == buttons) + return; + + m_acceptedButtons = buttons; + emit acceptedButtonsChanged(); +} + +void QQuickSinglePointHandler::reset() +{ + setActive(false); + m_pointInfo.reset(); +} + +/*! + \readonly + \qmlproperty HandlerPoint QtQuick::SinglePointHandler::point + + The event point currently being handled. When no point is currently being + handled, this object is reset to default values (all coordinates are 0). +*/ + +/*! + \qmltype HandlerPoint + \instantiates QQuickHandlerPoint + \inqmlmodule Qt.labs.handlers + \ingroup qtquick-handlers + \brief An event point + + A QML representation of a QQuickEventPoint. + + It's possible to make bindings to properties of a \l SinglePointHandler's + current point. For example: + + \snippet pointerHandlers/dragHandlerNullTarget.qml 0 + + The point is kept up-to-date when the DragHandler is actively responding to + an EventPoint; but when the point is released, or the current point is + being handled by a different handler, \c position.x and \c position.y are 0. + + \note This is practically identical to QtQuick::EventPoint; however an + EventPoint is a long-lived QObject which is invalidated between gestures + and reused for subsequent event deliveries. Continuous bindings to its + properties are not possible, and an individual handler cannot rely on it + outside the period when that point is part of an active gesture which that + handler is handling. HandlerPoint is a Q_GADGET that the handler owns. + This allows you to make lifetime bindings to its properties. + + \sa SinglePointHandler::point +*/ + +QQuickHandlerPoint::QQuickHandlerPoint() + : m_id(0) + , m_rotation(0) + , m_pressure(0) +{} + +void QQuickHandlerPoint::reset() +{ + m_id = 0; + m_uniqueId = QPointingDeviceUniqueId(); + m_position = QPointF(); + m_scenePosition = QPointF(); + m_pressPosition = QPointF(); + m_scenePressPosition = QPointF(); + m_sceneGrabPosition = QPointF(); + m_velocity = QVector2D(); + m_rotation = 0; + m_pressure = 0; + m_ellipseDiameters = QSizeF(); + m_pressedButtons = Qt::NoButton; +} + +/*! + \readonly + \qmlproperty int QtQuick::HandlerPoint::id + \brief The ID number of the point + + During a touch gesture, from the time that the first finger is pressed + until the last finger is released, each touchpoint will have a unique ID + number. Likewise, if input from multiple devices occurs (for example + simultaneous mouse and touch presses), all the current event points from + all the devices will have unique IDs. + + \note Do not assume that id numbers start at zero or that they are + sequential. Such an assumption is often false due to the way the underlying + drivers work. + + \sa QTouchEvent::TouchPoint::id +*/ + +/*! + \readonly + \qmlproperty PointingDeviceUniqueId QtQuick::HandlerPoint::uniqueId + \brief The unique ID of the point, if any + + This is normally empty, because touchscreens cannot uniquely identify fingers. + + On some types of touchscreens, especially those using TUIO drivers, + it's possible to use recognizable physical tokens (fiducial objects) + in addition to fingers. So if this point is a touch point, and + uniqueId is set, it is the identifier for such an object. + + On a graphics tablet, each type of stylus or other tool often has a unique + ID or serial number, which can be useful to respond in different ways to + different tools. + + Interpreting the contents of this ID requires knowledge of the hardware and + drivers in use. + + \sa QTabletEvent::uniqueId, QtQuick::TouchPoint::uniqueId, QtQuick::EventTouchPoint::uniqueId +*/ + +/*! + \readonly + \qmlproperty QPointF QtQuick::HandlerPoint::position + \brief The position within the \c parent Item + + This is the position of the event point relative to the bounds of the \l parent. +*/ + +/*! + \readonly + \qmlproperty QPointF QtQuick::HandlerPoint::scenePosition + \brief The position within the scene + + This is the position of the event point relative to the bounds of the Qt + Quick scene (typically the whole window). +*/ + +/*! + \readonly + \qmlproperty QPointF QtQuick::HandlerPoint::pressPosition + \brief The pressed position within the \c parent Item + + This is the position at which this point was pressed, relative to the + bounds of the \l parent. +*/ + +/*! + \readonly + \qmlproperty QPointF QtQuick::HandlerPoint::scenePressPosition + \brief The pressed position within the scene + + This is the position at which this point was pressed, in the coordinate + system of the \l {Qt Quick Scene Graph}{scene graph}. +*/ + +/*! + \readonly + \qmlproperty QPointF QtQuick::HandlerPoint::sceneGrabPosition + \brief The grabbed position within the scene + + If this point has been grabbed by a Pointer Handler or an Item, it means + that object has taken sole responsibility for handling the movement and the + release if this point. In that case, this is the position at which the grab + occurred, in the coordinate system of the \l {Qt Quick Scene Graph}{scene graph}. +*/ + +/*! + \readonly + \qmlproperty enum QtQuick::HandlerPoint::pressedButtons + \brief Which mouse or stylus buttons are currently pressed + + \sa MouseArea::pressedButtons +*/ + +/*! + \readonly + \qmlproperty QVector2D QtQuick::HandlerPoint::velocity + \brief A vector representing the average speed and direction of movement + + This is a velocity vector pointing in the direction of movement, in logical + pixels per second. It has x and y components, at least one of which will be + nonzero when this point is in motion. It holds the average recent velocity: + how fast and in which direction the event point has been moving recently. + + \sa QtQuick::EventPoint::velocity, QtQuick::TouchPoint::velocity, QTouchEvent::TouchPoint::velocity +*/ + +/*! + \readonly + \qmlproperty qreal QtQuick::HandlerPoint::rotation + + This property holds the rotation angle of the stylus on a graphics tablet + or the contact patch of a touchpoint on a touchscreen. + + It is valid only with certain tablet stylus devices and touchscreens that + can measure the rotation angle. Otherwise, it will be zero. +*/ + +/*! + \readonly + \qmlproperty qreal QtQuick::HandlerPoint::pressure + + This property tells how hard the user is pressing the stylus on a graphics + tablet or the finger against a touchscreen, in the range from \c 0 (no + measurable pressure) to \c 1.0 (maximum pressure which the device can + measure). + + It is valid only with certain tablets and touchscreens that can measure + pressure. Otherwise, it will be zero. +*/ + +/*! + \readonly + \qmlproperty size QtQuick::HandlerPoint::ellipseDiameters + + This property holds the diameters of the contact patch, if the event + comes from a touchpoint and the device provides this information. + + A touchpoint is modeled as an elliptical area where the finger is pressed + against the touchscreen. (In fact, it could also be modeled as a bitmap; + but in that case we expect an elliptical bounding estimate to be fitted to + the contact patch before the event is sent.) The harder the user presses, + the larger the contact patch; so, these diameters provide an alternate way + of detecting pressure, in case the device does not include a separate + pressure sensor. The ellipse is centered on \l scenePosition (\l position + in the PointerHandler's Item's local coordinates). The \l rotation property + provides the rotation of the ellipse, if known. It is expected that if the + \l rotation is zero, the \l {QSize::height}{height} is the larger dimension + (the major axis), because of the usual hand position, reaching upward or + outward across the surface. + + If the contact patch is unknown, or the device is not a touchscreen, + these values will be zero. + + \sa QtQuick::EventPoint::ellipseDiameters, QtQuick::TouchPoint::ellipseDiameters, QTouchEvent::TouchPoint::ellipseDiameters +*/ + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquicksinglepointhandler_p.h b/src/quick/handlers/qquicksinglepointhandler_p.h new file mode 100644 index 0000000000..386cea253a --- /dev/null +++ b/src/quick/handlers/qquicksinglepointhandler_p.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOINTERSINGLEHANDLER_H +#define QQUICKPOINTERSINGLEHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickpointerdevicehandler_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickSinglePointHandler; + +class Q_QUICK_PRIVATE_EXPORT QQuickHandlerPoint { + Q_GADGET + Q_PROPERTY(int id READ id) + Q_PROPERTY(QPointingDeviceUniqueId uniqueId READ uniqueId) + Q_PROPERTY(QPointF position READ position) + Q_PROPERTY(QPointF scenePosition READ scenePosition) + Q_PROPERTY(QPointF pressPosition READ pressPosition) + Q_PROPERTY(QPointF scenePressPosition READ scenePressPosition) + Q_PROPERTY(QPointF sceneGrabPosition READ sceneGrabPosition) + Q_PROPERTY(Qt::MouseButtons pressedButtons READ pressedButtons) + Q_PROPERTY(QVector2D velocity READ velocity) + Q_PROPERTY(qreal rotation READ rotation) + Q_PROPERTY(qreal pressure READ pressure) + Q_PROPERTY(QSizeF ellipseDiameters READ ellipseDiameters) + +public: + QQuickHandlerPoint(); + + int id() const { return m_id; } + Qt::MouseButtons pressedButtons() const { return m_pressedButtons; } + QPointF pressPosition() const { return m_pressPosition; } + QPointF scenePressPosition() const { return m_scenePressPosition; } + QPointF sceneGrabPosition() const { return m_sceneGrabPosition; } + QPointF position() const { return m_position; } + QPointF scenePosition() const { return m_scenePosition; } + QVector2D velocity() const { return m_velocity; } + qreal rotation() const { return m_rotation; } + qreal pressure() const { return m_pressure; } + QSizeF ellipseDiameters() const { return m_ellipseDiameters; } + QPointingDeviceUniqueId uniqueId() const { return m_uniqueId; } + +private: + void reset(); + int m_id; + QPointingDeviceUniqueId m_uniqueId; + Qt::MouseButtons m_pressedButtons; + QPointF m_position; + QPointF m_scenePosition; + QPointF m_pressPosition; + QPointF m_scenePressPosition; + QPointF m_sceneGrabPosition; + QVector2D m_velocity; + qreal m_rotation; + qreal m_pressure; + QSizeF m_ellipseDiameters; + friend class QQuickSinglePointHandler; +}; + +class Q_QUICK_PRIVATE_EXPORT QQuickSinglePointHandler : public QQuickPointerDeviceHandler +{ + Q_OBJECT + Q_PROPERTY(Qt::MouseButtons acceptedButtons READ acceptedButtons WRITE setAcceptedButtons NOTIFY acceptedButtonsChanged) + Q_PROPERTY(QQuickHandlerPoint point READ point NOTIFY pointChanged) +public: + explicit QQuickSinglePointHandler(QObject *parent = 0); + virtual ~QQuickSinglePointHandler() { } + + Qt::MouseButtons acceptedButtons() const { return m_acceptedButtons; } + void setAcceptedButtons(Qt::MouseButtons buttons); + + QQuickHandlerPoint point() const { return m_pointInfo; } + +Q_SIGNALS: + void pointChanged(); + void singlePointGrabChanged(); // QQuickPointerHandler::grabChanged signal can't be a property notifier here + void acceptedButtonsChanged(); + +protected: + bool wantsPointerEvent(QQuickPointerEvent *event) override; + virtual bool wantsEventPoint(QQuickEventPoint *point); + void handlePointerEventImpl(QQuickPointerEvent *event) override; + virtual void handleEventPoint(QQuickEventPoint *point) = 0; + + QQuickEventPoint *currentPoint(QQuickPointerEvent *ev) { return ev->pointById(m_pointInfo.m_id); } + void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) override; + + void setIgnoreAdditionalPoints(bool v = true); + + void moveTarget(QPointF pos, QQuickEventPoint *point); + +private: + void reset(); + +private: + QQuickHandlerPoint m_pointInfo; + Qt::MouseButtons m_acceptedButtons; + bool m_ignoreAdditionalPoints : 1; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickHandlerPoint) +QML_DECLARE_TYPE(QQuickSinglePointHandler) + +#endif // QQUICKPOINTERSINGLEHANDLER_H diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp new file mode 100644 index 0000000000..e5b728db0f --- /dev/null +++ b/src/quick/handlers/qquicktaphandler.cpp @@ -0,0 +1,363 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktaphandler_p.h" +#include <qpa/qplatformtheme.h> +#include <private/qguiapplication_p.h> +#include <QtGui/qstylehints.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTapHandler, "qt.quick.handler.tap") + +qreal QQuickTapHandler::m_multiTapInterval(0.0); +// single tap distance is the same as the drag threshold +int QQuickTapHandler::m_mouseMultiClickDistanceSquared(-1); +int QQuickTapHandler::m_touchMultiTapDistanceSquared(-1); + +/*! + \qmltype TapHandler + \instantiates QQuickTapHandler + \inherits SinglePointHandler + \inqmlmodule Qt.labs.handlers + \ingroup qtquick-handlers + \brief Handler for taps and clicks. + + TapHandler is a handler for taps on a touchscreen or clicks on a mouse. + + Detection of a valid tap gesture depends on \l gesturePolicy. + Note that buttons (such as QPushButton) are often implemented not to care + whether the press and release occur close together: if you press the button + and then change your mind, you need to drag all the way off the edge of the + button in order to cancel the click. Therefore the default + \l gesturePolicy is \c TapHandler.ReleaseWithinBounds. If you want to require + that the press and release are close together in both space and time, + set it to \c TapHandler.DragThreshold. + + For multi-tap gestures (double-tap, triple-tap etc.), the distance moved + must not exceed QPlatformTheme::MouseDoubleClickDistance with mouse and + QPlatformTheme::TouchDoubleTapDistance with touch, and the time between + taps must not exceed QStyleHints::mouseDoubleClickInterval(). + + \sa MouseArea +*/ + +QQuickTapHandler::QQuickTapHandler(QObject *parent) + : QQuickSinglePointHandler(parent) + , m_pressed(false) + , m_gesturePolicy(ReleaseWithinBounds) + , m_tapCount(0) + , m_longPressThreshold(-1) + , m_lastTapTimestamp(0.0) +{ + if (m_mouseMultiClickDistanceSquared < 0) { + m_multiTapInterval = qApp->styleHints()->mouseDoubleClickInterval() / 1000.0; + m_mouseMultiClickDistanceSquared = QGuiApplicationPrivate::platformTheme()-> + themeHint(QPlatformTheme::MouseDoubleClickDistance).toInt(); + m_mouseMultiClickDistanceSquared *= m_mouseMultiClickDistanceSquared; + m_touchMultiTapDistanceSquared = QGuiApplicationPrivate::platformTheme()-> + themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt(); + m_touchMultiTapDistanceSquared *= m_touchMultiTapDistanceSquared; + } +} + +QQuickTapHandler::~QQuickTapHandler() +{ +} + +static bool dragOverThreshold(QQuickEventPoint *point) +{ + QPointF delta = point->scenePosition() - point->scenePressPosition(); + return (QQuickWindowPrivate::dragOverThreshold(delta.x(), Qt::XAxis, point) || + QQuickWindowPrivate::dragOverThreshold(delta.y(), Qt::YAxis, point)); +} + +bool QQuickTapHandler::wantsEventPoint(QQuickEventPoint *point) +{ + // If the user has not violated any constraint, it could be a tap. + // Otherwise we want to give up the grab so that a competing handler + // (e.g. DragHandler) gets a chance to take over. + // Don't forget to emit released in case of a cancel. + bool ret = false; + switch (point->state()) { + case QQuickEventPoint::Pressed: + case QQuickEventPoint::Released: + ret = parentContains(point); + break; + case QQuickEventPoint::Updated: + switch (m_gesturePolicy) { + case DragThreshold: + ret = !dragOverThreshold(point); + break; + case WithinBounds: + ret = parentContains(point); + break; + case ReleaseWithinBounds: + ret = point->pointId() == this->point().id(); + break; + } + break; + case QQuickEventPoint::Stationary: + // Never react in any way when the point hasn't moved. + // In autotests, the point's position may not even be correct, because + // QTest::touchEvent(window, touchDevice).stationary(1) + // provides no opportunity to give a position, so it ends up being random. + break; + } + // If this is the grabber, returning false from this function will cancel the grab, + // so onGrabChanged(this, CancelGrabExclusive, point) and setPressed(false) will be called. + // But when m_gesturePolicy is DragThreshold, we don't get an exclusive grab, but + // we still don't want to be pressed anymore. + if (!ret && point->pointId() == this->point().id() && point->state() != QQuickEventPoint::Stationary) + setPressed(false, true, point); + return ret; +} + +void QQuickTapHandler::handleEventPoint(QQuickEventPoint *point) +{ + switch (point->state()) { + case QQuickEventPoint::Pressed: + setPressed(true, false, point); + break; + case QQuickEventPoint::Released: + if ((point->pointerEvent()->buttons() & acceptedButtons()) == Qt::NoButton) + setPressed(false, false, point); + break; + default: + break; + } +} + +/*! + \qmlproperty real TapHandler::longPressThreshold + + The time in seconds that an event point must be pressed in order to + trigger a long press gesture and emit the \l longPressed() signal. + If the point is released before this time limit, a tap can be detected + if the \l gesturePolicy constraint is satisfied. The default value is + QStyleHints::mousePressAndHoldInterval() converted to seconds. +*/ +qreal QQuickTapHandler::longPressThreshold() const +{ + return longPressThresholdMilliseconds() / 1000.0; +} + +void QQuickTapHandler::setLongPressThreshold(qreal longPressThreshold) +{ + int ms = qRound(longPressThreshold * 1000); + if (m_longPressThreshold == ms) + return; + + m_longPressThreshold = ms; + emit longPressThresholdChanged(); +} + +int QQuickTapHandler::longPressThresholdMilliseconds() const +{ + return (m_longPressThreshold < 0 ? QGuiApplication::styleHints()->mousePressAndHoldInterval() : m_longPressThreshold); +} + +void QQuickTapHandler::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_longPressTimer.timerId()) { + m_longPressTimer.stop(); + qCDebug(lcTapHandler) << objectName() << "longPressed"; + emit longPressed(); + } +} + +/*! + \qmlproperty enumeration TapHandler::gesturePolicy + + The spatial constraint for a tap or long press gesture to be recognized, + in addition to the constraint that the release must occur before + \l longPressThreshold has elapsed. If these constraints are not satisfied, + the \l tapped signal is not emitted, and \l tapCount is not incremented. + If the spatial constraint is violated, \l isPressed transitions immediately + from true to false, regardless of the time held. + + \value TapHandler.DragThreshold + The event point must not move significantly. If the mouse, finger + or stylus moves past the system-wide drag threshold + (QStyleHints::startDragDistance), the tap gesture is canceled, even + if the button or finger is still pressed. This policy can be useful + whenever TapHandler needs to cooperate with other pointer handlers + (for example \l DragHandler), because in this case TapHandler will + never grab. + + \value TapHandler.WithinBounds + If the event point leaves the bounds of the \l target item, the tap + gesture is canceled. The TapHandler will grab on press, but release + the grab as soon as the boundary constraint is no longer satisfied. + + \value TapHandler.ReleaseWithinBounds + (the default value) At the time of release (the mouse button is + released or the finger is lifted), if the event point is outside + the bounds of the \l target item, a tap gesture is not recognized. + This is the default value, because it corresponds to typical button + behavior: you can cancel a click by dragging outside the button, + and you can also change your mind by dragging back inside the button + before release. Note that it's necessary for TapHandler to grab on + press and retain it until release (greedy grab) in order to detect + this gesture. +*/ +void QQuickTapHandler::setGesturePolicy(QQuickTapHandler::GesturePolicy gesturePolicy) +{ + if (m_gesturePolicy == gesturePolicy) + return; + + m_gesturePolicy = gesturePolicy; + emit gesturePolicyChanged(); +} + +/*! + \qmlproperty bool TapHandler::pressed + \readonly + + Holds true whenever the mouse or touch point is pressed, + and any movement since the press is compliant with the current + \l gesturePolicy. When the event point is released or the policy is + violated, \e pressed will change to false. +*/ +void QQuickTapHandler::setPressed(bool press, bool cancel, QQuickEventPoint *point) +{ + if (m_pressed != press) { + qCDebug(lcTapHandler) << objectName() << "pressed" << m_pressed << "->" << press << (cancel ? "CANCEL" : "") << point; + m_pressed = press; + connectPreRenderSignal(press); + updateTimeHeld(); + if (press) { + m_longPressTimer.start(longPressThresholdMilliseconds(), this); + m_holdTimer.start(); + } else { + m_longPressTimer.stop(); + m_holdTimer.invalidate(); + } + if (press) { + // on press, grab before emitting changed signals + if (m_gesturePolicy == DragThreshold) + setPassiveGrab(point, press); + else + setExclusiveGrab(point, press); + } + if (!cancel && !press && parentContains(point)) { + if (point->timeHeld() < longPressThreshold()) { + // Assuming here that pointerEvent()->timestamp() is in ms. + qreal ts = point->pointerEvent()->timestamp() / 1000.0; + if (ts - m_lastTapTimestamp < m_multiTapInterval && + QVector2D(point->scenePosition() - m_lastTapPos).lengthSquared() < + (point->pointerEvent()->device()->type() == QQuickPointerDevice::Mouse ? + m_mouseMultiClickDistanceSquared : m_touchMultiTapDistanceSquared)) + ++m_tapCount; + else + m_tapCount = 1; + qCDebug(lcTapHandler) << objectName() << "tapped" << m_tapCount << "times"; + emit tapped(); + emit tapCountChanged(); + m_lastTapTimestamp = ts; + m_lastTapPos = point->scenePosition(); + } else { + qCDebug(lcTapHandler) << objectName() << "tap threshold" << longPressThreshold() << "exceeded:" << point->timeHeld(); + } + } + emit pressedChanged(); + if (!press && m_gesturePolicy != DragThreshold) { + // on release, ungrab after emitting changed signals + setExclusiveGrab(point, press); + } + } +} + +void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) +{ + QQuickSinglePointHandler::onGrabChanged(grabber, stateChange, point); + bool isCanceled = stateChange == QQuickEventPoint::CancelGrabExclusive || stateChange == QQuickEventPoint::CancelGrabPassive; + if (grabber == this && (isCanceled || point->state() == QQuickEventPoint::Released)) + setPressed(false, isCanceled, point); +} + +void QQuickTapHandler::connectPreRenderSignal(bool conn) +{ + if (conn) + connect(parentItem()->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld); + else + disconnect(parentItem()->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld); +} + +void QQuickTapHandler::updateTimeHeld() +{ + emit timeHeldChanged(); +} + +/*! + \qmlproperty int TapHandler::tapCount + \readonly + + The number of taps which have occurred within the time and space + constraints to be considered a single gesture. For example, to detect + a double-tap, you can write: + + \qml + Rectangle { + width: 100; height: 30 + signal doubleTap + TapHandler { + acceptedButtons: Qt.AllButtons + onTapped: if (tapCount == 2) doubleTap() + } + } + \endqml +*/ + +/*! + \qmlproperty real TapHandler::timeHeld + \readonly + + The amount of time in seconds that a pressed point has been held, without + moving beyond the drag threshold. It will be updated at least once per + frame rendered, which enables rendering an animation showing the progress + towards an action which will be triggered by a long-press. It is also + possible to trigger one of a series of actions depending on how long the + press is held. + + A value of less than zero means no point is being held within this + handler's \l [QML] Item. +*/ + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquicktaphandler_p.h b/src/quick/handlers/qquicktaphandler_p.h new file mode 100644 index 0000000000..6504ec87f0 --- /dev/null +++ b/src/quick/handlers/qquicktaphandler_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTAPHANDLER_H +#define QQUICKTAPHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickitem.h" +#include "qevent.h" +#include "qquicksinglepointhandler_p.h" +#include <QtCore/qbasictimer.h> + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickTapHandler : public QQuickSinglePointHandler +{ + Q_OBJECT + Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged) + Q_PROPERTY(int tapCount READ tapCount NOTIFY tapCountChanged) + Q_PROPERTY(qreal timeHeld READ timeHeld NOTIFY timeHeldChanged) + Q_PROPERTY(qreal longPressThreshold READ longPressThreshold WRITE setLongPressThreshold NOTIFY longPressThresholdChanged) + Q_PROPERTY(GesturePolicy gesturePolicy READ gesturePolicy WRITE setGesturePolicy NOTIFY gesturePolicyChanged) + +public: + enum GesturePolicy { + DragThreshold, + WithinBounds, + ReleaseWithinBounds + }; + Q_ENUM(GesturePolicy) + + explicit QQuickTapHandler(QObject *parent = 0); + ~QQuickTapHandler(); + + bool isPressed() const { return m_pressed; } + + int tapCount() const { return m_tapCount; } + qreal timeHeld() const { return (m_holdTimer.isValid() ? m_holdTimer.elapsed() / 1000.0 : -1.0); } + + qreal longPressThreshold() const; + void setLongPressThreshold(qreal longPressThreshold); + + GesturePolicy gesturePolicy() const { return m_gesturePolicy; } + void setGesturePolicy(GesturePolicy gesturePolicy); + +Q_SIGNALS: + void pressedChanged(); + void tapCountChanged(); + void timeHeldChanged(); + void longPressThresholdChanged(); + void gesturePolicyChanged(); + void tapped(); + void longPressed(); + +protected: + void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) override; + void timerEvent(QTimerEvent *event) override; + bool wantsEventPoint(QQuickEventPoint *point) override; + void handleEventPoint(QQuickEventPoint *point) override; + +private: + void setPressed(bool press, bool cancel, QQuickEventPoint *point); + int longPressThresholdMilliseconds() const; + void connectPreRenderSignal(bool conn = true); + void updateTimeHeld(); + +private: + bool m_pressed; + GesturePolicy m_gesturePolicy; + int m_tapCount; + int m_longPressThreshold; + QBasicTimer m_longPressTimer; + QElapsedTimer m_holdTimer; + QPointF m_lastTapPos; + qreal m_lastTapTimestamp; + + static qreal m_multiTapInterval; + static int m_mouseMultiClickDistanceSquared; + static int m_touchMultiTapDistanceSquared; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickTapHandler) + +#endif // QQUICKTAPHANDLER_H |