diff options
Diffstat (limited to 'src/quick/handlers/qquicktaphandler.cpp')
-rw-r--r-- | src/quick/handlers/qquicktaphandler.cpp | 482 |
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" |