diff options
Diffstat (limited to 'src/quick/handlers')
26 files changed, 4914 insertions, 0 deletions
diff --git a/src/quick/handlers/handlers.pri b/src/quick/handlers/handlers.pri new file mode 100644 index 0000000000..226cca22cb --- /dev/null +++ b/src/quick/handlers/handlers.pri @@ -0,0 +1,28 @@ +HEADERS += \ + $$PWD/qquickdraghandler_p.h \ + $$PWD/qquickhandlerpoint_p.h \ + $$PWD/qquickhoverhandler_p.h \ + $$PWD/qquickmultipointhandler_p.h \ + $$PWD/qquickpinchhandler_p.h \ + $$PWD/qquickpointerdevicehandler_p.h \ + $$PWD/qquickpointerdevicehandler_p_p.h \ + $$PWD/qquickpointerhandler_p.h \ + $$PWD/qquickpointerhandler_p_p.h \ + $$PWD/qquickpointhandler_p.h \ + $$PWD/qquicksinglepointhandler_p.h \ + $$PWD/qquicksinglepointhandler_p_p.h \ + $$PWD/qquicktaphandler_p.h \ + $$PWD/qquickdragaxis_p.h + +SOURCES += \ + $$PWD/qquickdraghandler.cpp \ + $$PWD/qquickhandlerpoint.cpp \ + $$PWD/qquickhoverhandler.cpp \ + $$PWD/qquickmultipointhandler.cpp \ + $$PWD/qquickpinchhandler.cpp \ + $$PWD/qquickpointerdevicehandler.cpp \ + $$PWD/qquickpointerhandler.cpp \ + $$PWD/qquickpointhandler.cpp \ + $$PWD/qquicksinglepointhandler.cpp \ + $$PWD/qquicktaphandler.cpp \ + $$PWD/qquickdragaxis.cpp diff --git a/src/quick/handlers/qquickdragaxis.cpp b/src/quick/handlers/qquickdragaxis.cpp new file mode 100644 index 0000000000..5efe19b2fe --- /dev/null +++ b/src/quick/handlers/qquickdragaxis.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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 "qquickdragaxis_p.h" +#include <limits> + +QQuickDragAxis::QQuickDragAxis() + : m_minimum(-std::numeric_limits<qreal>::max()) + , m_maximum(std::numeric_limits<qreal>::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(); +} + diff --git a/src/quick/handlers/qquickdragaxis_p.h b/src/quick/handlers/qquickdragaxis_p.h new file mode 100644 index 0000000000..2c2e0a426d --- /dev/null +++ b/src/quick/handlers/qquickdragaxis_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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 QQUICKDRAGAXIS_P_H +#define QQUICKDRAGAXIS_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 <QtCore/qobject.h> +#include <QtCore/qglobal.h> + +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; +}; + +#endif // QQUICKDRAGAXIS_P_H diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp new file mode 100644 index 0000000000..48f0599284 --- /dev/null +++ b/src/quick/handlers/qquickdraghandler.cpp @@ -0,0 +1,312 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + +static const qreal DragAngleToleranceDegrees = 10; + +Q_LOGGING_CATEGORY(lcDragHandler, "qt.quick.handler.drag") + +/*! + \qmltype DragHandler + \instantiates QQuickDragHandler + \inherits MultiPointHandler + \inqmlmodule QtQuick + \ingroup qtquick-input-handlers + \brief Handler for dragging. + + DragHandler is a handler that is used to interactively move an Item. + Like other Input Handlers, by default it is fully functional, and + manipulates its \l {PointerHandler::target} {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 {PointerHandler::target} {target}, then it handles events within the + bounds of the \l {PointerHandler::parent} {parent} Item but + manipulates the \c target Item instead: + + \snippet pointerHandlers/dragHandlerDifferentTarget.qml 0 + + A third way to use it is to set \l {PointerHandler::target} {target} to + \c null and react to property changes in some other way: + + \snippet pointerHandlers/dragHandlerNullTarget.qml 0 + + If minimumPointCount and maximumPointCount are set to values larger than 1, + the user will need to drag that many fingers in the same direction to start + dragging. A multi-finger drag gesture can be detected independently of both + a (default) single-finger DragHandler and a PinchHandler on the same Item, + and thus can be used to adjust some other feature independently of the + usual pinch behavior: for example adjust a tilt transformation, or adjust + some other numeric value, if the \c target is set to null. But if the + \c target is an Item, \c centroid is the point at which the drag begins and + to which the \c target will be moved (subject to constraints). + + At this time, drag-and-drop is not yet supported. + + \sa Drag, MouseArea +*/ + +QQuickDragHandler::QQuickDragHandler(QQuickItem *parent) + : QQuickMultiPointHandler(parent, 1, 1) +{ +} + +bool QQuickDragHandler::targetContainsCentroid() +{ + Q_ASSERT(parentItem() && target()); + return target()->contains(targetCentroidPosition()); +} + +QPointF QQuickDragHandler::targetCentroidPosition() +{ + QPointF pos = m_centroid.position(); + if (target() != parentItem()) + pos = parentItem()->mapToItem(target(), pos); + return pos; +} + +void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point) +{ + QQuickMultiPointHandler::onGrabChanged(grabber, transition, point); + if (grabber == this && transition == QQuickEventPoint::GrabExclusive) { + // In case the grab got handed over from another grabber, we might not get the Press. + if (!m_pressedInsideTarget) { + if (target()) + m_pressTargetPos = QPointF(target()->width(), target()->height()) / 2; + } else if (m_pressTargetPos.isNull()) { + if (target()) + m_pressTargetPos = targetCentroidPosition(); + } + } +} + +void QQuickDragHandler::onActiveChanged() +{ + QQuickMultiPointHandler::onActiveChanged(); + if (active()) { + if (auto parent = parentItem()) { + if (currentEvent()->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); + } + } else { + m_pressTargetPos = QPointF(); + m_pressedInsideTarget = false; + if (auto parent = parentItem()) { + parent->setKeepTouchGrab(false); + parent->setKeepMouseGrab(false); + } + } +} + +void QQuickDragHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + QQuickMultiPointHandler::handlePointerEventImpl(event); + event->setAccepted(true); + + if (active()) { + // Calculate drag delta, taking into account the axis enabled constraint + // i.e. if xAxis is not enabled, then ignore the horizontal component of the actual movement + QVector2D accumulatedDragDelta = QVector2D(m_centroid.scenePosition() - m_centroid.scenePressPosition()); + if (!m_xAxis.enabled()) + accumulatedDragDelta.setX(0); + if (!m_yAxis.enabled()) + accumulatedDragDelta.setY(0); + setTranslation(accumulatedDragDelta); + } else { + // Check that all points have been dragged past the drag threshold, + // to the extent that the constraints allow, + // and in approximately the same direction + qreal minAngle = 361; + qreal maxAngle = -361; + bool allOverThreshold = !event->isReleaseEvent(); + QVector <QQuickEventPoint *> chosenPoints; + + if (event->isPressEvent()) + m_pressedInsideTarget = target() && m_currentPoints.count() > 0; + + for (const QQuickHandlerPoint &p : m_currentPoints) { + if (!allOverThreshold) + break; + QQuickEventPoint *point = event->pointById(p.id()); + chosenPoints << point; + setPassiveGrab(point); + // Calculate drag delta, taking into account the axis enabled constraint + // i.e. if xAxis is not enabled, then ignore the horizontal component of the actual movement + QVector2D accumulatedDragDelta = QVector2D(point->scenePosition() - point->scenePressPosition()); + if (!m_xAxis.enabled()) { + // If horizontal dragging is disallowed, but the user is dragging + // mostly horizontally, then don't activate. + if (qAbs(accumulatedDragDelta.x()) > qAbs(accumulatedDragDelta.y())) + accumulatedDragDelta.setY(0); + accumulatedDragDelta.setX(0); + } + if (!m_yAxis.enabled()) { + // If vertical dragging is disallowed, but the user is dragging + // mostly vertically, then don't activate. + if (qAbs(accumulatedDragDelta.y()) > qAbs(accumulatedDragDelta.x())) + accumulatedDragDelta.setX(0); + accumulatedDragDelta.setY(0); + } + qreal angle = std::atan2(accumulatedDragDelta.y(), accumulatedDragDelta.x()) * 180 / M_PI; + bool overThreshold = QQuickWindowPrivate::dragOverThreshold(accumulatedDragDelta); + qCDebug(lcDragHandler) << "movement" << accumulatedDragDelta << "angle" << angle << "of point" << point + << "pressed @" << point->scenePressPosition() << "over threshold?" << overThreshold; + minAngle = qMin(angle, minAngle); + maxAngle = qMax(angle, maxAngle); + if (allOverThreshold && !overThreshold) + allOverThreshold = false; + + if (event->isPressEvent()) { + // m_pressedInsideTarget should stay true iff ALL points in which DragHandler is interested + // have been pressed inside the target() Item. (E.g. in a Slider the parent might be the + // whole control while the target is just the knob.) + if (target()) { + const QPointF localPressPos = target()->mapFromScene(point->scenePressPosition()); + m_pressedInsideTarget &= target()->contains(localPressPos); + m_pressTargetPos = targetCentroidPosition(); + } + // QQuickWindowPrivate::deliverToPassiveGrabbers() skips subsequent delivery if the event is filtered. + // (That affects behavior for mouse but not for touch, because Flickable only handles mouse.) + // So we have to compensate by accepting the event here to avoid any parent Flickable from + // getting the event via direct delivery and grabbing too soon. + point->setAccepted(event->asPointerMouseEvent()); // stop propagation iff it's a mouse event + } + } + if (allOverThreshold) { + qreal angleDiff = maxAngle - minAngle; + if (angleDiff > 180) + angleDiff = 360 - angleDiff; + qCDebug(lcDragHandler) << "angle min" << minAngle << "max" << maxAngle << "range" << angleDiff; + if (angleDiff < DragAngleToleranceDegrees && grabPoints(chosenPoints)) + setActive(true); + } + } + if (active() && target() && target()->parentItem()) { + const QPointF newTargetTopLeft = targetCentroidPosition() - m_pressTargetPos; + const QPointF xformOrigin = target()->transformOriginPoint(); + const QPointF targetXformOrigin = newTargetTopLeft + xformOrigin; + QPointF pos = target()->parentItem()->mapFromItem(target(), targetXformOrigin); + pos -= xformOrigin; + QPointF targetItemPos = target()->position(); + if (!m_xAxis.enabled()) + pos.setX(targetItemPos.x()); + if (!m_yAxis.enabled()) + pos.setY(targetItemPos.y()); + enforceAxisConstraints(&pos); + moveTarget(pos); + } +} + +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::setTranslation(const QVector2D &trans) +{ + if (trans == m_translation) // fuzzy compare? + return; + m_translation = trans; + emit translationChanged(); +} + +/*! + \qmlpropertygroup QtQuick::DragHandler::xAxis + \qmlproperty real QtQuick::DragHandler::xAxis.minimum + \qmlproperty real QtQuick::DragHandler::xAxis.maximum + \qmlproperty bool QtQuick::DragHandler::xAxis.enabled + + \c xAxis controls the constraints for horizontal dragging. + + \c minimum is the minimum acceptable value of \l {Item::x}{x} to be + applied to the \l {PointerHandler::target} {target}. + \c maximum is the maximum acceptable value of \l {Item::x}{x} to be + applied to the \l {PointerHandler::target} {target}. + If \c enabled is true, horizontal dragging is allowed. + */ + +/*! + \qmlpropertygroup QtQuick::DragHandler::yAxis + \qmlproperty real QtQuick::DragHandler::yAxis.minimum + \qmlproperty real QtQuick::DragHandler::yAxis.maximum + \qmlproperty bool QtQuick::DragHandler::yAxis.enabled + + \c yAxis controls the constraints for vertical dragging. + + \c minimum is the minimum acceptable value of \l {Item::y}{y} to be + applied to the \l {PointerHandler::target} {target}. + \c maximum is the maximum acceptable value of \l {Item::y}{y} to be + applied to the \l {PointerHandler::target} {target}. + If \c enabled is true, vertical dragging is allowed. + */ + +/*! + \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..387a81eb43 --- /dev/null +++ b/src/quick/handlers/qquickdraghandler_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qquickmultipointhandler_p.h" +#include "qquickdragaxis_p.h" + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickDragHandler : public QQuickMultiPointHandler +{ + 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(QQuickItem *parent = nullptr); + + void handlePointerEventImpl(QQuickPointerEvent *event) override; + + QQuickDragAxis *xAxis() { return &m_xAxis; } + QQuickDragAxis *yAxis() { return &m_yAxis; } + + QVector2D translation() const { return m_translation; } + void setTranslation(const QVector2D &trans); + + void enforceConstraints(); + +Q_SIGNALS: + void translationChanged(); + +protected: + void onActiveChanged() override; + void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point) override; + +private: + void ungrab(); + void enforceAxisConstraints(QPointF *localPos); + bool targetContainsCentroid(); + QPointF targetCentroidPosition(); + +private: + QPointF m_pressTargetPos; // We must also store the local targetPos, because we cannot deduce + // the press target pos from the scene pos in case there was e.g a + // flick in one of the ancestors during the drag. + QVector2D m_translation; + + QQuickDragAxis m_xAxis; + QQuickDragAxis m_yAxis; + bool m_pressedInsideTarget = false; + + friend class QQuickDragAxis; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickDragHandler) +QML_DECLARE_TYPE(QQuickDragAxis) + +#endif // QQUICKDRAGHANDLER_H diff --git a/src/quick/handlers/qquickhandlerpoint.cpp b/src/quick/handlers/qquickhandlerpoint.cpp new file mode 100644 index 0000000000..de21537f27 --- /dev/null +++ b/src/quick/handlers/qquickhandlerpoint.cpp @@ -0,0 +1,350 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qquickhandlerpoint_p.h" +#include "private/qquickevents_p_p.h" + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(DBG_TOUCH_TARGET) + +/*! + \qmltype HandlerPoint + \instantiates QQuickHandlerPoint + \inqmlmodule QtQuick + \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() +{} + +void QQuickHandlerPoint::localize(QQuickItem *item) +{ + m_pressPosition = item->mapFromScene(m_scenePressPosition); +} + +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; + m_pressedModifiers = Qt::NoModifier; +} + +void QQuickHandlerPoint::reset(const QQuickEventPoint *point) +{ + m_id = point->pointId(); + const QQuickPointerEvent *event = point->pointerEvent(); + switch (point->state()) { + case QQuickEventPoint::Pressed: + m_pressPosition = point->position(); + m_scenePressPosition = point->scenePosition(); + m_pressedButtons = event->buttons(); + break; + case QQuickEventPoint::Released: + if (event->buttons() == Qt::NoButton) { + reset(); + return; + } + break; + default: + break; + } + m_scenePressPosition = point->scenePressPosition(); + m_pressedButtons = event->buttons(); + m_pressedModifiers = event->modifiers(); + if (event->asPointerTouchEvent()) { + const QQuickEventTouchPoint *tp = static_cast<const QQuickEventTouchPoint *>(point); + m_uniqueId = tp->uniqueId(); + m_rotation = tp->rotation(); + m_pressure = tp->pressure(); + m_ellipseDiameters = tp->ellipseDiameters(); + } else if (event->asPointerTabletEvent()) { + // TODO + } else { + m_uniqueId = event->device()->uniqueId(); + m_rotation = 0; + m_pressure = event->buttons() ? 1 : 0; + m_ellipseDiameters = QSizeF(); + } + m_position = point->position(); + m_scenePosition = point->scenePosition(); + if (point->state() == QQuickEventPoint::Updated) + m_velocity = point->velocity(); +} + +void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points) +{ + if (points.isEmpty()) { + qWarning("reset: no points"); + return; + } + if (points.count() == 1) { + *this = points.first(); // copy all values + return; + } + // all points are required to be from the same event + QPointF posSum; + QPointF scenePosSum; + QPointF pressPosSum; + QPointF scenePressPosSum; + QVector2D velocitySum; + qreal pressureSum = 0; + QSizeF ellipseDiameterSum; + for (const QQuickHandlerPoint &point : points) { + posSum += point.position(); + scenePosSum += point.scenePosition(); + pressPosSum += point.pressPosition(); + scenePressPosSum += point.scenePressPosition(); + velocitySum += point.velocity(); + pressureSum += point.pressure(); + ellipseDiameterSum += point.ellipseDiameters(); + } + m_id = 0; + m_uniqueId = QPointingDeviceUniqueId(); + // all points are required to be from the same event, so pressed buttons and modifiers should be the same + m_pressedButtons = points.first().pressedButtons(); + m_pressedModifiers = points.first().modifiers(); + m_position = posSum / points.size(); + m_scenePosition = scenePosSum / points.size(); + m_pressPosition = pressPosSum / points.size(); + m_scenePressPosition = scenePressPosSum / points.size(); + m_velocity = velocitySum / points.size(); + m_rotation = 0; // averaging the rotations of all the points isn't very sensible + m_pressure = pressureSum / points.size(); + m_ellipseDiameters = ellipseDiameterSum / points.size(); +} + +/*! + \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 {PointerHandler::parent} {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 {PointerHandler::parent} {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 enumeration QtQuick::HandlerPoint::pressedButtons + \brief Which mouse or stylus buttons are currently pressed + + \sa MouseArea::pressedButtons +*/ + +/*! + \readonly + \qmlproperty enumeration QtQuick::HandlerPoint::modifiers + \brief Which modifier keys are currently pressed + + This property holds the keyboard modifiers that were pressed at the time + the event occurred. +*/ + +/*! + \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::EventTouchPoint::ellipseDiameters, QtQuick::TouchPoint::ellipseDiameters, QTouchEvent::TouchPoint::ellipseDiameters +*/ + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickhandlerpoint_p.h b/src/quick/handlers/qquickhandlerpoint_p.h new file mode 100644 index 0000000000..44fd830af2 --- /dev/null +++ b/src/quick/handlers/qquickhandlerpoint_p.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 QQUICKHANDLERPOINT_H +#define QQUICKHANDLERPOINT_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 QQuickMultiPointHandler; +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(Qt::KeyboardModifiers modifiers READ modifiers) + 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; } + Qt::KeyboardModifiers modifiers() const { return m_pressedModifiers; } + 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; } + void localize(QQuickItem *item); + + void reset(); + void reset(const QQuickEventPoint *point); + void reset(const QVector<QQuickHandlerPoint> &points); + +private: + int m_id = 0; + QPointingDeviceUniqueId m_uniqueId; + Qt::MouseButtons m_pressedButtons = Qt::NoButton; + Qt::KeyboardModifiers m_pressedModifiers = Qt::NoModifier; + QPointF m_position; + QPointF m_scenePosition; + QPointF m_pressPosition; + QPointF m_scenePressPosition; + QPointF m_sceneGrabPosition; + QVector2D m_velocity; + qreal m_rotation = 0; + qreal m_pressure = 0; + QSizeF m_ellipseDiameters; + friend class QQuickMultiPointHandler; + friend class QQuickSinglePointHandler; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickHandlerPoint) + +#endif // QQUICKHANDLERPOINT_H diff --git a/src/quick/handlers/qquickhoverhandler.cpp b/src/quick/handlers/qquickhoverhandler.cpp new file mode 100644 index 0000000000..61955cad03 --- /dev/null +++ b/src/quick/handlers/qquickhoverhandler.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qquickhoverhandler_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcHoverHandler, "qt.quick.handler.hover") + +/*! + \qmltype HoverHandler + \instantiates QQuickHoverHandler + \inqmlmodule QtQuick + \ingroup qtquick-input-handlers + \brief Handler for mouse and tablet hover. + + HoverHandler detects a hovering cursor. Since touchscreens don't generally + offer hover events, in practice it detects a hovering mouse or tablet stylus. + + \sa MouseArea +*/ + +QQuickHoverHandler::QQuickHoverHandler(QQuickItem *parent) + : QQuickSinglePointHandler(parent) +{ + // Rule out the touchscreen for now (can be overridden in QML in case a hover-detecting touchscreen exists) + setAcceptedDevices(static_cast<QQuickPointerDevice::DeviceType>( + static_cast<int>(QQuickPointerDevice::AllDevices) ^ static_cast<int>(QQuickPointerDevice::TouchScreen))); +} + +QQuickHoverHandler::~QQuickHoverHandler() +{ + if (auto parent = parentItem()) + QQuickItemPrivate::get(parent)->setHasHoverInChild(false); +} + +void QQuickHoverHandler::componentComplete() +{ + parentItem()->setAcceptHoverEvents(true); + QQuickItemPrivate::get(parentItem())->setHasHoverInChild(true); +} + +bool QQuickHoverHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + QQuickEventPoint *point = event->point(0); + if (QQuickPointerDeviceHandler::wantsPointerEvent(event) && wantsEventPoint(point) && parentContains(point)) { + // assume this is a mouse event, so there's only one point + setPointId(point->pointId()); + return true; + } + setHovered(false); + return false; +} + +void QQuickHoverHandler::handleEventPoint(QQuickEventPoint *point) +{ + setHovered(true); + setPassiveGrab(point); +} + +void QQuickHoverHandler::setHovered(bool hovered) +{ + if (m_hovered != hovered) { + qCDebug(lcHoverHandler) << objectName() << "hovered" << m_hovered << "->" << hovered; + m_hovered = hovered; + emit hoveredChanged(); + } +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickhoverhandler_p.h b/src/quick/handlers/qquickhoverhandler_p.h new file mode 100644 index 0000000000..1ee2aeb7e6 --- /dev/null +++ b/src/quick/handlers/qquickhoverhandler_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 QQUICKHOVERHANDLER_H +#define QQUICKHOVERHANDLER_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 QQuickHoverHandler : public QQuickSinglePointHandler +{ + Q_OBJECT + Q_PROPERTY(bool hovered READ isHovered NOTIFY hoveredChanged) + +public: + explicit QQuickHoverHandler(QQuickItem *parent = nullptr); + ~QQuickHoverHandler(); + + bool isHovered() const { return m_hovered; } + +Q_SIGNALS: + void hoveredChanged(); + +protected: + void componentComplete() override; + bool wantsPointerEvent(QQuickPointerEvent *event) override; + void handleEventPoint(QQuickEventPoint *point) override; + +private: + void setHovered(bool hovered); + +private: + bool m_hovered = false; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickHoverHandler) + +#endif // QQUICKHOVERHANDLER_H diff --git a/src/quick/handlers/qquickmultipointhandler.cpp b/src/quick/handlers/qquickmultipointhandler.cpp new file mode 100644 index 0000000000..baa68e5e53 --- /dev/null +++ b/src/quick/handlers/qquickmultipointhandler.cpp @@ -0,0 +1,347 @@ +/**************************************************************************** +** +** 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 QtQuick + \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(QQuickItem *parent, int minimumPointCount, int maximumPointCount) + : QQuickPointerDeviceHandler(parent) + , m_minimumPointCount(minimumPointCount) + , m_maximumPointCount(maximumPointCount) +{ +} + +bool QQuickMultiPointHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) + return false; + +#if QT_CONFIG(gestures) + if (event->asPointerNativeGestureEvent()) + return true; +#endif + + if (event->asPointerScrollEvent()) + return false; + + // If points were pressed or released within parentItem, reset stored state + // and check eligible points again. This class of handlers is intended to + // handle a specific number of points, so a differing number of points will + // usually result in different behavior. But otherwise if the currentPoints + // are all still there in the event, we're good to go (do not reset + // m_currentPoints, because we don't want to lose the pressPosition, and do + // not want to reshuffle the order either). + const QVector<QQuickEventPoint *> candidatePoints = eligiblePoints(event); + if (candidatePoints.count() != m_currentPoints.count()) { + m_currentPoints.clear(); + if (active()) { + setActive(false); + m_centroid.reset(); + emit centroidChanged(); + } + } else if (hasCurrentPoints(event)) { + return true; + } + + const bool ret = (candidatePoints.size() >= minimumPointCount() && candidatePoints.size() <= maximumPointCount()); + if (ret) { + const int c = candidatePoints.count(); + m_currentPoints.resize(c); + for (int i = 0; i < c; ++i) { + m_currentPoints[i].reset(candidatePoints[i]); + m_currentPoints[i].localize(parentItem()); + } + } else { + m_currentPoints.clear(); + } + return ret; +} + +void QQuickMultiPointHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + QQuickPointerHandler::handlePointerEventImpl(event); + // event's points can be reordered since the previous event, which is why m_currentPoints + // is _not_ a shallow copy of the QQuickPointerTouchEvent::m_touchPoints vector. + // So we have to update our m_currentPoints instances based on the given event. + for (QQuickHandlerPoint &p : m_currentPoints) { + const QQuickEventPoint *ep = event->pointById(p.id()); + if (ep) + p.reset(ep); + } + QPointF sceneGrabPos = m_centroid.sceneGrabPosition(); + m_centroid.reset(m_currentPoints); + m_centroid.m_sceneGrabPosition = sceneGrabPos; // preserve as it was + emit centroidChanged(); +} + +void QQuickMultiPointHandler::onActiveChanged() +{ + if (active()) { + m_centroid.m_sceneGrabPosition = m_centroid.m_scenePosition; + } else { + // Don't call m_centroid.reset() here, because in a QML onActiveChanged + // callback, we'd like to see what the position _was_, what the velocity _was_, etc. + // (having them undefined is not useful) + // But pressedButtons and pressedModifiers are meant to be more real-time than those + // (which seems a bit inconsistent, from one side). + m_centroid.m_pressedButtons = Qt::NoButton; + m_centroid.m_pressedModifiers = Qt::NoModifier; + } +} + +void QQuickMultiPointHandler::onGrabChanged(QQuickPointerHandler *, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *) +{ + // If another handler or item takes over this set of points, assume it has + // decided that it's the better fit for them. Don't immediately re-grab + // at the next opportunity. This should help to avoid grab cycles + // (e.g. between DragHandler and PinchHandler). + if (transition == QQuickEventPoint::UngrabExclusive || transition == QQuickEventPoint::CancelGrabExclusive) + m_currentPoints.clear(); +} + +QVector<QQuickEventPoint *> QQuickMultiPointHandler::eligiblePoints(QQuickPointerEvent *event) +{ + QVector<QQuickEventPoint *> ret; + int c = event->pointCount(); + // If one or more points are newly pressed or released, all non-released points are candidates for this handler. + // In other cases however, check whether it would be OK to steal the grab if the handler chooses to do that. + bool stealingAllowed = event->isPressEvent() || event->isReleaseEvent(); + for (int i = 0; i < c; ++i) { + QQuickEventPoint *p = event->point(i); + if (QQuickPointerMouseEvent *me = event->asPointerMouseEvent()) { + if (me->buttons() == Qt::NoButton) + continue; + } + if (!stealingAllowed) { + QObject *exclusiveGrabber = p->exclusiveGrabber(); + if (exclusiveGrabber && exclusiveGrabber != this && !canGrab(p)) + continue; + } + if (p->state() != QQuickEventPoint::Released && wantsEventPoint(p)) + 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 Input 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 Input 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(); +} + +bool QQuickMultiPointHandler::hasCurrentPoints(QQuickPointerEvent *event) +{ + if (event->pointCount() < m_currentPoints.size() || m_currentPoints.size() == 0) + return false; + // TODO optimize: either ensure the points are sorted, + // or use std::equal with a predicate + for (const QQuickHandlerPoint &p : qAsConst(m_currentPoints)) { + const QQuickEventPoint *ep = event->pointById(p.id()); + if (!ep) + return false; + if (ep->state() == QQuickEventPoint::Released) + return false; + } + return true; +} + +qreal QQuickMultiPointHandler::averageTouchPointDistance(const QPointF &ref) +{ + qreal ret = 0; + if (Q_UNLIKELY(m_currentPoints.size() == 0)) + return ret; + for (const QQuickHandlerPoint &p : m_currentPoints) + ret += QVector2D(p.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 (const QQuickHandlerPoint &p : m_currentPoints) + ret += QVector2D(p.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 (const QQuickHandlerPoint &p : m_currentPoints) { + qreal angle = QLineF(ref, p.scenePosition()).angle(); + angles.append(PointData(p.id(), -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) +{ + if (points.isEmpty()) + return false; + bool allowed = true; + for (QQuickEventPoint* point : points) { + if (point->exclusiveGrabber() != this && !canGrab(point)) { + allowed = false; + break; + } + } + if (allowed) { + for (QQuickEventPoint* point : points) + setExclusiveGrab(point); + } + return allowed; +} + +void QQuickMultiPointHandler::moveTarget(QPointF pos) +{ + target()->setPosition(pos); + m_centroid.m_position = target()->mapFromScene(m_centroid.m_scenePosition); +} + +/*! + \readonly + \qmlproperty QtQuick::HandlerPoint QtQuick::MultiPointHandler::centroid + + A point exactly in the middle of the currently-pressed touch points. + If only one point is pressed, it's the same as that point. + A handler that has a \l target will normally transform it relative to this point. +*/ + +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..94142013cc --- /dev/null +++ b/src/quick/handlers/qquickmultipointhandler_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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 "qquickhandlerpoint_p.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(QQuickHandlerPoint centroid READ centroid NOTIFY centroidChanged) + +public: + explicit QQuickMultiPointHandler(QQuickItem *parent = nullptr, int minimumPointCount = 2, int maximumPointCount = -1); + + 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); + + QQuickHandlerPoint centroid() const { return m_centroid; } + +signals: + void minimumPointCountChanged(); + void maximumPointCountChanged(); + void marginChanged(); + void centroidChanged(); + +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; + void handlePointerEventImpl(QQuickPointerEvent *event) override; + void onActiveChanged() override; + void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point) override; + bool hasCurrentPoints(QQuickPointerEvent *event); + QVector<QQuickEventPoint *> eligiblePoints(QQuickPointerEvent *event); + 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); + void moveTarget(QPointF pos); + +protected: + QVector<QQuickHandlerPoint> m_currentPoints; + QQuickHandlerPoint m_centroid; + int m_minimumPointCount; + int m_maximumPointCount; +}; + +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..9ae2116d39 --- /dev/null +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -0,0 +1,563 @@ +/**************************************************************************** +** +** 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 <QtQml/qqmlinfo.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> +#include <math.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch") + +/*! + \qmltype PinchHandler + \instantiates QQuickPinchHandler + \inherits MultiPointHandler + \inqmlmodule QtQuick + \ingroup qtquick-input-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 Input 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(QQuickItem *parent) + : QQuickMultiPointHandler(parent, 2) +{ +} + +/*! + \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 (qFuzzyCompare(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 (qFuzzyCompare(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 (qFuzzyCompare(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 (qFuzzyCompare(m_maximumRotation, maximumRotation)) + return; + + m_maximumRotation = maximumRotation; + emit maximumRotationChanged(); +} + +#if QT_DEPRECATED_SINCE(5, 12) +void QQuickPinchHandler::warnAboutMinMaxDeprecated() const +{ + qmlWarning(this) << "min and max constraints are now part of the xAxis and yAxis properties"; +} + +void QQuickPinchHandler::setMinimumX(qreal minX) +{ + warnAboutMinMaxDeprecated(); + if (qFuzzyCompare(m_minimumX, minX)) + return; + m_minimumX = minX; + emit minimumXChanged(); +} + +void QQuickPinchHandler::setMaximumX(qreal maxX) +{ + warnAboutMinMaxDeprecated(); + if (qFuzzyCompare(m_maximumX, maxX)) + return; + m_maximumX = maxX; + emit maximumXChanged(); +} + +void QQuickPinchHandler::setMinimumY(qreal minY) +{ + warnAboutMinMaxDeprecated(); + if (qFuzzyCompare(m_minimumY, minY)) + return; + m_minimumY = minY; + emit minimumYChanged(); +} + +void QQuickPinchHandler::setMaximumY(qreal maxY) +{ + warnAboutMinMaxDeprecated(); + if (qFuzzyCompare(m_maximumY, maxY)) + return; + m_maximumY = maxY; + emit maximumYChanged(); +} +#endif + +bool QQuickPinchHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickMultiPointHandler::wantsPointerEvent(event)) + return false; + +#if QT_CONFIG(gestures) + if (const auto gesture = event->asPointerNativeGestureEvent()) { + if (minimumPointCount() == 2) { + switch (gesture->type()) { + case Qt::BeginNativeGesture: + case Qt::EndNativeGesture: + case Qt::ZoomNativeGesture: + case Qt::RotateNativeGesture: + return parentContains(event->point(0)); + default: + return false; + } + } else { + return false; + } + } +#endif + + return true; +} + +/*! + \qmlpropertygroup QtQuick::PinchHandler::xAxis + \qmlproperty real QtQuick::PinchHandler::xAxis.minimum + \qmlproperty real QtQuick::PinchHandler::xAxis.maximum + \qmlproperty bool QtQuick::PinchHandler::xAxis.enabled + + \c xAxis controls the constraints for horizontal translation of the \l target item. + + \c minimum is the minimum acceptable x coordinate of the translation. + \c maximum is the maximum acceptable x coordinate of the translation. + If \c enabled is true, horizontal dragging is allowed. + */ + +/*! + \qmlpropertygroup QtQuick::PinchHandler::yAxis + \qmlproperty real QtQuick::PinchHandler::yAxis.minimum + \qmlproperty real QtQuick::PinchHandler::yAxis.maximum + \qmlproperty bool QtQuick::PinchHandler::yAxis.enabled + + \c yAxis controls the constraints for vertical translation of the \l target item. + + \c minimum is the minimum acceptable y coordinate of the translation. + \c maximum is the maximum acceptable y coordinate of the translation. + If \c enabled is true, vertical dragging is allowed. + */ + +/*! + \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() +{ + QQuickMultiPointHandler::onActiveChanged(); + if (active()) { + m_startMatrix = QMatrix4x4(); + m_startAngles = angles(m_centroid.sceneGrabPosition()); + m_startDistance = averageTouchPointDistance(m_centroid.sceneGrabPosition()); + 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(float(t->x()), float(t->y())); + m_startMatrix.translate(xformOrigin); + m_startMatrix.scale(float(m_startScale)); + m_startMatrix.rotate(float(m_startRotation), 0, 0, -1); + m_startMatrix.translate(-xformOrigin); + } else { + m_startScale = m_accumulatedScale; + 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 (const QQuickHandlerPoint &p : m_currentPoints) + qCDebug(lcPinchHandler) << hex << p.id() << p.sceneGrabPosition() << "->" << p.scenePosition(); + } + QQuickMultiPointHandler::handlePointerEventImpl(event); + + qreal dist = 0; +#if QT_CONFIG(gestures) + if (const auto gesture = event->asPointerNativeGestureEvent()) { + m_centroid.reset(event->point(0)); + switch (gesture->type()) { + case Qt::EndNativeGesture: + m_activeScale = 1; + m_activeRotation = 0; + m_activeTranslation = QVector2D(); + m_centroid.reset(); + 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()) { + setActive(true); + // 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_activeTranslation = QVector2D(); + } + } else +#endif // QT_CONFIG(gestures) + { + const bool containsReleasedPoints = event->isReleaseEvent(); + QVector<QQuickEventPoint *> chosenPoints; + for (const QQuickHandlerPoint &p : m_currentPoints) { + QQuickEventPoint *ep = event->pointById(p.id()); + chosenPoints << ep; + } + if (!active()) { + // Verify that at least one of the points has moved beyond threshold needed to activate the handler + int numberOfPointsDraggedOverThreshold = 0; + QVector2D accumulatedDrag; + const QVector2D currentCentroid(m_centroid.scenePosition()); + const QVector2D pressCentroid(m_centroid.scenePressPosition()); + + QStyleHints *styleHints = QGuiApplication::styleHints(); + const int dragThreshold = styleHints->startDragDistance(); + const int dragThresholdSquared = dragThreshold * dragThreshold; + + double accumulatedCentroidDistance = 0; // Used to detect scale + if (event->isPressEvent()) + m_accumulatedStartCentroidDistance = 0; // Used to detect scale + + float accumulatedMovementMagnitude = 0; + + for (QQuickEventPoint *point : qAsConst(chosenPoints)) { + if (!containsReleasedPoints) { + accumulatedDrag += QVector2D(point->scenePressPosition() - point->scenePosition()); + /* + In order to detect a drag, we want to check if all points have moved more or + less in the same direction. + + We then take each point, and convert the point to a local coordinate system where + the centroid is the origin. This is done both for the press positions and the + current positions. We will then have two positions: + + - pressCentroidRelativePosition + is the start point relative to the press centroid + - currentCentroidRelativePosition + is the current point relative to the current centroid + + If those two points are far enough apart, it might not be considered as a drag + anymore. (Note that the threshold will matched to the average of the relative + movement of all the points). Therefore, a big relative movement will make a big + contribution to the average relative movement. + + The algorithm then can be described as: + For each point: + - Calculate vector pressCentroidRelativePosition (from the press centroid to the press position) + - Calculate vector currentCentroidRelativePosition (from the current centroid to the current position) + - Calculate the relative movement vector: + + centroidRelativeMovement = currentCentroidRelativePosition - pressCentroidRelativePosition + + and measure its magnitude. Add the magnitude to the accumulatedMovementMagnitude. + + Finally, if the accumulatedMovementMagnitude is below some threshold, it means + that the points were stationary or they were moved in parallel (e.g. the hand + was moved, but the relative position between each finger remained very much + the same). This is then used to rule out if there is a rotation or scale. + */ + QVector2D pressCentroidRelativePosition = QVector2D(point->scenePosition()) - currentCentroid; + QVector2D currentCentroidRelativePosition = QVector2D(point->scenePressPosition()) - pressCentroid; + QVector2D centroidRelativeMovement = currentCentroidRelativePosition - pressCentroidRelativePosition; + accumulatedMovementMagnitude += centroidRelativeMovement.length(); + + accumulatedCentroidDistance += qreal(pressCentroidRelativePosition.length()); + if (event->isPressEvent()) + m_accumulatedStartCentroidDistance += qreal((QVector2D(point->scenePressPosition()) - pressCentroid).length()); + } else { + setPassiveGrab(point); + } + if (point->state() == QQuickEventPoint::Pressed) { + point->setAccepted(false); // don't stop propagation + setPassiveGrab(point); + } + if (QQuickWindowPrivate::dragOverThreshold(point)) + ++numberOfPointsDraggedOverThreshold; + } + + const bool requiredNumberOfPointsDraggedOverThreshold = numberOfPointsDraggedOverThreshold >= minimumPointCount() && numberOfPointsDraggedOverThreshold <= maximumPointCount(); + accumulatedMovementMagnitude /= m_currentPoints.count(); + + QVector2D avgDrag = accumulatedDrag / m_currentPoints.count(); + if (!xAxis()->enabled()) + avgDrag.setX(0); + if (!yAxis()->enabled()) + avgDrag.setY(0); + + const qreal centroidMovementDelta = qreal((currentCentroid - pressCentroid).length()); + + qreal distanceToCentroidDelta = qAbs(accumulatedCentroidDistance - m_accumulatedStartCentroidDistance); // Used to detect scale + if (numberOfPointsDraggedOverThreshold >= 1) { + if (requiredNumberOfPointsDraggedOverThreshold && avgDrag.lengthSquared() >= dragThresholdSquared && accumulatedMovementMagnitude < dragThreshold) { + // Drag + if (grabPoints(chosenPoints)) + setActive(true); + } else if (distanceToCentroidDelta > dragThreshold) { // all points should in accumulation have been moved beyond threshold (?) + // Scale + if (grabPoints(chosenPoints)) + setActive(true); + } else if (distanceToCentroidDelta < dragThreshold && (centroidMovementDelta < dragThreshold)) { + // Rotate + // Since it wasn't a scale and if we exceeded the dragthreshold, and the + // centroid didn't moved much, the points must have been moved around the centroid. + if (grabPoints(chosenPoints)) + setActive(true); + } + } + if (!active()) + return; + } + + // 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.scenePosition()); + 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.scenePosition()); + const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles); + m_activeRotation += angleDelta; + m_startAngles = std::move(newAngles); + + if (!containsReleasedPoints) + acceptPoints(chosenPoints); + } + + 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 + m_accumulatedScale = m_startScale * m_activeScale; + + if (target() && target()->parentItem()) { + const QPointF centroidParentPos = target()->parentItem()->mapFromScene(m_centroid.scenePosition()); + // 3. Drag/translate + const QPointF centroidStartParentPos = target()->parentItem()->mapFromScene(m_centroid.sceneGrabPosition()); + 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(float(m_activeRotation), 0, 0, 1); + mat.scale(float(m_activeScale)); + mat.translate(-centroidParentVector); + mat.translate(QVector3D(m_activeTranslation)); + + mat = mat * m_startMatrix; + + QPointF xformOriginPoint = target()->transformOriginPoint(); + QPointF pos = mat * xformOriginPoint; + pos -= xformOriginPoint; + + if (xAxis()->enabled()) + pos.setX(qBound(xAxis()->minimum(), pos.x(), xAxis()->maximum())); + else + pos.rx() -= qreal(m_activeTranslation.x()); + if (yAxis()->enabled()) + pos.setY(qBound(yAxis()->minimum(), pos.y(), yAxis()->maximum())); + else + pos.ry() -= qreal(m_activeTranslation.y()); + + target()->setPosition(pos); + target()->setRotation(rotation); + target()->setScale(m_accumulatedScale); + } else { + m_activeTranslation = QVector2D(m_centroid.scenePosition() - m_centroid.scenePressPosition()); + } + + qCDebug(lcPinchHandler) << "centroid" << m_centroid.scenePressPosition() << "->" << m_centroid.scenePosition() + << ", distance" << m_startDistance << "->" << dist + << ", startScale" << m_startScale << "->" << m_accumulatedScale + << ", activeRotation" << m_activeRotation + << ", rotation" << rotation + << " from " << event->device()->type(); + + emit updated(); +} + +/*! + \readonly + \qmlproperty QtQuick::HandlerPoint QtQuick::PinchHandler::centroid + + A point exactly in the middle of the currently-pressed touch points. + The \l target will be rotated around this point. +*/ + +/*! + \readonly + \qmlproperty real QtQuick::PinchHandler::scale + + The scale factor that will automatically be set on the \l target if it is not null. + Otherwise, bindings can be used to do arbitrary things with this value. + While the pinch gesture is being performed, it is continuously multiplied by + \l activeScale; after the gesture ends, it stays the same; and when the next + pinch gesture begins, it begins to be multiplied by activeScale again. +*/ + +/*! + \readonly + \qmlproperty real QtQuick::PinchHandler::activeScale + + The scale factor while the pinch gesture is being performed. + 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, its \l {Item::scale}{scale} will be automatically + multiplied by this value. + 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..1afc028758 --- /dev/null +++ b/src/quick/handlers/qquickpinchhandler_p.h @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** 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> +#include "qquickdragaxis_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(qreal scale READ scale NOTIFY updated) + Q_PROPERTY(qreal activeScale READ activeScale NOTIFY updated) + Q_PROPERTY(qreal rotation READ rotation NOTIFY updated) + Q_PROPERTY(QVector2D translation READ translation NOTIFY updated) +#if QT_DEPRECATED_SINCE(5, 12) + Q_PROPERTY(qreal minimumX READ minimumX WRITE setMinimumX NOTIFY minimumXChanged) // ### Qt 6: remove + Q_PROPERTY(qreal maximumX READ maximumX WRITE setMaximumX NOTIFY maximumXChanged) // ### Qt 6: remove + Q_PROPERTY(qreal minimumY READ minimumY WRITE setMinimumY NOTIFY minimumYChanged) // ### Qt 6: remove + Q_PROPERTY(qreal maximumY READ maximumY WRITE setMaximumY NOTIFY maximumYChanged) // ### Qt 6: remove +#endif + Q_PROPERTY(QQuickDragAxis * xAxis READ xAxis CONSTANT) + Q_PROPERTY(QQuickDragAxis * yAxis READ yAxis CONSTANT) + +public: + explicit QQuickPinchHandler(QQuickItem *parent = nullptr); + + 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); + + QVector2D translation() const { return m_activeTranslation; } + qreal scale() const { return m_accumulatedScale; } + qreal activeScale() const { return m_activeScale; } + qreal rotation() const { return m_activeRotation; } + +#if QT_DEPRECATED_SINCE(5, 12) + void warnAboutMinMaxDeprecated() const; + qreal minimumX() const { warnAboutMinMaxDeprecated(); return m_minimumX; } + void setMinimumX(qreal minX); + qreal maximumX() const { warnAboutMinMaxDeprecated(); return m_maximumX; } + void setMaximumX(qreal maxX); + qreal minimumY() const { warnAboutMinMaxDeprecated(); return m_minimumY; } + void setMinimumY(qreal minY); + qreal maximumY() const { warnAboutMinMaxDeprecated(); return m_maximumY; } + void setMaximumY(qreal maxY); +#endif + + QQuickDragAxis *xAxis() { return &m_xAxis; } + QQuickDragAxis *yAxis() { return &m_yAxis; } + +signals: + void minimumScaleChanged(); + void maximumScaleChanged(); + void minimumRotationChanged(); + void maximumRotationChanged(); + void minimumXChanged(); + void maximumXChanged(); + void minimumYChanged(); + void maximumYChanged(); + void updated(); + +protected: + bool wantsPointerEvent(QQuickPointerEvent *event) override; + void onActiveChanged() override; + void handlePointerEventImpl(QQuickPointerEvent *event) override; + +private: + // properties + qreal m_activeScale = 1; + qreal m_accumulatedScale = 1; + qreal m_activeRotation = 0; + QVector2D m_activeTranslation = QVector2D(0, 0); + + qreal m_minimumScale = -qInf(); + qreal m_maximumScale = qInf(); + + qreal m_minimumRotation = -qInf(); + qreal m_maximumRotation = qInf(); + + qreal m_minimumX = -qInf(); + qreal m_maximumX = qInf(); + qreal m_minimumY = -qInf(); + qreal m_maximumY = qInf(); + QQuickDragAxis m_xAxis; + QQuickDragAxis m_yAxis; + + // internal + qreal m_startScale = 1; + qreal m_startRotation = 0; + qreal m_startDistance = 0; + QPointF m_startPos; + qreal m_accumulatedStartCentroidDistance = 0; + QVector<PointData> m_startAngles; + QQuickMatrix4x4 m_transform; + QMatrix4x4 m_startMatrix; +}; + +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..096fad2071 --- /dev/null +++ b/src/quick/handlers/qquickpointerdevicehandler.cpp @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** 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_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 QtQuick + \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(QQuickItem *parent) + : QQuickPointerHandler(*(new QQuickPointerDeviceHandlerPrivate), parent) +{ +} + +QQuickPointerDeviceHandler::QQuickPointerDeviceHandler(QQuickPointerDeviceHandlerPrivate &dd, QQuickItem *parent) + : QQuickPointerHandler(dd, parent) +{ +} + +QQuickPointerDevice::DeviceTypes QQuickPointerDeviceHandler::acceptedDevices() const +{ + Q_D(const QQuickPointerDeviceHandler); + return d->acceptedDevices; +} + +QQuickPointerDevice::PointerTypes QQuickPointerDeviceHandler::acceptedPointerTypes() const +{ + Q_D(const QQuickPointerDeviceHandler); + return d->acceptedPointerTypes; +} + +/*! + \qmlproperty flags QtQuick::PointerDeviceHandler::acceptedButtons + + The mouse buttons which can activate this Pointer Handler. + + By default, this property is set to \l {QtQuick::MouseEvent::button} {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}. +*/ +Qt::MouseButtons QQuickPointerDeviceHandler::acceptedButtons() const +{ + Q_D(const QQuickPointerDeviceHandler); + return d->acceptedButtons; +} + +void QQuickPointerDeviceHandler::setAcceptedButtons(Qt::MouseButtons buttons) +{ + Q_D(QQuickPointerDeviceHandler); + if (d->acceptedButtons == buttons) + return; + + d->acceptedButtons = buttons; + emit acceptedButtonsChanged(); +} + +Qt::KeyboardModifiers QQuickPointerDeviceHandler::acceptedModifiers() const +{ + Q_D(const QQuickPointerDeviceHandler); + return d->acceptedModifiers; +} + +/*! + \qmlproperty flags PointerDeviceHandler::acceptedDevices + + The types of pointing devices that can activate this Pointer Handler. + + By default, this property is set to + \l{QtQuick::PointerDevice::type} {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) +{ + Q_D(QQuickPointerDeviceHandler); + if (d->acceptedDevices == acceptedDevices) + return; + + d->acceptedDevices = acceptedDevices; + emit acceptedDevicesChanged(); +} + +/*! + \qmlproperty flags 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 {QtQuick::PointerDevice::pointerType} {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) +{ + Q_D(QQuickPointerDeviceHandler); + if (d->acceptedPointerTypes == acceptedPointerTypes) + return; + + d->acceptedPointerTypes = acceptedPointerTypes; + emit acceptedPointerTypesChanged(); +} + +/*! + \qmlproperty flags 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) +{ + Q_D(QQuickPointerDeviceHandler); + if (d->acceptedModifiers == acceptedModifiers) + return; + + d->acceptedModifiers = acceptedModifiers; + emit acceptedModifiersChanged(); +} + +bool QQuickPointerDeviceHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + Q_D(QQuickPointerDeviceHandler); + if (!QQuickPointerHandler::wantsPointerEvent(event)) + return false; + qCDebug(lcPointerHandlerDispatch) << objectName() + << "checking device type" << d->acceptedDevices + << "pointer type" << d->acceptedPointerTypes + << "modifiers" << d->acceptedModifiers; + if ((event->device()->type() & d->acceptedDevices) == 0) + return false; + if ((event->device()->pointerType() & d->acceptedPointerTypes) == 0) + return false; + if (d->acceptedModifiers != Qt::KeyboardModifierMask && event->modifiers() != d->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..82b24369d3 --- /dev/null +++ b/src/quick/handlers/qquickpointerdevicehandler_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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" + +#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. +// + +QT_BEGIN_NAMESPACE + +class QQuickPointerDeviceHandlerPrivate; + +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::MouseButtons acceptedButtons READ acceptedButtons WRITE setAcceptedButtons NOTIFY acceptedButtonsChanged) + Q_PROPERTY(Qt::KeyboardModifiers acceptedModifiers READ acceptedModifiers WRITE setAcceptedModifiers NOTIFY acceptedModifiersChanged) + +public: + explicit QQuickPointerDeviceHandler(QQuickItem *parent = nullptr); + + QQuickPointerDevice::DeviceTypes acceptedDevices() const; + QQuickPointerDevice::PointerTypes acceptedPointerTypes() const; + Qt::MouseButtons acceptedButtons() const; + Qt::KeyboardModifiers acceptedModifiers() const; + +public Q_SLOTS: + void setAcceptedDevices(QQuickPointerDevice::DeviceTypes acceptedDevices); + void setAcceptedPointerTypes(QQuickPointerDevice::PointerTypes acceptedPointerTypes); + void setAcceptedButtons(Qt::MouseButtons buttons); + void setAcceptedModifiers(Qt::KeyboardModifiers acceptedModifiers); + +Q_SIGNALS: + void acceptedDevicesChanged(); + void acceptedPointerTypesChanged(); + void acceptedButtonsChanged(); + void acceptedModifiersChanged(); + +protected: + QQuickPointerDeviceHandler(QQuickPointerDeviceHandlerPrivate &dd, QQuickItem *parent = nullptr); + + bool wantsPointerEvent(QQuickPointerEvent *event) override; + + Q_DECLARE_PRIVATE(QQuickPointerDeviceHandler) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPointerDeviceHandler) + +#endif // QQUICKPOINTERDEVICEHANDLER_H diff --git a/src/quick/handlers/qquickpointerdevicehandler_p_p.h b/src/quick/handlers/qquickpointerdevicehandler_p_p.h new file mode 100644 index 0000000000..6a950590f3 --- /dev/null +++ b/src/quick/handlers/qquickpointerdevicehandler_p_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_P_H +#define QQUICKPOINTERDEVICEHANDLER_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 "qquickpointerdevicehandler_p.h" +#include "qquickpointerhandler_p_p.h" + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickPointerDeviceHandlerPrivate : public QQuickPointerHandlerPrivate +{ + Q_DECLARE_PUBLIC(QQuickPointerDeviceHandler) + +public: + static QQuickPointerDeviceHandlerPrivate* get(QQuickPointerDeviceHandler *q) { return q->d_func(); } + static const QQuickPointerDeviceHandlerPrivate* get(const QQuickPointerDeviceHandler *q) { return q->d_func(); } + + QQuickPointerDevice::DeviceTypes acceptedDevices = QQuickPointerDevice::AllDevices; + QQuickPointerDevice::PointerTypes acceptedPointerTypes = QQuickPointerDevice::AllPointerTypes; + Qt::MouseButtons acceptedButtons = Qt::LeftButton; + Qt::KeyboardModifiers acceptedModifiers = Qt::KeyboardModifierMask; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPOINTERDEVICEHANDLER_P_H diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp new file mode 100644 index 0000000000..12c06aa179 --- /dev/null +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -0,0 +1,550 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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" +#include "qquickpointerhandler_p_p.h" +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPointerHandlerDispatch, "qt.quick.handler.dispatch") +Q_LOGGING_CATEGORY(lcPointerHandlerGrab, "qt.quick.handler.grab") +Q_LOGGING_CATEGORY(lcPointerHandlerActive, "qt.quick.handler.active") + +/*! + \qmltype PointerHandler + \qmlabstract + \since 5.10 + \preliminary + \instantiates QQuickPointerHandler + \inqmlmodule QtQuick + \brief Abstract handler for pointer events. + + PointerHandler is the base class Input Handler (not registered as a QML type) for + events from any kind of pointing device (touch, mouse or graphics tablet). +*/ + +QQuickPointerHandler::QQuickPointerHandler(QQuickItem *parent) + : QObject(*(new QQuickPointerHandlerPrivate), parent) +{ +} + +QQuickPointerHandler::QQuickPointerHandler(QQuickPointerHandlerPrivate &dd, QQuickItem *parent) + : QObject(dd, parent) +{ +} + +QQuickPointerHandler::~QQuickPointerHandler() +{ + QQuickItem *parItem = parentItem(); + if (parItem) { + QQuickItemPrivate *p = QQuickItemPrivate::get(parItem); + p->extra.value().pointerHandlers.removeOne(this); + } +} + +/*! + \qmlproperty real PointerHandler::margin + + The margin beyond the bounds of the \l {PointerHandler::parent}{parent} + item within which an event 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. Or, if a + TapHandler-based button is placed near the screen edge, it can be used + to comply with Fitts's Law: react to mouse clicks at the screen edge + even though the button is visually spaced away from the edge by a few pixels. + + The default value is 0. + + \image pointerHandlerMargin.png +*/ +qreal QQuickPointerHandler::margin() const +{ + Q_D(const QQuickPointerHandler); + return d->m_margin; +} + +void QQuickPointerHandler::setMargin(qreal pointDistanceThreshold) +{ + Q_D(QQuickPointerHandler); + if (d->m_margin == pointDistanceThreshold) + return; + + d->m_margin = pointDistanceThreshold; + emit marginChanged(); +} + +/*! + Notification that the grab has changed in some way which is relevant to this handler. + The \a grabber (subject) will be the Input Handler whose state is changing, + or null if the state change regards an Item. + The \a transition (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 Input Handler 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::GrabTransition transition, QQuickEventPoint *point) +{ + qCDebug(lcPointerHandlerGrab) << point << transition << grabber; + Q_ASSERT(point); + if (grabber == this) { + bool wasCanceled = false; + switch (transition) { + 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()) { + Q_D(const QQuickPointerHandler); + par->setKeepMouseGrab(d->hadKeepMouseGrab); + par->setKeepTouchGrab(d->hadKeepTouchGrab); + } + break; + 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); + emit grabChanged(transition, point); + } +} + +/*! + Acquire or give up a passive grab of the given \a point, according to the \a grab state. + + Unlike the exclusive grab, multiple Input Handlers 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 an + Input Handler 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(lcPointerHandlerGrab) << point << grab; + if (grab) { + point->setGrabberPointerHandler(this, false); + } else { + point->removePassiveGrabber(this); + } +} + +/*! + Check whether it's OK to take an exclusive grab of the \a point. + + The default implementation will call approveGrabTransition() to check this + handler's \l grabPermissions. If grabbing can be done only by taking over + the exclusive grab from an Item, approveGrabTransition() checks the Item's + \l keepMouseGrab or \l keepTouchGrab flags appropriately. If grabbing can + be done only by taking over another handler's exclusive grab, canGrab() + also calls approveGrabTransition() on the handler which is about to lose + its grab. Either one can deny the takeover. +*/ +bool QQuickPointerHandler::canGrab(QQuickEventPoint *point) +{ + QQuickPointerHandler *existingPhGrabber = point->grabberPointerHandler(); + return approveGrabTransition(point, this) && + (existingPhGrabber ? existingPhGrabber->approveGrabTransition(point, this) : true); +} + +/*! + Check this handler's rules to see if \l proposedGrabber will be allowed to take + the exclusive grab. This function may be called twice: once on the instance which + will take the grab, and once on the instance which would thereby lose its grab, + in case of a takeover scenario. +*/ +bool QQuickPointerHandler::approveGrabTransition(QQuickEventPoint *point, QObject *proposedGrabber) +{ + Q_D(const QQuickPointerHandler); + bool allowed = false; + if (proposedGrabber == this) { + QObject* existingGrabber = point->exclusiveGrabber(); + allowed = (existingGrabber == nullptr) || ((d->grabPermissions & CanTakeOverFromAnything) == CanTakeOverFromAnything); + if (existingGrabber) { + if (QQuickPointerHandler *existingPhGrabber = point->grabberPointerHandler()) { + if (!allowed && (d->grabPermissions & CanTakeOverFromHandlersOfDifferentType) && + existingPhGrabber->metaObject()->className() != metaObject()->className()) + allowed = true; + if (!allowed && (d->grabPermissions & CanTakeOverFromHandlersOfSameType) && + existingPhGrabber->metaObject()->className() == metaObject()->className()) + allowed = true; + } else if ((d->grabPermissions & CanTakeOverFromItems)) { + QQuickItem * existingItemGrabber = point->grabberItem(); + if (existingItemGrabber && !((existingItemGrabber->keepMouseGrab() && point->pointerEvent()->asPointerMouseEvent()) || + (existingItemGrabber->keepTouchGrab() && point->pointerEvent()->asPointerTouchEvent()))) + allowed = true; + } + } + } else { + // proposedGrabber is different: that means this instance will lose its grab + if (proposedGrabber) { + if ((d->grabPermissions & ApprovesTakeOverByAnything) == ApprovesTakeOverByAnything) + allowed = true; + if (!allowed && (d->grabPermissions & ApprovesTakeOverByHandlersOfDifferentType) && + proposedGrabber->metaObject()->className() != metaObject()->className()) + allowed = true; + if (!allowed && (d->grabPermissions & ApprovesTakeOverByHandlersOfSameType) && + proposedGrabber->metaObject()->className() == metaObject()->className()) + allowed = true; + if (!allowed && (d->grabPermissions & ApprovesTakeOverByItems) && proposedGrabber->inherits("QQuickItem")) + allowed = true; + } else { + if (!allowed && (d->grabPermissions & ApprovesCancellation)) + allowed = true; + } + } + qCDebug(lcPointerHandlerGrab) << "point" << hex << point->pointId() << "permission" << + QMetaEnum::fromType<GrabPermissions>().valueToKeys(grabPermissions()) << + ':' << this << (allowed ? "approved to" : "denied to") << proposedGrabber; + return allowed; +} + +/*! + \qmlproperty flags QtQuick::PointerHandler::grabPermissions + + This property specifies the permissions when this handler's logic decides + to take over the exclusive grab, or when it is asked to approve grab + takeover or cancellation by another handler. + + \value PointerHandler.TakeOverForbidden + This handler neither takes from nor gives grab permission to any type of Item or Handler. + \value PointerHandler.CanTakeOverFromHandlersOfSameType + This handler can take the exclusive grab from another handler of the same class. + \value PointerHandler.CanTakeOverFromHandlersOfDifferentType + This handler can take the exclusive grab from any kind of handler. + \value PointerHandler.CanTakeOverFromAnything + This handler can take the exclusive grab from any type of Item or Handler. + \value PointerHandler.ApprovesTakeOverByHandlersOfSameType + This handler gives permission for another handler of the same class to take the grab. + \value PointerHandler.ApprovesTakeOverByHandlersOfDifferentType + This handler gives permission for any kind of handler to take the grab. + \value PointerHandler.ApprovesTakeOverByItems + This handler gives permission for any kind of Item to take the grab. + \value PointerHandler.ApprovesCancellation + This handler will allow its grab to be set to null. + \value PointerHandler.ApprovesTakeOverByAnything + This handler gives permission for any any type of Item or Handler to take the grab. + + The default is + \c {PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByAnything} + which allows most takeover scenarios but avoids e.g. two PinchHandlers fighting + over the same touchpoints. +*/ +QQuickPointerHandler::GrabPermissions QQuickPointerHandler::grabPermissions() const +{ + Q_D(const QQuickPointerHandler); + return static_cast<QQuickPointerHandler::GrabPermissions>(d->grabPermissions); +} + +void QQuickPointerHandler::setGrabPermissions(GrabPermissions grabPermission) +{ + Q_D(QQuickPointerHandler); + if (d->grabPermissions == grabPermission) + return; + + d->grabPermissions = grabPermission; + emit grabPermissionChanged(); +} + +void QQuickPointerHandler::classBegin() +{ +} + +void QQuickPointerHandler::componentComplete() +{ +} + +QQuickPointerEvent *QQuickPointerHandler::currentEvent() +{ + Q_D(const QQuickPointerHandler); + return d->currentEvent; +} + +/*! + Acquire or give up the exclusive grab of the given \a point, according to + the \a grab state, and subject to the rules: canGrab(), and the rule not to + relinquish another handler's grab. Returns true if permission is granted, + or if the exclusive grab has already been acquired or relinquished as + specified. Returns false if permission is denied either by this handler or + by the handler or item from which this handler would take over +*/ +bool QQuickPointerHandler::setExclusiveGrab(QQuickEventPoint *point, bool grab) +{ + if ((grab && point->exclusiveGrabber() == this) || (!grab && point->exclusiveGrabber() != this)) + return true; + // TODO m_hadKeepMouseGrab m_hadKeepTouchGrab + bool allowed = true; + if (grab) { + allowed = canGrab(point); + } else { + QQuickPointerHandler *existingPhGrabber = point->grabberPointerHandler(); + // Ask before allowing one handler to cancel another's grab + if (existingPhGrabber && existingPhGrabber != this && !existingPhGrabber->approveGrabTransition(point, nullptr)) + allowed = false; + } + qCDebug(lcPointerHandlerGrab) << point << (grab ? "grab" : "ungrab") << (allowed ? "allowed" : "forbidden") << + point->exclusiveGrabber() << "->" << (grab ? this : nullptr); + if (allowed) + point->setGrabberPointerHandler(grab ? this : nullptr, true); + return allowed; +} + +/*! + Cancel any existing grab of the given \a point. +*/ +void QQuickPointerHandler::cancelAllGrabs(QQuickEventPoint *point) +{ + qCDebug(lcPointerHandlerGrab) << 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) + return false; + if (QQuickItem *par = parentItem()) { + if (par->window()) { + QPoint screenPosition = par->window()->mapToGlobal(point->scenePosition().toPoint()); + if (!par->window()->geometry().contains(screenPosition)) + return false; + } + QPointF p = par->mapFromScene(point->scenePosition()); + qreal m = margin(); + if (m > 0) + return p.x() >= -m && p.y() >= -m && p.x() <= par->width() + m && p.y() <= par->height() + m; + return par->contains(p); + } + return false; +} + +/*! + \qmlproperty bool QtQuick::PointerHandler::enabled + + If a PointerHandler is disabled, it will reject all events + and no signals will be emitted. +*/ +bool QQuickPointerHandler::enabled() const +{ + Q_D(const QQuickPointerHandler); + return d->enabled; +} + +void QQuickPointerHandler::setEnabled(bool enabled) +{ + Q_D(QQuickPointerHandler); + if (d->enabled == enabled) + return; + + d->enabled = enabled; + emit enabledChanged(); +} + +bool QQuickPointerHandler::active() const +{ + Q_D(const QQuickPointerHandler); + return d->active; +} + +/*! + \qmlproperty Item QtQuick::PointerHandler::target + + The Item which this handler will manipulate. + + By default, it is the same as the \l [QML] {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) +{ + Q_D(QQuickPointerHandler); + d->targetExplicitlySet = true; + if (d->target == target) + return; + + QQuickItem *oldTarget = d->target; + d->target = target; + onTargetChanged(oldTarget); + emit targetChanged(); +} + +QQuickItem *QQuickPointerHandler::parentItem() const +{ + return static_cast<QQuickItem *>(QObject::parent()); +} + +QQuickItem *QQuickPointerHandler::target() const +{ + Q_D(const QQuickPointerHandler); + if (!d->targetExplicitlySet) + return parentItem(); + return d->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_D(const QQuickPointerHandler); + Q_UNUSED(event) + return d->enabled; +} + +bool QQuickPointerHandler::wantsEventPoint(QQuickEventPoint *point) +{ + bool ret = point->exclusiveGrabber() == this || point->passiveGrabbers().contains(this) || parentContains(point); + qCDebug(lcPointerHandlerDispatch) << hex << point->pointId() << "@" << point->scenePosition() + << metaObject()->className() << objectName() << ret; + return ret; +} + +/*! + \readonly + \qmlproperty bool QtQuick::PointerHandler::active + + This holds true whenever this Input Handler 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) +{ + Q_D(QQuickPointerHandler); + if (d->active != active) { + qCDebug(lcPointerHandlerActive) << this << d->active << "->" << active; + d->active = active; + onActiveChanged(); + emit activeChanged(); + } +} + +void QQuickPointerHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + Q_D(QQuickPointerHandler); + d->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 [QML] {target} {target()} is the same, but it + can be reassigned. + + \sa {target}, QObject::parent() +*/ + +/*! + \qmlsignal QtQuick::PointerHandler::grabChanged(GrabTransition transition, EventPoint point) + + This signal is emitted when the grab has changed in some way which is + relevant to this handler. + + The \a transition (verb) tells what happened. + The \a point (object) is the point that was grabbed or ungrabbed. +*/ + +/*! + \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. +*/ + +QQuickPointerHandlerPrivate::QQuickPointerHandlerPrivate() + : grabPermissions(QQuickPointerHandler::CanTakeOverFromItems | + QQuickPointerHandler::CanTakeOverFromHandlersOfDifferentType | + QQuickPointerHandler::ApprovesTakeOverByAnything) + , enabled(true) + , active(false) + , targetExplicitlySet(false) + , hadKeepMouseGrab(false) + , hadKeepTouchGrab(false) +{ +} + +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..c600e42491 --- /dev/null +++ b/src/quick/handlers/qquickpointerhandler_p.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 <QtQuick/private/qquickevents_p_p.h> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcPointerHandlerDispatch) + +class QQuickPointerHandlerPrivate; + +class Q_QUICK_PRIVATE_EXPORT QQuickPointerHandler : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + 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) + Q_PROPERTY(GrabPermissions grabPermissions READ grabPermissions WRITE setGrabPermissions NOTIFY grabPermissionChanged) + Q_PROPERTY(qreal margin READ margin WRITE setMargin NOTIFY marginChanged) + +public: + explicit QQuickPointerHandler(QQuickItem *parent = nullptr); + ~QQuickPointerHandler(); + + enum GrabPermission { + TakeOverForbidden = 0x0, + CanTakeOverFromHandlersOfSameType = 0x01, + CanTakeOverFromHandlersOfDifferentType= 0x02, + CanTakeOverFromItems = 0x04, + CanTakeOverFromAnything = 0x0F, + ApprovesTakeOverByHandlersOfSameType = 0x10, + ApprovesTakeOverByHandlersOfDifferentType= 0x20, + ApprovesTakeOverByItems = 0x40, + ApprovesCancellation = 0x80, + ApprovesTakeOverByAnything = 0xF0 + }; + Q_DECLARE_FLAGS(GrabPermissions, GrabPermission) + Q_FLAG(GrabPermissions) + +public: + bool enabled() const; + void setEnabled(bool enabled); + + bool active() const; + + QQuickItem *target() const; + void setTarget(QQuickItem *target); + + QQuickItem * parentItem() const; + + void handlePointerEvent(QQuickPointerEvent *event); + + GrabPermissions grabPermissions() const; + void setGrabPermissions(GrabPermissions grabPermissions); + + qreal margin() const; + void setMargin(qreal pointDistanceThreshold); + +Q_SIGNALS: + void enabledChanged(); + void activeChanged(); + void targetChanged(); + void marginChanged(); + void grabChanged(QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point); + void grabPermissionChanged(); + void canceled(QQuickEventPoint *point); + +protected: + QQuickPointerHandler(QQuickPointerHandlerPrivate &dd, QQuickItem *parent); + + void classBegin() override; + void componentComplete() override; + + QQuickPointerEvent *currentEvent(); + virtual bool wantsPointerEvent(QQuickPointerEvent *event); + virtual bool wantsEventPoint(QQuickEventPoint *point); + virtual void handlePointerEventImpl(QQuickPointerEvent *event); + void setActive(bool active); + virtual void onTargetChanged(QQuickItem *oldTarget) { Q_UNUSED(oldTarget); } + virtual void onActiveChanged() { } + virtual void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point); + virtual bool canGrab(QQuickEventPoint *point); + virtual bool approveGrabTransition(QQuickEventPoint *point, QObject *proposedGrabber); + void setPassiveGrab(QQuickEventPoint *point, bool grab = true); + bool setExclusiveGrab(QQuickEventPoint *point, bool grab = true); + void cancelAllGrabs(QQuickEventPoint *point); + QPointF eventPos(const QQuickEventPoint *point) const; + bool parentContains(const QQuickEventPoint *point) const; + + friend class QQuickEventPoint; + friend class QQuickItemPrivate; + friend class QQuickWindowPrivate; + + Q_DECLARE_PRIVATE(QQuickPointerHandler) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickPointerHandler::GrabPermissions) + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPointerHandler) + +#endif // QQUICKPOINTERHANDLER_H diff --git a/src/quick/handlers/qquickpointerhandler_p_p.h b/src/quick/handlers/qquickpointerhandler_p_p.h new file mode 100644 index 0000000000..2ea4905643 --- /dev/null +++ b/src/quick/handlers/qquickpointerhandler_p_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_P_H +#define QQUICKPOINTERHANDLER_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 "qevent.h" + +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICK_PRIVATE_EXPORT QQuickPointerHandlerPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickPointerHandler) + +public: + static QQuickPointerHandlerPrivate* get(QQuickPointerHandler *q) { return q->d_func(); } + static const QQuickPointerHandlerPrivate* get(const QQuickPointerHandler *q) { return q->d_func(); } + + QQuickPointerHandlerPrivate(); + + QQuickPointerEvent *currentEvent = nullptr; + QQuickItem *target = nullptr; + qreal m_margin = 0; + uint8_t grabPermissions : 8; + bool enabled : 1; + bool active : 1; + bool targetExplicitlySet : 1; + bool hadKeepMouseGrab : 1; // some handlers override target()->setKeepMouseGrab(); this remembers previous state + bool hadKeepTouchGrab : 1; // some handlers override target()->setKeepTouchGrab(); this remembers previous state +}; + +QT_END_NAMESPACE + +#endif // QQUICKPOINTERHANDLER_P_H diff --git a/src/quick/handlers/qquickpointhandler.cpp b/src/quick/handlers/qquickpointhandler.cpp new file mode 100644 index 0000000000..30f62332ba --- /dev/null +++ b/src/quick/handlers/qquickpointhandler.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** 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 "qquickpointhandler_p.h" +#include <private/qquickwindow_p.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype PointHandler + \instantiates QQuickPointHandler + \inherits SinglePointHandler + \inqmlmodule QtQuick + \ingroup qtquick-input-handlers + \brief Handler for reacting to a single touchpoint. + + PointHandler can be used to show feedback about a touchpoint or the mouse + position, or to otherwise react to pointer events. + + When a press event occurs, each instance of PointHandler chooses a + single point which is not yet "taken" at that moment: if the press + occurs within the bounds of the \l {PointerHandler::parent}, and + no sibling PointHandler within the same \l {PointerHandler::parent} + has yet acquired a passive grab on that point, and if the other + constraints such as \l {PointerDeviceHandler::acceptedButtons}{acceptedButtons}, \l {PointerDeviceHandler::acceptedDevices}{acceptedDevices} etc. + are satisfied, it's + eligible, and the PointHandler then acquires a passive grab. In + this way, the \l {PointerHandler::parent} acts like an exclusive + group: there can be multiple instances of PointHandler, and the + set of pressed touchpoints will be distributed among them. Each + PointHandler which has chosen a point to track has its \l active + property \c true. It then continues to track its chosen point + until release: the properties of the \l point will be kept + up-to-date. Any Item can bind to these properties, and thereby + follow the point's movements. + + By being only a passive grabber, it has the ability to keep independent + oversight of all movements. The passive grab cannot be stolen or overridden + even when other gestures are detected and exclusive grabs occur. + + If your goal is orthogonal surveillance of eventpoints, an older + alternative was QObject::installEventFilter(), but that has never been a + built-in QtQuick feature: it requires some C++ code, such as a QQuickItem + subclass. PointHandler is more efficient than that, because only pointer + events will be delivered to it, during the course of normal event delivery + in QQuickWindow; whereas an event filter needs to filter all QEvents of all + types, and thus sets itself up as a potential event delivery bottleneck. + + One possible use case is to add this handler to a transparent Item which is + on top of the rest of the scene (by having a high \l{Item::z} {z} value), + so that when a point is freshly pressed, it will be delivered to that Item + and its handlers first, providing the opportunity to take the passive grab + as early as possible. Such an item (like a pane of glass over the whole UI) + can be a convenient parent for other Items which visualize the kind of reactive + feedback which must always be on top; and likewise it can be the parent for + popups, popovers, dialogs and so on. If it will be used in that way, it can + be helpful for your main.cpp to use QQmlContext::setContextProperty() to + make the "glass pane" accessible by ID to the entire UI, so that other + Items and PointHandlers can be reparented to it. + + \snippet pointerHandlers/pointHandler.qml 0 + + Like all input handlers, a PointHandler has a \l target property, which + may be used as a convenient place to put a point-tracking Item; but + PointHandler will not automatically manipulate the \c target item in any way. + You need to use bindings to make it react to the \l point. + + \note On macOS, PointHandler does not react to the trackpad by default. + That is because macOS can provide either native gesture recognition, or raw + touchpoints, but not both. We prefer to use the native gesture event in + PinchHandler, so we do not want to disable it by enabling touch. However + MultiPointTouchArea does enable touch, thus disabling native gesture + recognition within the entire window; so it's an alternative if you only + want to react to all the touchpoints but do not require the smooth + native-gesture experience. + + \sa MultiPointTouchArea +*/ + +QQuickPointHandler::QQuickPointHandler(QQuickItem *parent) + : QQuickSinglePointHandler(parent) +{ + setIgnoreAdditionalPoints(); +} + +bool QQuickPointHandler::wantsEventPoint(QQuickEventPoint *pt) +{ + // On press, we want it unless a sibling of the same type also does. + if (pt->state() == QQuickEventPoint::Pressed && QQuickSinglePointHandler::wantsEventPoint(pt)) { + for (const QQuickPointerHandler *grabber : pt->passiveGrabbers()) { + if (grabber && grabber->parent() == parent() && + grabber->metaObject()->className() == metaObject()->className()) + return false; + } + return true; + } + // If we've already been interested in a point, stay interested, even if it has strayed outside bounds. + return (pt->state() != QQuickEventPoint::Pressed && point().id() == pt->pointId()); +} + +void QQuickPointHandler::handleEventPoint(QQuickEventPoint *point) +{ + switch (point->state()) { + case QQuickEventPoint::Pressed: + if (point->pointerEvent()->asPointerTouchEvent() || + (point->pointerEvent()->buttons() & acceptedButtons()) != Qt::NoButton) { + setPassiveGrab(point); + setActive(true); + } + break; + case QQuickEventPoint::Released: + if (point->pointerEvent()->asPointerTouchEvent() || + (point->pointerEvent()->buttons() & acceptedButtons()) == Qt::NoButton) + setActive(false); + break; + default: + break; + } + point->setAccepted(false); // Just lurking... don't interfere with propagation + emit translationChanged(); +} + +QVector2D QQuickPointHandler::translation() const +{ + return QVector2D(point().position() - point().pressPosition()); +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpointhandler_p.h b/src/quick/handlers/qquickpointhandler_p.h new file mode 100644 index 0000000000..380ce1f90f --- /dev/null +++ b/src/quick/handlers/qquickpointhandler_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 QQUICKPONTHANDLER_H +#define QQUICKPONTHANDLER_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 QQuickPointHandler : public QQuickSinglePointHandler +{ + Q_OBJECT + Q_PROPERTY(QVector2D translation READ translation NOTIFY translationChanged) + +public: + explicit QQuickPointHandler(QQuickItem *parent = nullptr); + + QVector2D translation() const; + +Q_SIGNALS: + void translationChanged(); + +protected: + bool wantsEventPoint(QQuickEventPoint *pt) override; + void handleEventPoint(QQuickEventPoint *point) override; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPointHandler) + +#endif // QQUICKPONTHANDLER_H diff --git a/src/quick/handlers/qquicksinglepointhandler.cpp b/src/quick/handlers/qquicksinglepointhandler.cpp new file mode 100644 index 0000000000..c0fa39fad3 --- /dev/null +++ b/src/quick/handlers/qquicksinglepointhandler.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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" +#include "qquicksinglepointhandler_p_p.h" + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(DBG_TOUCH_TARGET) + +/*! + \qmltype SinglePointHandler + \qmlabstract + \preliminary + \instantiates QQuickSinglePointHandler + \inherits PointerDeviceHandler + \inqmlmodule QtQuick + \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(QQuickItem *parent) + : QQuickPointerDeviceHandler(*(new QQuickSinglePointHandlerPrivate), parent) +{ +} + +QQuickSinglePointHandler::QQuickSinglePointHandler(QQuickSinglePointHandlerPrivate &dd, QQuickItem *parent) + : QQuickPointerDeviceHandler(dd, parent) +{ +} + +bool QQuickSinglePointHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + Q_D(QQuickSinglePointHandler); + if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) + return false; + if (event->device()->pointerType() != QQuickPointerDevice::Finger && + (event->buttons() & acceptedButtons()) == 0 && (event->button() & acceptedButtons()) == 0) + return false; + + if (d->pointInfo.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; + bool missing = true; + QQuickEventPoint *point = nullptr; + int c = event->pointCount(); + for (int i = 0; i < c; ++i) { + QQuickEventPoint *p = event->point(i); + const bool found = (p->pointId() == d->pointInfo.id()); + if (found) + missing = false; + if (wantsEventPoint(p)) { + ++candidatePointCount; + if (found) + point = p; + } + } + if (missing) + qCWarning(DBG_TOUCH_TARGET) << this << "pointId" << hex << d->pointInfo.id() + << "is missing from current event, but was neither canceled nor released"; + if (point) { + if (candidatePointCount == 1 || (candidatePointCount > 1 && d->ignoreAdditionalPoints)) { + point->setAccepted(); + return true; + } else { + point->cancelAllGrabs(this); + } + } else { + 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 && !chosen; ++i) { + QQuickEventPoint *p = event->point(i); + if (!p->exclusiveGrabber() && wantsEventPoint(p)) { + if (!chosen) + chosen = p; + ++candidatePointCount; + } + } + if (chosen && candidatePointCount == 1) { + setPointId(chosen->pointId()); + chosen->setAccepted(); + } + } + return d->pointInfo.id(); +} + +void QQuickSinglePointHandler::handlePointerEventImpl(QQuickPointerEvent *event) +{ + Q_D(QQuickSinglePointHandler); + QQuickPointerDeviceHandler::handlePointerEventImpl(event); + QQuickEventPoint *currentPoint = event->pointById(d->pointInfo.id()); + Q_ASSERT(currentPoint); + d->pointInfo.reset(currentPoint); + handleEventPoint(currentPoint); + if (currentPoint->state() == QQuickEventPoint::Released && (event->buttons() & acceptedButtons()) == Qt::NoButton) { + setExclusiveGrab(currentPoint, false); + d->reset(); + } + emit pointChanged(); +} + +void QQuickSinglePointHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point) +{ + Q_D(QQuickSinglePointHandler); + if (grabber != this) + return; + switch (transition) { + case QQuickEventPoint::GrabExclusive: + d->pointInfo.m_sceneGrabPosition = point->sceneGrabPosition(); + setActive(true); + QQuickPointerHandler::onGrabChanged(grabber, transition, point); + break; + case QQuickEventPoint::GrabPassive: + d->pointInfo.m_sceneGrabPosition = point->sceneGrabPosition(); + QQuickPointerHandler::onGrabChanged(grabber, transition, 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, transition, point); + d->reset(); + break; + } +} + +void QQuickSinglePointHandler::setIgnoreAdditionalPoints(bool v) +{ + Q_D(QQuickSinglePointHandler); + d->ignoreAdditionalPoints = v; +} + +void QQuickSinglePointHandler::moveTarget(QPointF pos, QQuickEventPoint *point) +{ + Q_D(QQuickSinglePointHandler); + target()->setPosition(pos); + d->pointInfo.m_scenePosition = point->scenePosition(); + d->pointInfo.m_position = target()->mapFromScene(d->pointInfo.m_scenePosition); +} + +void QQuickSinglePointHandler::setPointId(int id) +{ + Q_D(QQuickSinglePointHandler); + d->pointInfo.m_id = id; +} + +QQuickHandlerPoint QQuickSinglePointHandler::point() const +{ + Q_D(const QQuickSinglePointHandler); + return d->pointInfo; +} + +/*! + \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). +*/ + +QQuickSinglePointHandlerPrivate::QQuickSinglePointHandlerPrivate() + : QQuickPointerDeviceHandlerPrivate() +{ +} + +void QQuickSinglePointHandlerPrivate::reset() +{ + Q_Q(QQuickSinglePointHandler); + q->setActive(false); + pointInfo.reset(); +} + +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..edc55aaaf6 --- /dev/null +++ b/src/quick/handlers/qquicksinglepointhandler_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qquickhandlerpoint_p.h" +#include "qquickpointerdevicehandler_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickSinglePointHandlerPrivate; + +class Q_QUICK_PRIVATE_EXPORT QQuickSinglePointHandler : public QQuickPointerDeviceHandler +{ + Q_OBJECT + Q_PROPERTY(QQuickHandlerPoint point READ point NOTIFY pointChanged) + +public: + explicit QQuickSinglePointHandler(QQuickItem *parent = nullptr); + + QQuickHandlerPoint point() const; + +Q_SIGNALS: + void pointChanged(); + +protected: + QQuickSinglePointHandler(QQuickSinglePointHandlerPrivate &dd, QQuickItem *parent); + + bool wantsPointerEvent(QQuickPointerEvent *event) override; + void handlePointerEventImpl(QQuickPointerEvent *event) override; + virtual void handleEventPoint(QQuickEventPoint *point) = 0; + + QQuickEventPoint *currentPoint(QQuickPointerEvent *ev); + void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point) override; + + void setIgnoreAdditionalPoints(bool v = true); + + void moveTarget(QPointF pos, QQuickEventPoint *point); + + void setPointId(int id); + + Q_DECLARE_PRIVATE(QQuickSinglePointHandler) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSinglePointHandler) + +#endif // QQUICKPOINTERSINGLEHANDLER_H diff --git a/src/quick/handlers/qquicksinglepointhandler_p_p.h b/src/quick/handlers/qquicksinglepointhandler_p_p.h new file mode 100644 index 0000000000..1e66c25e14 --- /dev/null +++ b/src/quick/handlers/qquicksinglepointhandler_p_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_P_H +#define QQUICKPOINTERSINGLEHANDLER_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 "qquickhandlerpoint_p.h" +#include "qquickpointerdevicehandler_p_p.h" +#include "qquicksinglepointhandler_p.h" + +QT_BEGIN_NAMESPACE + +class Q_QUICK_PRIVATE_EXPORT QQuickSinglePointHandlerPrivate : public QQuickPointerDeviceHandlerPrivate +{ + Q_DECLARE_PUBLIC(QQuickSinglePointHandler) + +public: + static QQuickSinglePointHandlerPrivate* get(QQuickSinglePointHandler *q) { return q->d_func(); } + static const QQuickSinglePointHandlerPrivate* get(const QQuickSinglePointHandler *q) { return q->d_func(); } + + QQuickSinglePointHandlerPrivate(); + + void reset(); + + QQuickHandlerPoint pointInfo; + bool ignoreAdditionalPoints = false; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPOINTERSINGLEHANDLER_P_H + diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp new file mode 100644 index 0000000000..081645da71 --- /dev/null +++ b/src/quick/handlers/qquicktaphandler.cpp @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "qquicksinglepointhandler_p_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 QtQuick + \ingroup qtquick-input-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. The default + value is DragThreshold, which requires the press and release to be close + together in both space and time. In this case, DragHandler is able to + function using only a passive grab, and therefore does not interfere with + event delivery to any other Items or Input Handlers. So the default + gesturePolicy is useful when you want to modify behavior of an existing + control or Item by adding a TapHandler with bindings and/or JavaScript + callbacks. + + 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. For this use case, set the + \l gesturePolicy to \c TapHandler.ReleaseWithinBounds. + + 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(QQuickItem *parent) + : QQuickSinglePointHandler(parent) +{ + 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; + } +} + +static bool dragOverThreshold(const 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 (!point->pointerEvent()->asPointerMouseEvent() && + !point->pointerEvent()->asPointerTouchEvent() && + !point->pointerEvent()->asPointerTabletEvent() ) + return false; + // 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; + bool overThreshold = dragOverThreshold(point); + if (overThreshold) { + m_longPressTimer.stop(); + m_holdTimer.invalidate(); + } + switch (point->state()) { + case QQuickEventPoint::Pressed: + case QQuickEventPoint::Released: + ret = parentContains(point); + break; + case QQuickEventPoint::Updated: + switch (m_gesturePolicy) { + case DragThreshold: + ret = !overThreshold && parentContains(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 QtQuick::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 QtQuick::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 pressed transitions immediately + from true to false, regardless of the time held. + + \value TapHandler.DragThreshold + (the default value) 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 + input handlers (for example \l DragHandler) or event-handling Items + (for example QtQuick Controls), because in this case TapHandler + will not take the exclusive grab, but merely a passive grab. + + \value TapHandler.WithinBounds + If the event point leaves the bounds of the \c parent Item, the tap + gesture is canceled. The TapHandler will take the exclusive grab on + press, but will release the grab as soon as the boundary constraint + is no longer satisfied. + + \value TapHandler.ReleaseWithinBounds + 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 + \c parent Item, a tap gesture is not recognized. This corresponds to + typical behavior for button widgets: 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 take the exclusive grab on press and retain + it until release in order to detect this gesture. +*/ +void QQuickTapHandler::setGesturePolicy(QQuickTapHandler::GesturePolicy gesturePolicy) +{ + if (m_gesturePolicy == gesturePolicy) + return; + + m_gesturePolicy = gesturePolicy; + emit gesturePolicyChanged(); +} + +/*! + \qmlproperty bool QtQuick::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(point); + emit tapCountChanged(); + if (m_tapCount == 1) + emit singleTapped(point); + else if (m_tapCount == 2) + emit doubleTapped(point); + 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); + } + if (cancel) { + emit canceled(point); + setExclusiveGrab(point, false); + // In case there is a filtering parent (Flickable), we should not give up the passive grab, + // so that it can continue to filter future events. + d_func()->reset(); + emit pointChanged(); + } + } +} + +void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point) +{ + QQuickSinglePointHandler::onGrabChanged(grabber, transition, point); + bool isCanceled = transition == QQuickEventPoint::CancelGrabExclusive || transition == 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 QtQuick::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 triple-tap, you can write: + + \qml + Rectangle { + width: 100; height: 30 + signal tripleTap + TapHandler { + acceptedButtons: Qt.AllButtons + onTapped: if (tapCount == 3) tripleTap() + } + } + \endqml +*/ + +/*! + \qmlproperty real QtQuick::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. +*/ + +/*! + \qmlsignal QtQuick::TapHandler::tapped + + This signal is emitted each time the \c parent Item is tapped. + + That is, if you press and release a touchpoint or button within a time + period less than \l longPressThreshold, while any movement does not exceed + the drag threshold, then the \c tapped signal will be emitted at the time + of release. +*/ + +/*! + \qmlsignal QtQuick::TapHandler::singleTapped + \since 5.11 + + This signal is emitted when the \c parent Item is tapped once. + After an amount of time greater than QStyleHints::mouseDoubleClickInterval, + it can be tapped again; but if the time until the next tap is less, + \l tapCount will increase. +*/ + +/*! + \qmlsignal QtQuick::TapHandler::doubleTapped + \since 5.11 + + This signal is emitted when the \c parent Item is tapped twice within a + short span of time (QStyleHints::mouseDoubleClickInterval) and distance + (QPlatformTheme::MouseDoubleClickDistance or + QPlatformTheme::TouchDoubleTapDistance). This signal always occurs after + \l singleTapped, \l tapped, and \l tapCountChanged. +*/ + +/*! + \qmlsignal QtQuick::TapHandler::longPressed + + This signal is emitted when the \c parent Item is pressed and held for a + time period greater than \l longPressThreshold. That is, if you press and + hold a touchpoint or button, while any movement does not exceed the drag + threshold, then the \c longPressed signal will be emitted at the time that + \l timeHeld exceeds \l longPressThreshold. +*/ + +/*! + \qmlsignal QtQuick::TapHandler::tapCountChanged + + This signal is emitted when the \c parent Item is tapped once or more (within + a specified time and distance span) and when the present \c tapCount differs + from the previous \c tapCount. +*/ +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..6ec5d55227 --- /dev/null +++ b/src/quick/handlers/qquicktaphandler_p.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** 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(QQuickItem *parent = nullptr); + + 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(QQuickEventPoint *eventPoint); + void singleTapped(QQuickEventPoint *eventPoint); + void doubleTapped(QQuickEventPoint *eventPoint); + void longPressed(); + +protected: + void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, 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: + QPointF m_lastTapPos; + qreal m_lastTapTimestamp = 0; + QElapsedTimer m_holdTimer; + QBasicTimer m_longPressTimer; + int m_tapCount = 0; + int m_longPressThreshold = -1; + GesturePolicy m_gesturePolicy = GesturePolicy::DragThreshold; + bool m_pressed = false; + + static qreal m_multiTapInterval; + static int m_mouseMultiClickDistanceSquared; + static int m_touchMultiTapDistanceSquared; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickTapHandler) + +#endif // QQUICKTAPHANDLER_H |