aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/handlers/qquicktaphandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/handlers/qquicktaphandler.cpp')
-rw-r--r--src/quick/handlers/qquicktaphandler.cpp482
1 files changed, 319 insertions, 163 deletions
diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp
index 61f97868e8..accf307382 100644
--- a/src/quick/handlers/qquicktaphandler.cpp
+++ b/src/quick/handlers/qquicktaphandler.cpp
@@ -1,43 +1,10 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquicktaphandler_p.h"
+#include "qquicksinglepointhandler_p_p.h"
+#include <QtQuick/private/qquickdeliveryagent_p_p.h>
+#include <QtQuick/qquickwindow.h>
#include <qpa/qplatformtheme.h>
#include <private/qguiapplication_p.h>
#include <QtGui/qstylehints.h>
@@ -46,7 +13,7 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcTapHandler, "qt.quick.handler.tap")
-qreal QQuickTapHandler::m_multiTapInterval(0.0);
+quint64 QQuickTapHandler::m_multiTapInterval(0);
// single tap distance is the same as the drag threshold
int QQuickTapHandler::m_mouseMultiClickDistanceSquared(-1);
int QQuickTapHandler::m_touchMultiTapDistanceSquared(-1);
@@ -76,116 +43,133 @@ int QQuickTapHandler::m_touchMultiTapDistanceSquared(-1);
button in order to cancel the click. For this use case, set the
\l gesturePolicy to \c TapHandler.ReleaseWithinBounds.
+ \snippet pointerHandlers/tapHandlerButton.qml 0
+
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
+ must not exceed QStyleHints::mouseDoubleClickDistance() with mouse and
+ QStyleHints::touchDoubleTapDistance() with touch, and the time between
taps must not exceed QStyleHints::mouseDoubleClickInterval().
- \sa MouseArea
+ \sa MouseArea, {Qt Quick Examples - Pointer Handlers}
*/
QQuickTapHandler::QQuickTapHandler(QQuickItem *parent)
: QQuickSinglePointHandler(parent)
+ , m_longPressThreshold(QGuiApplication::styleHints()->mousePressAndHoldInterval())
{
if (m_mouseMultiClickDistanceSquared < 0) {
- m_multiTapInterval = qApp->styleHints()->mouseDoubleClickInterval() / 1000.0;
- m_mouseMultiClickDistanceSquared = QGuiApplicationPrivate::platformTheme()->
- themeHint(QPlatformTheme::MouseDoubleClickDistance).toInt();
+ m_multiTapInterval = qApp->styleHints()->mouseDoubleClickInterval();
+ m_mouseMultiClickDistanceSquared = qApp->styleHints()->mouseDoubleClickDistance();
m_mouseMultiClickDistanceSquared *= m_mouseMultiClickDistanceSquared;
- m_touchMultiTapDistanceSquared = QGuiApplicationPrivate::platformTheme()->
- themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt();
+ m_touchMultiTapDistanceSquared = qApp->styleHints()->touchDoubleTapDistance();
m_touchMultiTapDistanceSquared *= m_touchMultiTapDistanceSquared;
}
}
-static bool dragOverThreshold(const QQuickEventPoint *point)
+bool QQuickTapHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &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() )
+ if (!QQuickDeliveryAgentPrivate::isMouseEvent(event) &&
+ !QQuickDeliveryAgentPrivate::isTouchEvent(event) &&
+ !QQuickDeliveryAgentPrivate::isTabletEvent(event))
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) {
+ bool overThreshold = d_func()->dragOverThreshold(point);
+ if (overThreshold && m_gesturePolicy != DragWithinBounds) {
+ if (m_longPressTimer.isActive())
+ qCDebug(lcTapHandler) << objectName() << "drag threshold exceeded";
m_longPressTimer.stop();
m_holdTimer.invalidate();
}
- switch (point->state()) {
- case QQuickEventPoint::Pressed:
- case QQuickEventPoint::Released:
+ switch (point.state()) {
+ case QEventPoint::Pressed:
+ case QEventPoint::Released:
ret = parentContains(point);
break;
- case QQuickEventPoint::Updated:
+ case QEventPoint::Updated:
+ ret = point.id() == this->point().id();
switch (m_gesturePolicy) {
case DragThreshold:
- ret = !overThreshold && parentContains(point);
+ ret = ret && !overThreshold && parentContains(point);
break;
case WithinBounds:
- ret = parentContains(point);
+ case DragWithinBounds:
+ ret = ret && parentContains(point);
break;
case ReleaseWithinBounds:
- ret = point->pointId() == this->point().id();
+ // no change to ret: depends only whether it's the already-tracking 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.
+ case QEventPoint::Stationary:
+ // If the point hasn't moved since last time, the return value should be the same as last time.
+ // If we return false here, QQuickPointerHandler::handlePointerEvent() will call setActive(false).
+ ret = point.id() == this->point().id();
+ break;
+ case QEventPoint::Unknown:
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);
+ if (!ret && point.id() == this->point().id())
+ setPressed(false, true, const_cast<QPointerEvent *>(event), const_cast<QEventPoint &>(point));
return ret;
}
-void QQuickTapHandler::handleEventPoint(QQuickEventPoint *point)
+void QQuickTapHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
{
- switch (point->state()) {
- case QQuickEventPoint::Pressed:
- setPressed(true, false, point);
+ const bool isTouch = QQuickDeliveryAgentPrivate::isTouchEvent(event);
+ switch (point.state()) {
+ case QEventPoint::Pressed:
+ setPressed(true, false, event, point);
break;
- case QQuickEventPoint::Released:
- if ((point->pointerEvent()->buttons() & acceptedButtons()) == Qt::NoButton)
- setPressed(false, false, point);
+ case QEventPoint::Released: {
+ if (isTouch || (static_cast<const QSinglePointEvent *>(event)->buttons() & acceptedButtons()) == Qt::NoButton)
+ setPressed(false, false, event, point);
break;
+ }
default:
break;
}
+
+ QQuickSinglePointHandler::handleEventPoint(event, point);
+
+ // If TapHandler only needs a passive grab, it should not block other items and handlers from reacting.
+ // If the point is accepted, QQuickItemPrivate::localizedTouchEvent() would skip it.
+ if (isTouch && m_gesturePolicy == DragThreshold)
+ point.setAccepted(false);
}
/*!
\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.
+ The time in seconds that an \l eventPoint must be pressed in order to
+ trigger a long press gesture and emit the \l longPressed() signal, if the
+ value is greater than \c 0. If the point is released before this time
+ limit, a tap can be detected if the \l gesturePolicy constraint is
+ satisfied. If \c longPressThreshold is \c 0, the timer is disabled and the
+ signal will not be emitted. If \c longPressThreshold is set to \c undefined,
+ the default value is used instead, and can be read back from this property.
+
+ The default value is QStyleHints::mousePressAndHoldInterval() converted to
+ seconds.
*/
qreal QQuickTapHandler::longPressThreshold() const
{
- return longPressThresholdMilliseconds() / 1000.0;
+ return m_longPressThreshold / qreal(1000);
}
void QQuickTapHandler::setLongPressThreshold(qreal longPressThreshold)
{
+ if (longPressThreshold < 0) {
+ resetLongPressThreshold();
+ return;
+ }
int ms = qRound(longPressThreshold * 1000);
if (m_longPressThreshold == ms)
return;
@@ -194,9 +178,14 @@ void QQuickTapHandler::setLongPressThreshold(qreal longPressThreshold)
emit longPressThresholdChanged();
}
-int QQuickTapHandler::longPressThresholdMilliseconds() const
+void QQuickTapHandler::resetLongPressThreshold()
{
- return (m_longPressThreshold < 0 ? QGuiApplication::styleHints()->mousePressAndHoldInterval() : m_longPressThreshold);
+ int ms = QGuiApplication::styleHints()->mousePressAndHoldInterval();
+ if (m_longPressThreshold == ms)
+ return;
+
+ m_longPressThreshold = ms;
+ emit longPressThresholdChanged();
}
void QQuickTapHandler::timerEvent(QTimerEvent *event)
@@ -204,7 +193,16 @@ void QQuickTapHandler::timerEvent(QTimerEvent *event)
if (event->timerId() == m_longPressTimer.timerId()) {
m_longPressTimer.stop();
qCDebug(lcTapHandler) << objectName() << "longPressed";
+ m_longPressed = true;
emit longPressed();
+ } else if (event->timerId() == m_doubleTapTimer.timerId()) {
+ m_doubleTapTimer.stop();
+ qCDebug(lcTapHandler) << objectName() << "double-tap timer expired; taps:" << m_tapCount;
+ Q_ASSERT(m_exclusiveSignals == (SingleTap | DoubleTap));
+ if (m_tapCount == 1)
+ emit singleTapped(m_singleTapReleasedPoint, m_singleTapReleasedButton);
+ else if (m_tapCount == 2)
+ emit doubleTapped(m_singleTapReleasedPoint, m_singleTapReleasedButton);
}
}
@@ -218,31 +216,98 @@ void QQuickTapHandler::timerEvent(QTimerEvent *event)
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.
+ The \c gesturePolicy also affects grab behavior as described below.
+
+ \table
+ \header
+ \li Constant
+ \li Description
+ \row
+ \li \c TapHandler.DragThreshold
+ \image pointerHandlers/tapHandlerOverlappingButtons.webp
+ Grab on press: \e passive
+ \li (the default value) The \l eventPoint 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 device 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 \l {Qt Quick Controls}), because in this case TapHandler
+ will not take the exclusive grab, but merely a
+ \l {QPointerEvent::addPassiveGrabber()}{passive grab}.
+ That is, \c DragThreshold is especially useful to \e augment
+ existing behavior: it reacts to tap/click/long-press even when
+ another item or handler is already reacting, perhaps even in a
+ different layer of the UI. The following snippet shows one
+ TapHandler as used in one component; but if we stack up two
+ instances of the component, you will see the handlers in both of them
+ react simultaneously when a press occurs over both of them, because
+ the passive grab does not stop event propagation:
+ \quotefromfile pointerHandlers/tapHandlerOverlappingButtons.qml
+ \skipto Item
+ \printuntil component Button
+ \skipto TapHandler
+ \printuntil }
+ \skipuntil Text {
+ \skipuntil }
+ \printuntil Button
+ \printuntil Button
+ \printuntil }
+
+ \row
+ \li \c TapHandler.WithinBounds
+ \image pointerHandlers/tapHandlerButtonWithinBounds.webp
+ Grab on press: \e exclusive
+ \li If the \l eventPoint leaves the bounds of the \c parent Item, the tap
+ gesture is canceled. The TapHandler will take the
+ \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on
+ press, but will release the grab as soon as the boundary constraint
+ is no longer satisfied.
+ \snippet pointerHandlers/tapHandlerButtonWithinBounds.qml 1
+
+ \row
+ \li \c TapHandler.ReleaseWithinBounds
+ \image pointerHandlers/tapHandlerButtonReleaseWithinBounds.webp
+ Grab on press: \e exclusive
+ \li At the time of release (the mouse button is released or the finger
+ is lifted), if the \l eventPoint 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 to take the
+ \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on press
+ and retain it until release in order to detect this gesture.
+ \snippet pointerHandlers/tapHandlerButtonReleaseWithinBounds.qml 1
+
+ \row
+ \li \c TapHandler.DragWithinBounds
+ \image pointerHandlers/dragReleaseMenu.webp
+ Grab on press: \e exclusive
+ \li On press, TapHandler takes the
+ \l {QPointerEvent::setExclusiveGrabber}{exclusive grab}; after that,
+ the \l eventPoint can be dragged within the bounds of the \c parent
+ item, while the \l timeHeld property keeps counting, and the
+ \l longPressed() signal will be emitted regardless of drag distance.
+ However, like \c WithinBounds, if the point leaves the bounds,
+ the tap gesture is \l {PointerHandler::}{canceled()}, \l active()
+ becomes \c false, and \l timeHeld stops counting. This is suitable
+ for implementing press-drag-release components, such as menus, in
+ which a single TapHandler detects press, \c timeHeld drives an
+ "opening" animation, and then the user can drag to a menu item and
+ release, while never leaving the bounds of the parent scene containing
+ the menu. This value was added in Qt 6.3.
+ \snippet pointerHandlers/dragReleaseMenu.qml 1
+ \endtable
+
+ The \l {Qt Quick Examples - Pointer Handlers} demonstrates some use cases for these.
+
+ \note If you find that TapHandler is reacting in cases that conflict with
+ some other behavior, the first thing you should try is to think about which
+ \c gesturePolicy is appropriate. If you cannot fix it by changing \c gesturePolicy,
+ some cases are better served by adjusting \l {PointerHandler::}{grabPermissions},
+ either in this handler, or in another handler that should \e prevent TapHandler
+ from reacting.
*/
void QQuickTapHandler::setGesturePolicy(QQuickTapHandler::GesturePolicy gesturePolicy)
{
@@ -254,23 +319,57 @@ void QQuickTapHandler::setGesturePolicy(QQuickTapHandler::GesturePolicy gestureP
}
/*!
+ \qmlproperty enumeration QtQuick::TapHandler::exclusiveSignals
+ \since 6.5
+
+ Determines the exclusivity of the singleTapped() and doubleTapped() signals.
+
+ \value NotExclusive (the default) singleTapped() and doubleTapped() are
+ emitted immediately when the user taps once or twice, respectively.
+
+ \value SingleTap singleTapped() is emitted immediately when the user taps
+ once, and doubleTapped() is never emitted.
+
+ \value DoubleTap doubleTapped() is emitted immediately when the user taps
+ twice, and singleTapped() is never emitted.
+
+ \value (SingleTap | DoubleTap) Both signals are delayed until
+ QStyleHints::mouseDoubleClickInterval(), such that either singleTapped()
+ or doubleTapped() can be emitted, but not both. But if 3 or more taps
+ occur within \c mouseDoubleClickInterval, neither signal is emitted.
+
+ \note The remaining signals such as tapped() and tapCountChanged() are
+ always emitted immediately, regardless of this property.
+*/
+void QQuickTapHandler::setExclusiveSignals(QQuickTapHandler::ExclusiveSignals exc)
+{
+ if (m_exclusiveSignals == exc)
+ return;
+
+ m_exclusiveSignals = exc;
+ emit exclusiveSignalsChanged();
+}
+
+/*!
\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
+ \l gesturePolicy. When the \l eventPoint is released or the policy is
violated, \e pressed will change to false.
*/
-void QQuickTapHandler::setPressed(bool press, bool cancel, QQuickEventPoint *point)
+void QQuickTapHandler::setPressed(bool press, bool cancel, QPointerEvent *event, QEventPoint &point)
{
if (m_pressed != press) {
- qCDebug(lcTapHandler) << objectName() << "pressed" << m_pressed << "->" << press << (cancel ? "CANCEL" : "") << point;
+ qCDebug(lcTapHandler) << objectName() << "pressed" << m_pressed << "->" << press
+ << (cancel ? "CANCEL" : "") << point << "gp" << m_gesturePolicy;
m_pressed = press;
connectPreRenderSignal(press);
updateTimeHeld();
if (press) {
- m_longPressTimer.start(longPressThresholdMilliseconds(), this);
+ if (m_longPressThreshold > 0)
+ m_longPressTimer.start(m_longPressThreshold, this);
m_holdTimer.start();
} else {
m_longPressTimer.stop();
@@ -279,64 +378,108 @@ void QQuickTapHandler::setPressed(bool press, bool cancel, QQuickEventPoint *poi
if (press) {
// on press, grab before emitting changed signals
if (m_gesturePolicy == DragThreshold)
- setPassiveGrab(point, press);
+ setPassiveGrab(event, point, press);
else
- setExclusiveGrab(point, press);
+ setExclusiveGrab(event, point, press);
}
if (!cancel && !press && parentContains(point)) {
- if (point->timeHeld() < longPressThreshold()) {
+ if (m_longPressed) {
+ qCDebug(lcTapHandler) << objectName() << "long press threshold" << longPressThreshold() << "exceeded:" << point.timeHeld();
+ } else {
// 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 ?
+ const quint64 ts = event->timestamp();
+ const quint64 interval = ts - m_lastTapTimestamp;
+ const auto distanceSquared = QVector2D(point.scenePosition() - m_lastTapPos).lengthSquared();
+ const auto singleTapReleasedButton = event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->button() : Qt::NoButton;
+ if ((interval < m_multiTapInterval && distanceSquared <
+ (event->device()->type() == QInputDevice::DeviceType::Mouse ?
m_mouseMultiClickDistanceSquared : m_touchMultiTapDistanceSquared))
+ && m_singleTapReleasedButton == singleTapReleasedButton) {
++m_tapCount;
- else
+ } else {
+ m_singleTapReleasedButton = singleTapReleasedButton;
+ m_singleTapReleasedPoint = point;
m_tapCount = 1;
- qCDebug(lcTapHandler) << objectName() << "tapped" << m_tapCount << "times";
- emit tapped(point);
+ }
+ qCDebug(lcTapHandler) << objectName() << "tapped" << m_tapCount << "times; interval since last:" << interval
+ << "sec; distance since last:" << qSqrt(distanceSquared);
+ auto button = event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->button() : Qt::NoButton;
+ emit tapped(point, button);
emit tapCountChanged();
- if (m_tapCount == 1)
- emit singleTapped(point);
- else if (m_tapCount == 2)
- emit doubleTapped(point);
+ switch (m_exclusiveSignals) {
+ case NotExclusive:
+ if (m_tapCount == 1)
+ emit singleTapped(point, button);
+ else if (m_tapCount == 2)
+ emit doubleTapped(point, button);
+ break;
+ case SingleTap:
+ if (m_tapCount == 1)
+ emit singleTapped(point, button);
+ break;
+ case DoubleTap:
+ if (m_tapCount == 2)
+ emit doubleTapped(point, button);
+ break;
+ case (SingleTap | DoubleTap):
+ if (m_tapCount == 1) {
+ qCDebug(lcTapHandler) << objectName() << "waiting to emit singleTapped:" << m_multiTapInterval << "ms";
+ m_doubleTapTimer.start(m_multiTapInterval, this);
+ }
+ }
+ qCDebug(lcTapHandler) << objectName() << "tap" << m_tapCount << "after" << event->timestamp() - m_lastTapTimestamp << "ms";
+
m_lastTapTimestamp = ts;
- m_lastTapPos = point->scenePosition();
- } else {
- qCDebug(lcTapHandler) << objectName() << "tap threshold" << longPressThreshold() << "exceeded:" << point->timeHeld();
+ m_lastTapPos = point.scenePosition();
}
}
+ m_longPressed = false;
emit pressedChanged();
if (!press && m_gesturePolicy != DragThreshold) {
// on release, ungrab after emitting changed signals
- setExclusiveGrab(point, press);
+ setExclusiveGrab(event, point, press);
}
if (cancel) {
emit canceled(point);
- setExclusiveGrab(point, false);
+ setExclusiveGrab(event, 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.
- reset();
+ d_func()->reset();
emit pointChanged();
}
}
}
-void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point)
+void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition,
+ QPointerEvent *ev, QEventPoint &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);
+ QQuickSinglePointHandler::onGrabChanged(grabber, transition, ev, point);
+ bool isCanceled = transition == QPointingDevice::CancelGrabExclusive || transition == QPointingDevice::CancelGrabPassive;
+ if (grabber == this && (isCanceled || point.state() == QEventPoint::Released))
+ setPressed(false, isCanceled, ev, 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);
+ // disconnect pre-existing connection, if any
+ disconnect(m_preRenderSignalConnection);
+
+ auto par = parentItem();
+ if (!par || !par->window())
+ return;
+
+ /*
+ Note: beforeSynchronizing is emitted from the SG thread, and the
+ timeHeldChanged signal can be used to do arbitrary things in user QML.
+
+ But the docs say the GUI thread is blockd, and "Therefore, it is safe
+ to access GUI thread thread data in a slot or lambda that is connected
+ with Qt::DirectConnection." We use the default AutoConnection just in case.
+ */
+ if (conn) {
+ m_preRenderSignalConnection = connect(par->window(), &QQuickWindow::beforeSynchronizing,
+ this, &QQuickTapHandler::updateTimeHeld);
+ }
}
void QQuickTapHandler::updateTimeHeld()
@@ -349,8 +492,8 @@ void QQuickTapHandler::updateTimeHeld()
\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:
+ constraints to be considered a single gesture. The counter is reset to 1
+ if the button changed. For example, to detect a triple-tap, you can write:
\qml
Rectangle {
@@ -377,48 +520,59 @@ void QQuickTapHandler::updateTimeHeld()
A value of less than zero means no point is being held within this
handler's \l [QML] Item.
+
+ \note If \l gesturePolicy is set to \c TapHandler.DragWithinBounds,
+ \c timeHeld does not stop counting even when the pressed point is moved
+ beyond the drag threshold, but only when the point leaves the \l {Item::}
+ {parent} item's \l {QtQuick::Item::contains()}{bounds}.
*/
/*!
- \qmlsignal QtQuick::TapHandler::tapped(EventPoint eventPoint)
+ \qmlsignal QtQuick::TapHandler::tapped(eventPoint eventPoint, Qt::MouseButton button)
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. The \c eventPoint signal parameter contains information
- from the release event about the point that was tapped:
+ of release. The \a eventPoint signal parameter contains information
+ from the release event about the point that was tapped, and \a button
+ is the \l {Qt::MouseButton}{mouse button} that was clicked, or \c NoButton
+ on a touchscreen.
\snippet pointerHandlers/tapHandlerOnTapped.qml 0
*/
/*!
- \qmlsignal QtQuick::TapHandler::singleTapped(EventPoint eventPoint)
+ \qmlsignal QtQuick::TapHandler::singleTapped(eventPoint eventPoint, Qt::MouseButton button)
\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. The \c eventPoint signal parameter contains
- information from the release event about the point that was tapped.
+ \l tapCount will increase. The \a eventPoint signal parameter contains
+ information from the release event about the point that was tapped, and
+ \a button is the \l {Qt::MouseButton}{mouse button} that was clicked, or
+ \c NoButton on a touchscreen.
*/
/*!
- \qmlsignal QtQuick::TapHandler::doubleTapped(EventPoint eventPoint)
+ \qmlsignal QtQuick::TapHandler::doubleTapped(eventPoint eventPoint, Qt::MouseButton button)
\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. The \c eventPoint
+ short span of time (QStyleHints::mouseDoubleClickInterval()) and distance
+ (QStyleHints::mouseDoubleClickDistance() or
+ QStyleHints::touchDoubleTapDistance()). This signal always occurs after
+ \l singleTapped, \l tapped, and \l tapCountChanged. The \a eventPoint
signal parameter contains information from the release event about the
- point that was tapped.
+ point that was tapped, and \a button is the
+ \l {Qt::MouseButton}{mouse button} that was clicked, or \c NoButton
+ on a touchscreen.
*/
/*!
- \qmlsignal QtQuick::TapHandler::longPressed
+ \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
@@ -428,10 +582,12 @@ void QQuickTapHandler::updateTimeHeld()
*/
/*!
- \qmlsignal QtQuick::TapHandler::tapCountChanged
+ \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
+
+#include "moc_qquicktaphandler_p.cpp"