2022-05-13 13:12:05 +00:00
|
|
|
// Copyright (C) 2017 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
#include <QtTest/QtTest>
|
|
|
|
|
|
|
|
#include <QtGui/qstylehints.h>
|
|
|
|
#include <QtQuick/qquickview.h>
|
|
|
|
#include <QtQuick/qquickitem.h>
|
|
|
|
#include <QtQuick/private/qquickpointerhandler_p.h>
|
|
|
|
#include <QtQuick/private/qquicktaphandler_p.h>
|
|
|
|
#include <qpa/qwindowsysteminterface.h>
|
|
|
|
|
|
|
|
#include <private/qquickwindow_p.h>
|
|
|
|
|
|
|
|
#include <QtQml/qqmlengine.h>
|
|
|
|
#include <QtQml/qqmlproperty.h>
|
|
|
|
|
2021-08-06 10:27:35 +00:00
|
|
|
#include <QtQuickTestUtils/private/qmlutils_p.h>
|
|
|
|
#include <QtQuickTestUtils/private/viewtestutils_p.h>
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
|
|
|
|
|
|
|
|
class tst_TapHandler : public QQmlDataTest
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
tst_TapHandler()
|
2021-08-06 10:27:35 +00:00
|
|
|
: QQmlDataTest(QT_QMLTEST_DATADIR)
|
2017-03-22 08:54:57 +00:00
|
|
|
{}
|
|
|
|
|
|
|
|
private slots:
|
2021-02-23 08:46:23 +00:00
|
|
|
void initTestCase() override;
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
void touchGesturePolicyDragThreshold();
|
|
|
|
void mouseGesturePolicyDragThreshold();
|
2018-11-07 16:48:27 +00:00
|
|
|
void touchMouseGesturePolicyDragThreshold();
|
2017-03-22 08:54:57 +00:00
|
|
|
void touchGesturePolicyWithinBounds();
|
|
|
|
void mouseGesturePolicyWithinBounds();
|
|
|
|
void touchGesturePolicyReleaseWithinBounds();
|
|
|
|
void mouseGesturePolicyReleaseWithinBounds();
|
Add TapHandler.gesturePolicy: DragWithinBounds enum value; examples
On a touchscreen, right-clicking is not directly possible; so sometimes
a long-press gesture is used as a substitute. The next thing a UI
designer would want would then be a way of showing feedback that a
long-press is in progress, rather than simply waiting for the long-press
to occur and then surprising the user with some instant action.
For example, a menu might begin to open as the user holds down the
touchpoint; but before the long-press gesture is complete, the user can
simply release, to cancel the gesture and close the menu. The timeHeld
property could drive the animation, to avoid needing a separate
animation type; in fact the reason timeHeld exists is to make it easy
to emulate this sort of touch-press animation, like one that occurs on
touchscreens since Windows 7.
But after the menu is open, the user would probably expect to be able to
drag the finger to a menu item and release, to select the menu item. For
such a purpose, the existing gesture policies weren't very useful: each
of them resets the timeHeld property if the user drags beyond the drag
threshold; so if the user expects to drag and release over a menu item,
then the timeHeld property cannot drive the menu-opening animation,
because the menu would disappear as soon as the user drags a little.
So it makes more sense to have a gesturePolicy that acts like
WithinBounds, but also applies the same policy to the timeHeld property
and the longPressed signal. We don't care about the drag threshold:
if the user is holding down a finger, it's considered to be a
long-press-in-progress, regardless of how far it has moved since press
(as long as it stays within the parent's bounds).
An example of such a menu is added. The menu must have TapHandler as
its root object, because it reacts to press-and-drag within some larger
item, larger than the menu itself. For example such a menu could be
used in a canvas-like application (drawing, diagramming, dragging things
like photos or file icons, or something like that): dragging items on
the canvas is possible, but long-pressing anywhere will open a context
menu. But in this example so far, only the menu is implemented.
It's a pie menu, because those are particularly touch-friendly; but
perhaps for the mouse, a conventional context menu would be used.
[ChangeLog][QtQuick][Event Handlers] TapHandler now has one more
gesturePolicy value: DragWithinBounds; it is similar to WithinBounds,
except that timeHeld is not reset during dragging, and the longPressed
signal can be emitted regardless of the drag threshold. This is useful
for implementing press-drag-release components such as menus, while
using timeHeld to directly drive an "opening" animation.
Change-Id: I298f8b1ad8f8d7d3c241ef4fdd68e7ec8d8b5bdd
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
2021-10-07 20:27:32 +00:00
|
|
|
void gesturePolicyDragWithinBounds_data();
|
|
|
|
void gesturePolicyDragWithinBounds();
|
2017-03-22 08:54:57 +00:00
|
|
|
void touchMultiTap();
|
2022-10-06 15:50:17 +00:00
|
|
|
void mouseMultiTap_data();
|
2017-03-22 08:54:57 +00:00
|
|
|
void mouseMultiTap();
|
2023-02-23 11:33:27 +00:00
|
|
|
void mouseMultiTapLeftRight_data();
|
|
|
|
void mouseMultiTapLeftRight();
|
2022-10-06 15:50:17 +00:00
|
|
|
void singleTapDoubleTap_data();
|
|
|
|
void singleTapDoubleTap();
|
2017-03-22 08:54:57 +00:00
|
|
|
void touchLongPress();
|
|
|
|
void mouseLongPress();
|
|
|
|
void buttonsMultiTouch();
|
2017-11-22 13:13:53 +00:00
|
|
|
void componentUserBehavioralOverride();
|
2018-12-04 11:27:23 +00:00
|
|
|
void rightLongPressIgnoreWheel();
|
2019-08-16 12:32:22 +00:00
|
|
|
void negativeZStackingOrder();
|
2021-05-04 15:42:30 +00:00
|
|
|
void nonTopLevelParentWindow();
|
QQuickItem: ignore double-clicks by default; remove allowDoubleClick
Because Qt has a pattern that events arrive pre-accepted, most
event-handling functions in QQuickItem call ignore(), and it's up to
subclasses to override those functions to allow the event to remain
accepted, if they choose to handle it. So it was odd that
QQuickItem::mouseDoubleClickEvent() did not call ignore().
Pointer handlers don't handle MouseButtonDblClick events, so
QQuickDeliveryAgent does not send those events to handlers. Since
0e3adb65b0e9c44fa6e202630ff57c907ecf0820 though, we disallowed delivery
of double-click events to Items after any handler has already accepted
the single point in a mouse event. This caused some inconsistencies; in
fact the allowDoubleClick variable was getting thrashed a lot, making it
hard to reason about the logic. Items that contained handlers behaved
differently than items that did not. One scenario being fixed here was
absurd: a parent Rectangle (which never handles pointer events on its
own) got an implicit grab in deliverMatchingPointsToItem() and thus
stole the grab from a handler (!), during delivery of a
MouseButtonDblClick (!!), just because the event happened to remain
accepted, even though no item or handler reacted to it directly.
The Rectangle needs to ignore() the event to avoid that, just as all
Items now do by default. Then it turns out that we don't need a stateful
allowDoubleClick anymore: the logic is more consistent without it.
Items can handle double-clicks, but they don't by default, as with any
other pointer event. Pointer handlers don't handle MouseButtonDblClick
because they detect double-clicks in their own way, and that's enforced
by simply not sending those events to handlers. Passive grabs should be
retained regardless of the interloper MouseButtonDblClick event: items
that handle it cannot cancel a handler's passive grab. They can steal a
handler's exclusive grab, but that should be prevented in other ways,
such as ignoring the event so that there is no accidental implicit grab.
Reverts 0e3adb65b0e9c44fa6e202630ff57c907ecf0820. DeliveryAgent no longer
calls clearPassiveGrabbers() directly as QQuickWindow did then; and it
also no longer delivers MouseButtonDblClick the same as a press event.
QSinglePointEvent::isBeginEvent() returns false in that case, so
deliverPressOrReleaseEvent() is not called.
A couple of existing tests now need to avoid generating double-clicks,
but they were not trying to test that anyway. New tests are added (test
coverage of double-clicks has been unfortunately sparse so far).
Pick-to: 6.3
Fixes: QTBUG-102625
Change-Id: If74baff68ffc46b8b403d37f4e10ddf6b159d40c
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
2022-05-30 21:04:22 +00:00
|
|
|
void nestedDoubleTap_data();
|
|
|
|
void nestedDoubleTap();
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
private:
|
2021-05-04 15:42:30 +00:00
|
|
|
void createView(QScopedPointer<QQuickView> &window, const char *fileName,
|
|
|
|
QWindow *parent = nullptr);
|
2022-10-23 08:09:55 +00:00
|
|
|
QPointingDevice *touchDevice = QTest::createTouchDevice(); // TODO const after fixing QTBUG-107864
|
2021-05-04 15:42:30 +00:00
|
|
|
void mouseEvent(QEvent::Type type, Qt::MouseButton button, const QPoint &point,
|
|
|
|
QWindow *targetWindow, QWindow *mapToWindow);
|
2017-03-22 08:54:57 +00:00
|
|
|
};
|
|
|
|
|
2021-05-04 15:42:30 +00:00
|
|
|
void tst_TapHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName,
|
|
|
|
QWindow *parent)
|
2017-03-22 08:54:57 +00:00
|
|
|
{
|
2021-05-04 15:42:30 +00:00
|
|
|
window.reset(new QQuickView(parent));
|
|
|
|
if (parent) {
|
|
|
|
parent->show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowActive(parent));
|
|
|
|
}
|
|
|
|
|
2017-03-22 08:54:57 +00:00
|
|
|
window->setSource(testFileUrl(fileName));
|
|
|
|
QTRY_COMPARE(window->status(), QQuickView::Ready);
|
2021-08-06 10:27:35 +00:00
|
|
|
QQuickViewTestUtils::centerOnScreen(window.data());
|
|
|
|
QQuickViewTestUtils::moveMouseAway(window.data());
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
window->show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowActive(window.data()));
|
2018-02-21 09:41:54 +00:00
|
|
|
QVERIFY(window->rootObject() != nullptr);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
2021-05-04 15:42:30 +00:00
|
|
|
void tst_TapHandler::mouseEvent(QEvent::Type type, Qt::MouseButton button, const QPoint &point,
|
|
|
|
QWindow *targetWindow, QWindow *mapToWindow)
|
|
|
|
{
|
|
|
|
QVERIFY(targetWindow);
|
|
|
|
QVERIFY(mapToWindow);
|
|
|
|
auto buttons = button;
|
|
|
|
if (type == QEvent::MouseButtonRelease) {
|
|
|
|
buttons = Qt::NoButton;
|
|
|
|
}
|
|
|
|
QMouseEvent me(type, point, mapToWindow->mapToGlobal(point), button, buttons,
|
|
|
|
Qt::KeyboardModifiers(), QPointingDevice::primaryPointingDevice());
|
|
|
|
QVERIFY(qApp->notify(targetWindow, &me));
|
|
|
|
}
|
|
|
|
|
2017-03-22 08:54:57 +00:00
|
|
|
void tst_TapHandler::initTestCase()
|
|
|
|
{
|
|
|
|
// This test assumes that we don't get synthesized mouse events from QGuiApplication
|
|
|
|
qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
|
|
|
|
|
|
|
|
QQmlDataTest::initTestCase();
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::touchGesturePolicyDragThreshold()
|
|
|
|
{
|
|
|
|
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(buttonDragThreshold);
|
2019-07-03 15:17:53 +00:00
|
|
|
QQuickTapHandler *tapHandler = buttonDragThreshold->findChild<QQuickTapHandler*>();
|
|
|
|
QVERIFY(tapHandler);
|
2017-03-22 08:54:57 +00:00
|
|
|
QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped()));
|
|
|
|
|
|
|
|
// DragThreshold button stays pressed while touchpoint stays within dragThreshold, emits tapped on release
|
|
|
|
QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
p1 += QPoint(dragThreshold, 0);
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(dragThresholdTappedSpy.size(), 1);
|
2019-07-03 15:17:53 +00:00
|
|
|
QCOMPARE(buttonDragThreshold->property("tappedPosition").toPoint(), p1);
|
|
|
|
QCOMPARE(tapHandler->point().position(), QPointF());
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// DragThreshold button is no longer pressed if touchpoint goes beyond dragThreshold
|
|
|
|
dragThresholdTappedSpy.clear();
|
|
|
|
p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
p1 += QPoint(dragThreshold, 0);
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
p1 += QPoint(1, 0);
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(!buttonDragThreshold->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(dragThresholdTappedSpy.size(), 0);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::mouseGesturePolicyDragThreshold()
|
|
|
|
{
|
|
|
|
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(buttonDragThreshold);
|
2019-07-03 15:17:53 +00:00
|
|
|
QQuickTapHandler *tapHandler = buttonDragThreshold->findChild<QQuickTapHandler*>();
|
|
|
|
QVERIFY(tapHandler);
|
2017-03-22 08:54:57 +00:00
|
|
|
QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped()));
|
|
|
|
|
|
|
|
// DragThreshold button stays pressed while mouse stays within dragThreshold, emits tapped on release
|
|
|
|
QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
p1 += QPoint(dragThreshold, 0);
|
|
|
|
QTest::mouseMove(window, p1);
|
|
|
|
QVERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QTRY_COMPARE(dragThresholdTappedSpy.size(), 1);
|
2019-07-03 15:17:53 +00:00
|
|
|
QCOMPARE(buttonDragThreshold->property("tappedPosition").toPoint(), p1);
|
|
|
|
QCOMPARE(tapHandler->point().position(), QPointF());
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// DragThreshold button is no longer pressed if mouse goes beyond dragThreshold
|
|
|
|
dragThresholdTappedSpy.clear();
|
|
|
|
p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
p1 += QPoint(dragThreshold, 0);
|
|
|
|
QTest::mouseMove(window, p1);
|
|
|
|
QVERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
p1 += QPoint(1, 0);
|
|
|
|
QTest::mouseMove(window, p1);
|
|
|
|
QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QVERIFY(!buttonDragThreshold->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(dragThresholdTappedSpy.size(), 0);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
2018-11-07 16:48:27 +00:00
|
|
|
void tst_TapHandler::touchMouseGesturePolicyDragThreshold()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(buttonDragThreshold);
|
|
|
|
QSignalSpy tappedSpy(buttonDragThreshold, SIGNAL(tapped()));
|
|
|
|
QSignalSpy canceledSpy(buttonDragThreshold, SIGNAL(canceled()));
|
|
|
|
|
|
|
|
// Press mouse, drag it outside the button, release
|
|
|
|
QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QPoint p2 = p1 + QPoint(int(buttonDragThreshold->height()), 0);
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QTest::mouseMove(window, p2);
|
2022-10-05 05:29:16 +00:00
|
|
|
QTRY_COMPARE(canceledSpy.size(), 1);
|
|
|
|
QCOMPARE(tappedSpy.size(), 0);
|
2018-11-07 16:48:27 +00:00
|
|
|
QCOMPARE(buttonDragThreshold->property("pressed").toBool(), false);
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p2);
|
|
|
|
|
|
|
|
// Press and release touch, verify that it still works (QTBUG-71466)
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 1);
|
2018-11-07 16:48:27 +00:00
|
|
|
|
|
|
|
// Press touch, drag it outside the button, release
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p2, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_COMPARE(buttonDragThreshold->property("pressed").toBool(), false);
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p2, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
2022-10-05 05:29:16 +00:00
|
|
|
QTRY_COMPARE(canceledSpy.size(), 2);
|
|
|
|
QCOMPARE(tappedSpy.size(), 1); // didn't increase
|
2018-11-07 16:48:27 +00:00
|
|
|
QCOMPARE(buttonDragThreshold->property("pressed").toBool(), false);
|
|
|
|
|
|
|
|
// Press and release mouse, verify that it still works
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
|
2022-10-05 05:29:16 +00:00
|
|
|
QTRY_COMPARE(tappedSpy.size(), 2);
|
|
|
|
QCOMPARE(canceledSpy.size(), 2); // didn't increase
|
2018-11-07 16:48:27 +00:00
|
|
|
QCOMPARE(buttonDragThreshold->property("pressed").toBool(), false);
|
|
|
|
}
|
|
|
|
|
2017-03-22 08:54:57 +00:00
|
|
|
void tst_TapHandler::touchGesturePolicyWithinBounds()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>("WithinBounds");
|
|
|
|
QVERIFY(buttonWithinBounds);
|
|
|
|
QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped()));
|
|
|
|
|
|
|
|
// WithinBounds button stays pressed while touchpoint stays within bounds, emits tapped on release
|
|
|
|
QPoint p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(50, 0);
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(withinBoundsTappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// WithinBounds button is no longer pressed if touchpoint leaves bounds
|
|
|
|
withinBoundsTappedSpy.clear();
|
|
|
|
p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(0, 100);
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(!buttonWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(withinBoundsTappedSpy.size(), 0);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::mouseGesturePolicyWithinBounds()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>("WithinBounds");
|
|
|
|
QVERIFY(buttonWithinBounds);
|
|
|
|
QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped()));
|
|
|
|
|
|
|
|
// WithinBounds button stays pressed while touchpoint stays within bounds, emits tapped on release
|
|
|
|
QPoint p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(50, 0);
|
|
|
|
QTest::mouseMove(window, p1);
|
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(withinBoundsTappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// WithinBounds button is no longer pressed if touchpoint leaves bounds
|
|
|
|
withinBoundsTappedSpy.clear();
|
|
|
|
p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(0, 100);
|
|
|
|
QTest::mouseMove(window, p1);
|
|
|
|
QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QVERIFY(!buttonWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(withinBoundsTappedSpy.size(), 0);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::touchGesturePolicyReleaseWithinBounds()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>("ReleaseWithinBounds");
|
|
|
|
QVERIFY(buttonReleaseWithinBounds);
|
|
|
|
QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped()));
|
|
|
|
|
|
|
|
// ReleaseWithinBounds button stays pressed while touchpoint wanders anywhere,
|
|
|
|
// then if it comes back within bounds, emits tapped on release
|
|
|
|
QPoint p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(50, 0);
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(250, 100);
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
p1 = buttonReleaseWithinBounds->mapToScene(QPointF(25, 15)).toPoint();
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(releaseWithinBoundsTappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// ReleaseWithinBounds button does not emit tapped if released out of bounds
|
|
|
|
releaseWithinBoundsTappedSpy.clear();
|
|
|
|
p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(0, 100);
|
|
|
|
QTest::touchEvent(window, touchDevice).move(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(releaseWithinBoundsTappedSpy.size(), 0);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::mouseGesturePolicyReleaseWithinBounds()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>("ReleaseWithinBounds");
|
|
|
|
QVERIFY(buttonReleaseWithinBounds);
|
|
|
|
QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped()));
|
|
|
|
|
|
|
|
// ReleaseWithinBounds button stays pressed while touchpoint wanders anywhere,
|
|
|
|
// then if it comes back within bounds, emits tapped on release
|
|
|
|
QPoint p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(50, 0);
|
|
|
|
QTest::mouseMove(window, p1);
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(250, 100);
|
|
|
|
QTest::mouseMove(window, p1);
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
p1 = buttonReleaseWithinBounds->mapToScene(QPointF(25, 15)).toPoint();
|
|
|
|
QTest::mouseMove(window, p1);
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(releaseWithinBoundsTappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// ReleaseWithinBounds button does not emit tapped if released out of bounds
|
|
|
|
releaseWithinBoundsTappedSpy.clear();
|
|
|
|
p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
p1 += QPoint(0, 100);
|
|
|
|
QTest::mouseMove(window, p1);
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(releaseWithinBoundsTappedSpy.size(), 0);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
Add TapHandler.gesturePolicy: DragWithinBounds enum value; examples
On a touchscreen, right-clicking is not directly possible; so sometimes
a long-press gesture is used as a substitute. The next thing a UI
designer would want would then be a way of showing feedback that a
long-press is in progress, rather than simply waiting for the long-press
to occur and then surprising the user with some instant action.
For example, a menu might begin to open as the user holds down the
touchpoint; but before the long-press gesture is complete, the user can
simply release, to cancel the gesture and close the menu. The timeHeld
property could drive the animation, to avoid needing a separate
animation type; in fact the reason timeHeld exists is to make it easy
to emulate this sort of touch-press animation, like one that occurs on
touchscreens since Windows 7.
But after the menu is open, the user would probably expect to be able to
drag the finger to a menu item and release, to select the menu item. For
such a purpose, the existing gesture policies weren't very useful: each
of them resets the timeHeld property if the user drags beyond the drag
threshold; so if the user expects to drag and release over a menu item,
then the timeHeld property cannot drive the menu-opening animation,
because the menu would disappear as soon as the user drags a little.
So it makes more sense to have a gesturePolicy that acts like
WithinBounds, but also applies the same policy to the timeHeld property
and the longPressed signal. We don't care about the drag threshold:
if the user is holding down a finger, it's considered to be a
long-press-in-progress, regardless of how far it has moved since press
(as long as it stays within the parent's bounds).
An example of such a menu is added. The menu must have TapHandler as
its root object, because it reacts to press-and-drag within some larger
item, larger than the menu itself. For example such a menu could be
used in a canvas-like application (drawing, diagramming, dragging things
like photos or file icons, or something like that): dragging items on
the canvas is possible, but long-pressing anywhere will open a context
menu. But in this example so far, only the menu is implemented.
It's a pie menu, because those are particularly touch-friendly; but
perhaps for the mouse, a conventional context menu would be used.
[ChangeLog][QtQuick][Event Handlers] TapHandler now has one more
gesturePolicy value: DragWithinBounds; it is similar to WithinBounds,
except that timeHeld is not reset during dragging, and the longPressed
signal can be emitted regardless of the drag threshold. This is useful
for implementing press-drag-release components such as menus, while
using timeHeld to directly drive an "opening" animation.
Change-Id: I298f8b1ad8f8d7d3c241ef4fdd68e7ec8d8b5bdd
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
2021-10-07 20:27:32 +00:00
|
|
|
void tst_TapHandler::gesturePolicyDragWithinBounds_data()
|
|
|
|
{
|
2022-10-18 11:28:52 +00:00
|
|
|
QTest::addColumn<const QPointingDevice *>("device");
|
Add TapHandler.gesturePolicy: DragWithinBounds enum value; examples
On a touchscreen, right-clicking is not directly possible; so sometimes
a long-press gesture is used as a substitute. The next thing a UI
designer would want would then be a way of showing feedback that a
long-press is in progress, rather than simply waiting for the long-press
to occur and then surprising the user with some instant action.
For example, a menu might begin to open as the user holds down the
touchpoint; but before the long-press gesture is complete, the user can
simply release, to cancel the gesture and close the menu. The timeHeld
property could drive the animation, to avoid needing a separate
animation type; in fact the reason timeHeld exists is to make it easy
to emulate this sort of touch-press animation, like one that occurs on
touchscreens since Windows 7.
But after the menu is open, the user would probably expect to be able to
drag the finger to a menu item and release, to select the menu item. For
such a purpose, the existing gesture policies weren't very useful: each
of them resets the timeHeld property if the user drags beyond the drag
threshold; so if the user expects to drag and release over a menu item,
then the timeHeld property cannot drive the menu-opening animation,
because the menu would disappear as soon as the user drags a little.
So it makes more sense to have a gesturePolicy that acts like
WithinBounds, but also applies the same policy to the timeHeld property
and the longPressed signal. We don't care about the drag threshold:
if the user is holding down a finger, it's considered to be a
long-press-in-progress, regardless of how far it has moved since press
(as long as it stays within the parent's bounds).
An example of such a menu is added. The menu must have TapHandler as
its root object, because it reacts to press-and-drag within some larger
item, larger than the menu itself. For example such a menu could be
used in a canvas-like application (drawing, diagramming, dragging things
like photos or file icons, or something like that): dragging items on
the canvas is possible, but long-pressing anywhere will open a context
menu. But in this example so far, only the menu is implemented.
It's a pie menu, because those are particularly touch-friendly; but
perhaps for the mouse, a conventional context menu would be used.
[ChangeLog][QtQuick][Event Handlers] TapHandler now has one more
gesturePolicy value: DragWithinBounds; it is similar to WithinBounds,
except that timeHeld is not reset during dragging, and the longPressed
signal can be emitted regardless of the drag threshold. This is useful
for implementing press-drag-release components such as menus, while
using timeHeld to directly drive an "opening" animation.
Change-Id: I298f8b1ad8f8d7d3c241ef4fdd68e7ec8d8b5bdd
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
2021-10-07 20:27:32 +00:00
|
|
|
QTest::addColumn<QPoint>("dragStart");
|
|
|
|
QTest::addColumn<QPoint>("dragDistance");
|
|
|
|
QTest::addColumn<QString>("expectedFeedback");
|
|
|
|
|
2022-10-23 08:09:55 +00:00
|
|
|
const QPointingDevice *constTouchDevice = touchDevice;
|
|
|
|
|
2022-10-18 11:28:52 +00:00
|
|
|
QTest::newRow("mouse: click") << QPointingDevice::primaryPointingDevice() << QPoint(200, 200) << QPoint(0, 0) << "middle";
|
2022-10-23 08:09:55 +00:00
|
|
|
QTest::newRow("touch: tap") << constTouchDevice << QPoint(200, 200) << QPoint(0, 0) << "middle";
|
2022-10-18 11:28:52 +00:00
|
|
|
QTest::newRow("mouse: drag up") << QPointingDevice::primaryPointingDevice() << QPoint(200, 200) << QPoint(0, -20) << "top";
|
2022-10-23 08:09:55 +00:00
|
|
|
QTest::newRow("touch: drag up") << constTouchDevice << QPoint(200, 200) << QPoint(0, -20) << "top";
|
2022-10-18 11:28:52 +00:00
|
|
|
QTest::newRow("mouse: drag out to cancel") << QPointingDevice::primaryPointingDevice() << QPoint(435, 200) << QPoint(10, 0) << "canceled";
|
2022-10-23 08:09:55 +00:00
|
|
|
QTest::newRow("touch: drag out to cancel") << constTouchDevice << QPoint(435, 200) << QPoint(10, 0) << "canceled";
|
Add TapHandler.gesturePolicy: DragWithinBounds enum value; examples
On a touchscreen, right-clicking is not directly possible; so sometimes
a long-press gesture is used as a substitute. The next thing a UI
designer would want would then be a way of showing feedback that a
long-press is in progress, rather than simply waiting for the long-press
to occur and then surprising the user with some instant action.
For example, a menu might begin to open as the user holds down the
touchpoint; but before the long-press gesture is complete, the user can
simply release, to cancel the gesture and close the menu. The timeHeld
property could drive the animation, to avoid needing a separate
animation type; in fact the reason timeHeld exists is to make it easy
to emulate this sort of touch-press animation, like one that occurs on
touchscreens since Windows 7.
But after the menu is open, the user would probably expect to be able to
drag the finger to a menu item and release, to select the menu item. For
such a purpose, the existing gesture policies weren't very useful: each
of them resets the timeHeld property if the user drags beyond the drag
threshold; so if the user expects to drag and release over a menu item,
then the timeHeld property cannot drive the menu-opening animation,
because the menu would disappear as soon as the user drags a little.
So it makes more sense to have a gesturePolicy that acts like
WithinBounds, but also applies the same policy to the timeHeld property
and the longPressed signal. We don't care about the drag threshold:
if the user is holding down a finger, it's considered to be a
long-press-in-progress, regardless of how far it has moved since press
(as long as it stays within the parent's bounds).
An example of such a menu is added. The menu must have TapHandler as
its root object, because it reacts to press-and-drag within some larger
item, larger than the menu itself. For example such a menu could be
used in a canvas-like application (drawing, diagramming, dragging things
like photos or file icons, or something like that): dragging items on
the canvas is possible, but long-pressing anywhere will open a context
menu. But in this example so far, only the menu is implemented.
It's a pie menu, because those are particularly touch-friendly; but
perhaps for the mouse, a conventional context menu would be used.
[ChangeLog][QtQuick][Event Handlers] TapHandler now has one more
gesturePolicy value: DragWithinBounds; it is similar to WithinBounds,
except that timeHeld is not reset during dragging, and the longPressed
signal can be emitted regardless of the drag threshold. This is useful
for implementing press-drag-release components such as menus, while
using timeHeld to directly drive an "opening" animation.
Change-Id: I298f8b1ad8f8d7d3c241ef4fdd68e7ec8d8b5bdd
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
2021-10-07 20:27:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::gesturePolicyDragWithinBounds()
|
|
|
|
{
|
2022-10-18 11:28:52 +00:00
|
|
|
QFETCH(const QPointingDevice *, device);
|
Add TapHandler.gesturePolicy: DragWithinBounds enum value; examples
On a touchscreen, right-clicking is not directly possible; so sometimes
a long-press gesture is used as a substitute. The next thing a UI
designer would want would then be a way of showing feedback that a
long-press is in progress, rather than simply waiting for the long-press
to occur and then surprising the user with some instant action.
For example, a menu might begin to open as the user holds down the
touchpoint; but before the long-press gesture is complete, the user can
simply release, to cancel the gesture and close the menu. The timeHeld
property could drive the animation, to avoid needing a separate
animation type; in fact the reason timeHeld exists is to make it easy
to emulate this sort of touch-press animation, like one that occurs on
touchscreens since Windows 7.
But after the menu is open, the user would probably expect to be able to
drag the finger to a menu item and release, to select the menu item. For
such a purpose, the existing gesture policies weren't very useful: each
of them resets the timeHeld property if the user drags beyond the drag
threshold; so if the user expects to drag and release over a menu item,
then the timeHeld property cannot drive the menu-opening animation,
because the menu would disappear as soon as the user drags a little.
So it makes more sense to have a gesturePolicy that acts like
WithinBounds, but also applies the same policy to the timeHeld property
and the longPressed signal. We don't care about the drag threshold:
if the user is holding down a finger, it's considered to be a
long-press-in-progress, regardless of how far it has moved since press
(as long as it stays within the parent's bounds).
An example of such a menu is added. The menu must have TapHandler as
its root object, because it reacts to press-and-drag within some larger
item, larger than the menu itself. For example such a menu could be
used in a canvas-like application (drawing, diagramming, dragging things
like photos or file icons, or something like that): dragging items on
the canvas is possible, but long-pressing anywhere will open a context
menu. But in this example so far, only the menu is implemented.
It's a pie menu, because those are particularly touch-friendly; but
perhaps for the mouse, a conventional context menu would be used.
[ChangeLog][QtQuick][Event Handlers] TapHandler now has one more
gesturePolicy value: DragWithinBounds; it is similar to WithinBounds,
except that timeHeld is not reset during dragging, and the longPressed
signal can be emitted regardless of the drag threshold. This is useful
for implementing press-drag-release components such as menus, while
using timeHeld to directly drive an "opening" animation.
Change-Id: I298f8b1ad8f8d7d3c241ef4fdd68e7ec8d8b5bdd
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
2021-10-07 20:27:32 +00:00
|
|
|
QFETCH(QPoint, dragStart);
|
|
|
|
QFETCH(QPoint, dragDistance);
|
|
|
|
QFETCH(QString, expectedFeedback);
|
|
|
|
const bool expectedCanceled = expectedFeedback == "canceled";
|
|
|
|
|
|
|
|
QQuickView window;
|
|
|
|
QVERIFY(QQuickTest::showView(window, testFileUrl("dragReleaseMenu.qml")));
|
|
|
|
QQuickTapHandler *tapHandler = window.rootObject()->findChild<QQuickTapHandler*>();
|
|
|
|
QVERIFY(tapHandler);
|
|
|
|
QSignalSpy canceledSpy(tapHandler, &QQuickTapHandler::canceled);
|
|
|
|
|
2022-10-18 11:28:52 +00:00
|
|
|
QQuickTest::pointerPress(device, &window, 0, dragStart);
|
|
|
|
QTRY_VERIFY(tapHandler->isPressed());
|
|
|
|
QQuickTest::pointerMove(device, &window, 0, dragStart + dragDistance);
|
|
|
|
if (expectedCanceled)
|
|
|
|
QTRY_COMPARE(tapHandler->timeHeld(), -1);
|
|
|
|
else
|
|
|
|
QTRY_VERIFY(tapHandler->timeHeld() > 0.1);
|
|
|
|
QQuickTest::pointerRelease(device, &window, 0, dragStart + dragDistance);
|
Add TapHandler.gesturePolicy: DragWithinBounds enum value; examples
On a touchscreen, right-clicking is not directly possible; so sometimes
a long-press gesture is used as a substitute. The next thing a UI
designer would want would then be a way of showing feedback that a
long-press is in progress, rather than simply waiting for the long-press
to occur and then surprising the user with some instant action.
For example, a menu might begin to open as the user holds down the
touchpoint; but before the long-press gesture is complete, the user can
simply release, to cancel the gesture and close the menu. The timeHeld
property could drive the animation, to avoid needing a separate
animation type; in fact the reason timeHeld exists is to make it easy
to emulate this sort of touch-press animation, like one that occurs on
touchscreens since Windows 7.
But after the menu is open, the user would probably expect to be able to
drag the finger to a menu item and release, to select the menu item. For
such a purpose, the existing gesture policies weren't very useful: each
of them resets the timeHeld property if the user drags beyond the drag
threshold; so if the user expects to drag and release over a menu item,
then the timeHeld property cannot drive the menu-opening animation,
because the menu would disappear as soon as the user drags a little.
So it makes more sense to have a gesturePolicy that acts like
WithinBounds, but also applies the same policy to the timeHeld property
and the longPressed signal. We don't care about the drag threshold:
if the user is holding down a finger, it's considered to be a
long-press-in-progress, regardless of how far it has moved since press
(as long as it stays within the parent's bounds).
An example of such a menu is added. The menu must have TapHandler as
its root object, because it reacts to press-and-drag within some larger
item, larger than the menu itself. For example such a menu could be
used in a canvas-like application (drawing, diagramming, dragging things
like photos or file icons, or something like that): dragging items on
the canvas is possible, but long-pressing anywhere will open a context
menu. But in this example so far, only the menu is implemented.
It's a pie menu, because those are particularly touch-friendly; but
perhaps for the mouse, a conventional context menu would be used.
[ChangeLog][QtQuick][Event Handlers] TapHandler now has one more
gesturePolicy value: DragWithinBounds; it is similar to WithinBounds,
except that timeHeld is not reset during dragging, and the longPressed
signal can be emitted regardless of the drag threshold. This is useful
for implementing press-drag-release components such as menus, while
using timeHeld to directly drive an "opening" animation.
Change-Id: I298f8b1ad8f8d7d3c241ef4fdd68e7ec8d8b5bdd
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
2021-10-07 20:27:32 +00:00
|
|
|
|
|
|
|
QCOMPARE(window.rootObject()->property("feedbackText"), expectedFeedback);
|
|
|
|
if (expectedCanceled)
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(canceledSpy.size(), 1);
|
Add TapHandler.gesturePolicy: DragWithinBounds enum value; examples
On a touchscreen, right-clicking is not directly possible; so sometimes
a long-press gesture is used as a substitute. The next thing a UI
designer would want would then be a way of showing feedback that a
long-press is in progress, rather than simply waiting for the long-press
to occur and then surprising the user with some instant action.
For example, a menu might begin to open as the user holds down the
touchpoint; but before the long-press gesture is complete, the user can
simply release, to cancel the gesture and close the menu. The timeHeld
property could drive the animation, to avoid needing a separate
animation type; in fact the reason timeHeld exists is to make it easy
to emulate this sort of touch-press animation, like one that occurs on
touchscreens since Windows 7.
But after the menu is open, the user would probably expect to be able to
drag the finger to a menu item and release, to select the menu item. For
such a purpose, the existing gesture policies weren't very useful: each
of them resets the timeHeld property if the user drags beyond the drag
threshold; so if the user expects to drag and release over a menu item,
then the timeHeld property cannot drive the menu-opening animation,
because the menu would disappear as soon as the user drags a little.
So it makes more sense to have a gesturePolicy that acts like
WithinBounds, but also applies the same policy to the timeHeld property
and the longPressed signal. We don't care about the drag threshold:
if the user is holding down a finger, it's considered to be a
long-press-in-progress, regardless of how far it has moved since press
(as long as it stays within the parent's bounds).
An example of such a menu is added. The menu must have TapHandler as
its root object, because it reacts to press-and-drag within some larger
item, larger than the menu itself. For example such a menu could be
used in a canvas-like application (drawing, diagramming, dragging things
like photos or file icons, or something like that): dragging items on
the canvas is possible, but long-pressing anywhere will open a context
menu. But in this example so far, only the menu is implemented.
It's a pie menu, because those are particularly touch-friendly; but
perhaps for the mouse, a conventional context menu would be used.
[ChangeLog][QtQuick][Event Handlers] TapHandler now has one more
gesturePolicy value: DragWithinBounds; it is similar to WithinBounds,
except that timeHeld is not reset during dragging, and the longPressed
signal can be emitted regardless of the drag threshold. This is useful
for implementing press-drag-release components such as menus, while
using timeHeld to directly drive an "opening" animation.
Change-Id: I298f8b1ad8f8d7d3c241ef4fdd68e7ec8d8b5bdd
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
2021-10-07 20:27:32 +00:00
|
|
|
}
|
|
|
|
|
2017-03-22 08:54:57 +00:00
|
|
|
void tst_TapHandler::touchMultiTap()
|
|
|
|
{
|
|
|
|
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(button);
|
|
|
|
QSignalSpy tappedSpy(button, SIGNAL(tapped()));
|
|
|
|
|
|
|
|
// Tap once
|
|
|
|
QPoint p1 = button->mapToScene(QPointF(2, 2)).toPoint();
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(button->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!button->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// Tap again in exactly the same place (not likely with touch in the real world)
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(button->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!button->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 2);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// Tap a third time, nearby
|
|
|
|
p1 += QPoint(dragThreshold, dragThreshold);
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(button->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!button->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 3);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// Tap a fourth time, drifting farther away
|
|
|
|
p1 += QPoint(dragThreshold, dragThreshold);
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(button->property("pressed").toBool());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!button->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 4);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
2022-10-06 15:50:17 +00:00
|
|
|
void tst_TapHandler::mouseMultiTap_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<QQuickTapHandler::ExclusiveSignals>("exclusiveSignals");
|
|
|
|
QTest::addColumn<int>("expectedSingleTaps");
|
|
|
|
QTest::addColumn<int>("expectedSingleTapsAfterMovingAway");
|
|
|
|
QTest::addColumn<int>("expectedSingleTapsAfterWaiting");
|
|
|
|
QTest::addColumn<int>("expectedDoubleTaps");
|
|
|
|
|
|
|
|
QTest::newRow("NotExclusive") << QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::NotExclusive)
|
|
|
|
<< 1 << 2 << 3 << 1;
|
|
|
|
QTest::newRow("SingleTap") << QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::SingleTap)
|
|
|
|
<< 1 << 2 << 3 << 0;
|
|
|
|
QTest::newRow("DoubleTap") << QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::DoubleTap)
|
|
|
|
<< 0 << 0 << 0 << 1;
|
|
|
|
QTest::newRow("SingleTap|DoubleTap") << QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::SingleTap | QQuickTapHandler::DoubleTap)
|
|
|
|
<< 0 << 0 << 0 << 0;
|
|
|
|
}
|
|
|
|
|
2017-03-22 08:54:57 +00:00
|
|
|
void tst_TapHandler::mouseMultiTap()
|
|
|
|
{
|
2022-10-06 15:50:17 +00:00
|
|
|
QFETCH(QQuickTapHandler::ExclusiveSignals, exclusiveSignals);
|
|
|
|
QFETCH(int, expectedSingleTaps);
|
|
|
|
QFETCH(int, expectedSingleTapsAfterMovingAway);
|
|
|
|
QFETCH(int, expectedSingleTapsAfterWaiting);
|
|
|
|
QFETCH(int, expectedDoubleTaps);
|
|
|
|
|
2017-03-22 08:54:57 +00:00
|
|
|
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(button);
|
2022-10-06 15:50:17 +00:00
|
|
|
QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>();
|
|
|
|
QVERIFY(tapHandler);
|
|
|
|
tapHandler->setExclusiveSignals(exclusiveSignals);
|
2017-03-22 08:54:57 +00:00
|
|
|
QSignalSpy tappedSpy(button, SIGNAL(tapped()));
|
2022-10-06 15:50:17 +00:00
|
|
|
QSignalSpy singleTapSpy(tapHandler, &QQuickTapHandler::singleTapped);
|
|
|
|
QSignalSpy doubleTapSpy(tapHandler, &QQuickTapHandler::doubleTapped);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
2022-10-06 15:50:17 +00:00
|
|
|
// Click once
|
2017-03-22 08:54:57 +00:00
|
|
|
QPoint p1 = button->mapToScene(QPointF(2, 2)).toPoint();
|
2022-10-06 15:50:17 +00:00
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1, 10);
|
2017-03-22 08:54:57 +00:00
|
|
|
QTRY_VERIFY(button->property("pressed").toBool());
|
2022-10-06 15:50:17 +00:00
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1, 10);
|
2017-03-22 08:54:57 +00:00
|
|
|
QTRY_VERIFY(!button->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 1);
|
2022-10-06 15:50:17 +00:00
|
|
|
// If exclusiveSignals == SingleTap | DoubleTap:
|
|
|
|
// This would be a single-click if we waited longer than the double-click interval,
|
|
|
|
// but it's too early for the signal at this moment; and we're going to click again.
|
|
|
|
// If exclusiveSignals == DoubleTap: singleTapped() won't happen.
|
|
|
|
// Otherwise: we got singleTapped() immediately.
|
|
|
|
QCOMPARE(singleTapSpy.size(), expectedSingleTaps);
|
|
|
|
QCOMPARE(tapHandler->timeHeld(), -1);
|
|
|
|
|
|
|
|
// Click again in exactly the same place
|
|
|
|
QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, p1, 10);
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 2);
|
2022-10-06 15:50:17 +00:00
|
|
|
QCOMPARE(singleTapSpy.size(), expectedSingleTaps);
|
|
|
|
QCOMPARE(doubleTapSpy.size(), expectedDoubleTaps);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
2022-10-06 15:50:17 +00:00
|
|
|
// Click a third time, nearby: that'll be a triple-click
|
|
|
|
p1 += QPoint(1, 1);
|
|
|
|
QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, p1, 10);
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 3);
|
2022-10-06 15:50:17 +00:00
|
|
|
QCOMPARE(singleTapSpy.size(), expectedSingleTaps);
|
|
|
|
QCOMPARE(doubleTapSpy.size(), expectedDoubleTaps);
|
|
|
|
QCOMPARE(tapHandler->tapCount(), 3);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
2022-10-06 15:50:17 +00:00
|
|
|
// Click a fourth time, drifting farther away: treated as a separate click, regardless of timing
|
2017-03-22 08:54:57 +00:00
|
|
|
p1 += QPoint(dragThreshold, dragThreshold);
|
2022-10-06 15:50:17 +00:00
|
|
|
QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, p1); // default delay to prevent double-click
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 4);
|
2022-10-06 15:50:17 +00:00
|
|
|
QCOMPARE(tapHandler->tapCount(), 1);
|
|
|
|
QTRY_COMPARE(singleTapSpy.size(), expectedSingleTapsAfterMovingAway);
|
|
|
|
|
|
|
|
// Click a fifth time later on at the same place: treated as a separate click
|
|
|
|
QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QCOMPARE(tappedSpy.size(), 5);
|
|
|
|
QCOMPARE(tapHandler->tapCount(), 1);
|
|
|
|
QCOMPARE(singleTapSpy.size(), expectedSingleTapsAfterWaiting);
|
|
|
|
}
|
|
|
|
|
2023-02-23 11:33:27 +00:00
|
|
|
void tst_TapHandler::mouseMultiTapLeftRight_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<QQuickTapHandler::ExclusiveSignals>("exclusiveSignals");
|
|
|
|
QTest::addColumn<int>("expectedSingleTaps");
|
|
|
|
QTest::addColumn<int>("expectedDoubleTaps");
|
|
|
|
QTest::addColumn<int>("expectedTabCount2");
|
|
|
|
QTest::addColumn<int>("expectedTabCount3");
|
|
|
|
|
|
|
|
QTest::newRow("NotExclusive") << QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::NotExclusive)
|
|
|
|
<< 3 << 0 << 1 << 1;
|
|
|
|
QTest::newRow("SingleTap") << QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::SingleTap)
|
|
|
|
<< 3 << 0 << 1 << 1;
|
|
|
|
QTest::newRow("DoubleTap") << QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::DoubleTap)
|
|
|
|
<< 0 << 0 << 1 << 1;
|
|
|
|
QTest::newRow("SingleTap|DoubleTap") << QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::SingleTap | QQuickTapHandler::DoubleTap)
|
|
|
|
<< 0 << 0 << 1 << 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::mouseMultiTapLeftRight() //QTBUG-111557
|
|
|
|
{
|
|
|
|
QFETCH(QQuickTapHandler::ExclusiveSignals, exclusiveSignals);
|
|
|
|
QFETCH(int, expectedSingleTaps);
|
|
|
|
QFETCH(int, expectedDoubleTaps);
|
|
|
|
QFETCH(int, expectedTabCount2);
|
|
|
|
QFETCH(int, expectedTabCount3);
|
|
|
|
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(button);
|
|
|
|
QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>();
|
|
|
|
QVERIFY(tapHandler);
|
|
|
|
tapHandler->setExclusiveSignals(exclusiveSignals);
|
|
|
|
tapHandler->setAcceptedButtons(Qt::LeftButton | Qt::RightButton);
|
|
|
|
QSignalSpy tappedSpy(button, SIGNAL(tapped()));
|
|
|
|
QSignalSpy singleTapSpy(tapHandler, &QQuickTapHandler::singleTapped);
|
|
|
|
QSignalSpy doubleTapSpy(tapHandler, &QQuickTapHandler::doubleTapped);
|
|
|
|
|
|
|
|
// Click once with the left button
|
|
|
|
QPoint p1 = button->mapToScene(QPointF(2, 2)).toPoint();
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1, 10);
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1, 10);
|
|
|
|
|
|
|
|
// Click again with the right button -> should reset tabCount()
|
|
|
|
QTest::mousePress(window, Qt::RightButton, Qt::NoModifier, p1, 10);
|
|
|
|
QTest::mouseRelease(window, Qt::RightButton, Qt::NoModifier, p1, 10);
|
|
|
|
|
|
|
|
QCOMPARE(tapHandler->tapCount(), expectedTabCount2);
|
|
|
|
|
|
|
|
// Click again with the left button -> should reset tabCount()
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1, 10);
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1, 10);
|
|
|
|
|
|
|
|
QCOMPARE(tapHandler->tapCount(), expectedTabCount3);
|
|
|
|
QCOMPARE(singleTapSpy.size(), expectedSingleTaps);
|
|
|
|
QCOMPARE(doubleTapSpy.size(), expectedDoubleTaps);
|
|
|
|
}
|
|
|
|
|
2022-10-06 15:50:17 +00:00
|
|
|
void tst_TapHandler::singleTapDoubleTap_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<QPointingDevice::DeviceType>("deviceType");
|
|
|
|
QTest::addColumn<QQuickTapHandler::ExclusiveSignals>("exclusiveSignals");
|
|
|
|
QTest::addColumn<int>("expectedEndingSingleTapCount");
|
|
|
|
QTest::addColumn<int>("expectedDoubleTapCount");
|
|
|
|
|
|
|
|
QTest::newRow("mouse:NotExclusive")
|
|
|
|
<< QPointingDevice::DeviceType::Mouse
|
|
|
|
<< QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::NotExclusive)
|
|
|
|
<< 1 << 1;
|
|
|
|
QTest::newRow("mouse:SingleTap")
|
|
|
|
<< QPointingDevice::DeviceType::Mouse
|
|
|
|
<< QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::SingleTap)
|
|
|
|
<< 1 << 0;
|
|
|
|
QTest::newRow("mouse:DoubleTap")
|
|
|
|
<< QPointingDevice::DeviceType::Mouse
|
|
|
|
<< QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::DoubleTap)
|
|
|
|
<< 0 << 1;
|
|
|
|
QTest::newRow("mouse:SingleTap|DoubleTap")
|
|
|
|
<< QPointingDevice::DeviceType::Mouse
|
|
|
|
<< QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::SingleTap | QQuickTapHandler::DoubleTap)
|
|
|
|
<< 0 << 1;
|
|
|
|
QTest::newRow("touch:NotExclusive")
|
|
|
|
<< QPointingDevice::DeviceType::TouchScreen
|
|
|
|
<< QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::NotExclusive)
|
|
|
|
<< 1 << 1;
|
|
|
|
QTest::newRow("touch:SingleTap")
|
|
|
|
<< QPointingDevice::DeviceType::TouchScreen
|
|
|
|
<< QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::SingleTap)
|
|
|
|
<< 1 << 0;
|
|
|
|
QTest::newRow("touch:DoubleTap")
|
|
|
|
<< QPointingDevice::DeviceType::TouchScreen
|
|
|
|
<< QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::DoubleTap)
|
|
|
|
<< 0 << 1;
|
|
|
|
QTest::newRow("touch:SingleTap|DoubleTap")
|
|
|
|
<< QPointingDevice::DeviceType::TouchScreen
|
|
|
|
<< QQuickTapHandler::ExclusiveSignals(QQuickTapHandler::SingleTap | QQuickTapHandler::DoubleTap)
|
|
|
|
<< 0 << 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::singleTapDoubleTap()
|
|
|
|
{
|
|
|
|
QFETCH(QPointingDevice::DeviceType, deviceType);
|
|
|
|
QFETCH(QQuickTapHandler::ExclusiveSignals, exclusiveSignals);
|
|
|
|
QFETCH(int, expectedEndingSingleTapCount);
|
|
|
|
QFETCH(int, expectedDoubleTapCount);
|
|
|
|
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(button);
|
|
|
|
QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>();
|
|
|
|
QVERIFY(tapHandler);
|
|
|
|
tapHandler->setExclusiveSignals(exclusiveSignals);
|
|
|
|
QSignalSpy tappedSpy(tapHandler, &QQuickTapHandler::tapped);
|
|
|
|
QSignalSpy singleTapSpy(tapHandler, &QQuickTapHandler::singleTapped);
|
|
|
|
QSignalSpy doubleTapSpy(tapHandler, &QQuickTapHandler::doubleTapped);
|
|
|
|
|
|
|
|
auto tap = [window, tapHandler, deviceType, this](const QPoint &p1) {
|
|
|
|
switch (static_cast<QPointingDevice::DeviceType>(deviceType)) {
|
|
|
|
case QPointingDevice::DeviceType::Mouse:
|
|
|
|
QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, p1, 10);
|
|
|
|
break;
|
|
|
|
case QPointingDevice::DeviceType::TouchScreen:
|
|
|
|
QTest::touchEvent(window, touchDevice).press(0, p1, window);
|
|
|
|
QTRY_VERIFY(tapHandler->isPressed());
|
|
|
|
QTest::touchEvent(window, touchDevice).release(0, p1, window);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// tap once
|
|
|
|
const QPoint p1 = button->mapToScene(QPointF(2, 2)).toPoint();
|
|
|
|
tap(p1);
|
|
|
|
QCOMPARE(tappedSpy.size(), 1);
|
|
|
|
QCOMPARE(doubleTapSpy.size(), 0);
|
|
|
|
|
|
|
|
// tap again immediately afterwards
|
|
|
|
tap(p1);
|
|
|
|
QTRY_COMPARE(doubleTapSpy.size(), expectedDoubleTapCount);
|
|
|
|
QCOMPARE(tappedSpy.size(), 2);
|
|
|
|
QCOMPARE(singleTapSpy.size(), expectedEndingSingleTapCount);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::touchLongPress()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(button);
|
|
|
|
QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>("DragThreshold");
|
|
|
|
QVERIFY(tapHandler);
|
|
|
|
QSignalSpy tappedSpy(button, SIGNAL(tapped()));
|
|
|
|
QSignalSpy longPressThresholdChangedSpy(tapHandler, SIGNAL(longPressThresholdChanged()));
|
|
|
|
QSignalSpy timeHeldSpy(tapHandler, SIGNAL(timeHeldChanged()));
|
|
|
|
QSignalSpy longPressedSpy(tapHandler, SIGNAL(longPressed()));
|
|
|
|
|
|
|
|
// Reduce the threshold so that we can get a long press quickly
|
|
|
|
tapHandler->setLongPressThreshold(0.5);
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(longPressThresholdChangedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// Press and hold
|
|
|
|
QPoint p1 = button->mapToScene(button->clipRect().center()).toPoint();
|
|
|
|
QTest::touchEvent(window, touchDevice).press(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(button->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QTRY_COMPARE(longPressedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
timeHeldSpy.wait(); // the longer we hold it, the more this will occur
|
2022-10-05 05:29:16 +00:00
|
|
|
qDebug() << "held" << tapHandler->timeHeld() << "secs; timeHeld updated" << timeHeldSpy.size() << "times";
|
|
|
|
QVERIFY(timeHeldSpy.size() > 0);
|
2017-04-24 17:12:42 +00:00
|
|
|
QVERIFY(tapHandler->timeHeld() > 0.4); // Should be > 0.5 but slow CI and timer granularity can interfere
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// Release and verify that tapped was not emitted
|
|
|
|
QTest::touchEvent(window, touchDevice).release(1, p1, window);
|
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!button->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 0);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::mouseLongPress()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(button);
|
|
|
|
QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>("DragThreshold");
|
|
|
|
QVERIFY(tapHandler);
|
|
|
|
QSignalSpy tappedSpy(button, SIGNAL(tapped()));
|
|
|
|
QSignalSpy longPressThresholdChangedSpy(tapHandler, SIGNAL(longPressThresholdChanged()));
|
|
|
|
QSignalSpy timeHeldSpy(tapHandler, SIGNAL(timeHeldChanged()));
|
|
|
|
QSignalSpy longPressedSpy(tapHandler, SIGNAL(longPressed()));
|
|
|
|
|
|
|
|
// Reduce the threshold so that we can get a long press quickly
|
|
|
|
tapHandler->setLongPressThreshold(0.5);
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(longPressThresholdChangedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// Press and hold
|
|
|
|
QPoint p1 = button->mapToScene(button->clipRect().center()).toPoint();
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_VERIFY(button->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QTRY_COMPARE(longPressedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
timeHeldSpy.wait(); // the longer we hold it, the more this will occur
|
2022-10-05 05:29:16 +00:00
|
|
|
qDebug() << "held" << tapHandler->timeHeld() << "secs; timeHeld updated" << timeHeldSpy.size() << "times";
|
|
|
|
QVERIFY(timeHeldSpy.size() > 0);
|
2017-04-24 17:12:42 +00:00
|
|
|
QVERIFY(tapHandler->timeHeld() > 0.4); // Should be > 0.5 but slow CI and timer granularity can interfere
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// Release and verify that tapped was not emitted
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1, 500);
|
|
|
|
QTRY_VERIFY(!button->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 0);
|
2017-03-22 08:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::buttonsMultiTouch()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttons.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
|
|
|
|
QVERIFY(buttonDragThreshold);
|
|
|
|
QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped()));
|
|
|
|
|
|
|
|
QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>("WithinBounds");
|
|
|
|
QVERIFY(buttonWithinBounds);
|
|
|
|
QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped()));
|
|
|
|
|
|
|
|
QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>("ReleaseWithinBounds");
|
|
|
|
QVERIFY(buttonReleaseWithinBounds);
|
|
|
|
QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped()));
|
2018-07-03 11:59:12 +00:00
|
|
|
QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, touchDevice, false);
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// can press multiple buttons at the same time
|
|
|
|
QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
|
2018-07-03 11:59:12 +00:00
|
|
|
touchSeq.press(1, p1, window).commit();
|
2017-03-22 08:54:57 +00:00
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QPoint p2 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
2018-07-03 11:59:12 +00:00
|
|
|
touchSeq.stationary(1).press(2, p2, window).commit();
|
2017-03-22 08:54:57 +00:00
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
|
2019-12-05 11:46:25 +00:00
|
|
|
QVERIFY(buttonWithinBounds->property("active").toBool());
|
2017-03-22 08:54:57 +00:00
|
|
|
QPoint p3 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
|
2018-07-03 11:59:12 +00:00
|
|
|
touchSeq.stationary(1).stationary(2).press(3, p3, window).commit();
|
2017-03-22 08:54:57 +00:00
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
2019-12-05 11:46:25 +00:00
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("active").toBool());
|
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonWithinBounds->property("active").toBool());
|
|
|
|
QVERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
|
|
|
|
// combinations of small touchpoint movements and stationary points should not cause state changes
|
|
|
|
p1 += QPoint(2, 0);
|
|
|
|
p2 += QPoint(3, 0);
|
|
|
|
touchSeq.move(1, p1).move(2, p2).stationary(3).commit();
|
|
|
|
QVERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonWithinBounds->property("active").toBool());
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("active").toBool());
|
|
|
|
p3 += QPoint(4, 0);
|
|
|
|
touchSeq.stationary(1).stationary(2).move(3, p3).commit();
|
|
|
|
QVERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonWithinBounds->property("active").toBool());
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("active").toBool());
|
2017-03-22 08:54:57 +00:00
|
|
|
|
|
|
|
// can release top button and press again: others stay pressed the whole time
|
2018-07-03 11:59:12 +00:00
|
|
|
touchSeq.stationary(2).stationary(3).release(1, p1, window).commit();
|
2017-03-22 08:54:57 +00:00
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(dragThresholdTappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(withinBoundsTappedSpy.size(), 0);
|
2017-03-22 08:54:57 +00:00
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(releaseWithinBoundsTappedSpy.size(), 0);
|
2018-07-03 11:59:12 +00:00
|
|
|
touchSeq.stationary(2).stationary(3).press(1, p1, window).commit();
|
2017-03-22 08:54:57 +00:00
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
|
|
|
|
// can release middle button and press again: others stay pressed the whole time
|
2018-07-03 11:59:12 +00:00
|
|
|
touchSeq.stationary(1).stationary(3).release(2, p2, window).commit();
|
2017-03-22 08:54:57 +00:00
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(withinBoundsTappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
QVERIFY(buttonDragThreshold->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(dragThresholdTappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(releaseWithinBoundsTappedSpy.size(), 0);
|
2018-07-03 11:59:12 +00:00
|
|
|
touchSeq.stationary(1).stationary(3).press(2, p2, window).commit();
|
2017-03-22 08:54:57 +00:00
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QVERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
|
|
|
|
// can release bottom button and press again: others stay pressed the whole time
|
2018-07-03 11:59:12 +00:00
|
|
|
touchSeq.stationary(1).stationary(2).release(3, p3, window).commit();
|
2017-03-22 08:54:57 +00:00
|
|
|
QQuickTouchUtils::flush(window);
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(releaseWithinBoundsTappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(withinBoundsTappedSpy.size(), 1);
|
2017-03-22 08:54:57 +00:00
|
|
|
QVERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(dragThresholdTappedSpy.size(), 1);
|
2018-07-03 11:59:12 +00:00
|
|
|
touchSeq.stationary(1).stationary(2).press(3, p3, window).commit();
|
2017-03-22 08:54:57 +00:00
|
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonWithinBounds->property("pressed").toBool());
|
|
|
|
QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
|
|
|
|
}
|
|
|
|
|
2017-11-22 13:13:53 +00:00
|
|
|
void tst_TapHandler::componentUserBehavioralOverride()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "buttonOverrideHandler.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("Overridden");
|
|
|
|
QVERIFY(button);
|
|
|
|
QQuickTapHandler *innerTapHandler = button->findChild<QQuickTapHandler*>("Overridden");
|
|
|
|
QVERIFY(innerTapHandler);
|
|
|
|
QQuickTapHandler *userTapHandler = button->findChild<QQuickTapHandler*>("override");
|
|
|
|
QVERIFY(userTapHandler);
|
|
|
|
QSignalSpy tappedSpy(button, SIGNAL(tapped()));
|
2020-11-23 11:08:26 +00:00
|
|
|
QSignalSpy innerGrabChangedSpy(innerTapHandler, SIGNAL(grabChanged(QPointingDevice::GrabTransition, QEventPoint)));
|
|
|
|
QSignalSpy userGrabChangedSpy(userTapHandler, SIGNAL(grabChanged(QPointingDevice::GrabTransition, QEventPoint)));
|
2017-11-22 13:13:53 +00:00
|
|
|
QSignalSpy innerPressedChangedSpy(innerTapHandler, SIGNAL(pressedChanged()));
|
|
|
|
QSignalSpy userPressedChangedSpy(userTapHandler, SIGNAL(pressedChanged()));
|
|
|
|
|
|
|
|
// Press
|
|
|
|
QPoint p1 = button->mapToScene(button->clipRect().center()).toPoint();
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
|
2022-10-05 05:29:16 +00:00
|
|
|
QTRY_COMPARE(userPressedChangedSpy.size(), 1);
|
|
|
|
QCOMPARE(innerPressedChangedSpy.size(), 0);
|
|
|
|
QCOMPARE(innerGrabChangedSpy.size(), 0);
|
|
|
|
QCOMPARE(userGrabChangedSpy.size(), 1);
|
2017-11-22 13:13:53 +00:00
|
|
|
|
|
|
|
// Release
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
|
2022-10-05 05:29:16 +00:00
|
|
|
QTRY_COMPARE(userPressedChangedSpy.size(), 2);
|
|
|
|
QCOMPARE(innerPressedChangedSpy.size(), 0);
|
|
|
|
QCOMPARE(tappedSpy.size(), 1); // only because the override handler makes that happen
|
|
|
|
QCOMPARE(innerGrabChangedSpy.size(), 0);
|
|
|
|
QCOMPARE(userGrabChangedSpy.size(), 2);
|
2017-11-22 13:13:53 +00:00
|
|
|
}
|
|
|
|
|
2018-12-04 11:27:23 +00:00
|
|
|
void tst_TapHandler::rightLongPressIgnoreWheel()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "rightTapHandler.qml");
|
|
|
|
QQuickView * window = windowPtr.data();
|
|
|
|
|
|
|
|
QQuickTapHandler *tap = window->rootObject()->findChild<QQuickTapHandler*>();
|
|
|
|
QVERIFY(tap);
|
Add button argument to the TapHandler.[single|double|]tapped signals
It would be better to emit the whole pointer event (by pointer because
it's non-copyable, or make it copyable and emit by value), but we can't.
So we just add the button being tapped; more information is available
from the eventpoint argument and TapHandler's point property.
To avoid name clashes with anything that's already called "button" in
anyone's QML (which is quite likely, actually), the new signal argument
is unnamed, so that users will be required to write a function signature
that gives it a name rather than relying on context injection.
[ChangeLog][QtQuick][Event Handlers] TapHandler's tapped(), singleTapped()
and doubleTapped() signals now have two arguments: the QEventPoint instance,
and the button being tapped. If you need it, you should write an explicit
function for the signal handler: onTapped: function(point, button) { ... }
or onDoubleTapped: (point, button)=> ...
Fixes: QTBUG-91350
Task-number: QTBUG-64847
Pick-to: 6.2 6.2.0
Change-Id: I6d25300cbfceb56f27452eac4b29b66bd1b2a41a
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2021-09-07 14:54:44 +00:00
|
|
|
QSignalSpy tappedSpy(tap, &QQuickTapHandler::tapped);
|
|
|
|
QSignalSpy longPressedSpy(tap, &QQuickTapHandler::longPressed);
|
2018-12-04 11:27:23 +00:00
|
|
|
QPoint p1(100, 100);
|
|
|
|
|
|
|
|
// Mouse wheel with ScrollBegin phase (because as soon as two fingers are touching
|
|
|
|
// the trackpad, it will send such an event: QTBUG-71955)
|
|
|
|
{
|
|
|
|
QWheelEvent wheelEvent(p1, p1, QPoint(0, 0), QPoint(0, 0),
|
|
|
|
Qt::NoButton, Qt::NoModifier, Qt::ScrollBegin, false, Qt::MouseEventNotSynthesized);
|
|
|
|
QGuiApplication::sendEvent(window, &wheelEvent);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Press
|
|
|
|
QTest::mousePress(window, Qt::RightButton, Qt::NoModifier, p1);
|
|
|
|
QTRY_COMPARE(tap->isPressed(), true);
|
|
|
|
|
|
|
|
// Mouse wheel ScrollEnd phase
|
|
|
|
QWheelEvent wheelEvent(p1, p1, QPoint(0, 0), QPoint(0, 0),
|
|
|
|
Qt::NoButton, Qt::NoModifier, Qt::ScrollEnd, false, Qt::MouseEventNotSynthesized);
|
|
|
|
QGuiApplication::sendEvent(window, &wheelEvent);
|
2022-10-05 05:29:16 +00:00
|
|
|
QTRY_COMPARE(longPressedSpy.size(), 1);
|
2018-12-04 11:27:23 +00:00
|
|
|
QCOMPARE(tap->isPressed(), true);
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 0);
|
2018-12-04 11:27:23 +00:00
|
|
|
|
|
|
|
// Release
|
|
|
|
QTest::mouseRelease(window, Qt::RightButton, Qt::NoModifier, p1, 500);
|
|
|
|
QTRY_COMPARE(tap->isPressed(), false);
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(tappedSpy.size(), 0);
|
2018-12-04 11:27:23 +00:00
|
|
|
}
|
|
|
|
|
2019-08-16 12:32:22 +00:00
|
|
|
void tst_TapHandler::negativeZStackingOrder() // QTBUG-83114
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
QQuickItem: ignore double-clicks by default; remove allowDoubleClick
Because Qt has a pattern that events arrive pre-accepted, most
event-handling functions in QQuickItem call ignore(), and it's up to
subclasses to override those functions to allow the event to remain
accepted, if they choose to handle it. So it was odd that
QQuickItem::mouseDoubleClickEvent() did not call ignore().
Pointer handlers don't handle MouseButtonDblClick events, so
QQuickDeliveryAgent does not send those events to handlers. Since
0e3adb65b0e9c44fa6e202630ff57c907ecf0820 though, we disallowed delivery
of double-click events to Items after any handler has already accepted
the single point in a mouse event. This caused some inconsistencies; in
fact the allowDoubleClick variable was getting thrashed a lot, making it
hard to reason about the logic. Items that contained handlers behaved
differently than items that did not. One scenario being fixed here was
absurd: a parent Rectangle (which never handles pointer events on its
own) got an implicit grab in deliverMatchingPointsToItem() and thus
stole the grab from a handler (!), during delivery of a
MouseButtonDblClick (!!), just because the event happened to remain
accepted, even though no item or handler reacted to it directly.
The Rectangle needs to ignore() the event to avoid that, just as all
Items now do by default. Then it turns out that we don't need a stateful
allowDoubleClick anymore: the logic is more consistent without it.
Items can handle double-clicks, but they don't by default, as with any
other pointer event. Pointer handlers don't handle MouseButtonDblClick
because they detect double-clicks in their own way, and that's enforced
by simply not sending those events to handlers. Passive grabs should be
retained regardless of the interloper MouseButtonDblClick event: items
that handle it cannot cancel a handler's passive grab. They can steal a
handler's exclusive grab, but that should be prevented in other ways,
such as ignoring the event so that there is no accidental implicit grab.
Reverts 0e3adb65b0e9c44fa6e202630ff57c907ecf0820. DeliveryAgent no longer
calls clearPassiveGrabbers() directly as QQuickWindow did then; and it
also no longer delivers MouseButtonDblClick the same as a press event.
QSinglePointEvent::isBeginEvent() returns false in that case, so
deliverPressOrReleaseEvent() is not called.
A couple of existing tests now need to avoid generating double-clicks,
but they were not trying to test that anyway. New tests are added (test
coverage of double-clicks has been unfortunately sparse so far).
Pick-to: 6.3
Fixes: QTBUG-102625
Change-Id: If74baff68ffc46b8b403d37f4e10ddf6b159d40c
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
2022-05-30 21:04:22 +00:00
|
|
|
createView(windowPtr, "nested.qml");
|
2019-08-16 12:32:22 +00:00
|
|
|
QQuickView *window = windowPtr.data();
|
|
|
|
QQuickItem *root = window->rootObject();
|
|
|
|
|
|
|
|
QQuickTapHandler *parentTapHandler = window->rootObject()->findChild<QQuickTapHandler*>("parentTapHandler");
|
|
|
|
QVERIFY(parentTapHandler != nullptr);
|
|
|
|
QSignalSpy clickSpyParent(parentTapHandler, &QQuickTapHandler::tapped);
|
|
|
|
QQuickTapHandler *childTapHandler = window->rootObject()->findChild<QQuickTapHandler*>("childTapHandler");
|
|
|
|
QVERIFY(childTapHandler != nullptr);
|
|
|
|
QSignalSpy clickSpyChild(childTapHandler, &QQuickTapHandler::tapped);
|
|
|
|
|
|
|
|
QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, QPoint(150, 100));
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(clickSpyChild.size(), 1);
|
|
|
|
QCOMPARE(clickSpyParent.size(), 1);
|
2019-08-16 12:32:22 +00:00
|
|
|
auto order = root->property("taps").toList();
|
|
|
|
QVERIFY(order.at(0) == "childTapHandler");
|
|
|
|
QVERIFY(order.at(1) == "parentTapHandler");
|
|
|
|
|
|
|
|
// Now change stacking order and try again.
|
|
|
|
childTapHandler->parentItem()->setZ(-1);
|
|
|
|
root->setProperty("taps", QVariantList());
|
|
|
|
QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, QPoint(150, 100));
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(clickSpyChild.size(), 2);
|
|
|
|
QCOMPARE(clickSpyParent.size(), 2);
|
2019-08-16 12:32:22 +00:00
|
|
|
order = root->property("taps").toList();
|
|
|
|
QVERIFY(order.at(0) == "parentTapHandler");
|
|
|
|
QVERIFY(order.at(1) == "childTapHandler");
|
|
|
|
}
|
|
|
|
|
2021-05-04 15:42:30 +00:00
|
|
|
void tst_TapHandler::nonTopLevelParentWindow() // QTBUG-91716
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickWindow> parentWindowPtr(new QQuickWindow);
|
|
|
|
auto parentWindow = parentWindowPtr.get();
|
|
|
|
parentWindow->setGeometry(400, 400, 250, 250);
|
|
|
|
|
|
|
|
QScopedPointer<QQuickView> windowPtr;
|
|
|
|
createView(windowPtr, "simpleTapHandler.qml", parentWindow);
|
|
|
|
auto window = windowPtr.get();
|
|
|
|
window->setGeometry(10, 10, 100, 100);
|
|
|
|
|
|
|
|
QQuickItem *root = window->rootObject();
|
|
|
|
|
|
|
|
auto p1 = QPoint(20, 20);
|
|
|
|
mouseEvent(QEvent::MouseButtonPress, Qt::LeftButton, p1, window, parentWindow);
|
|
|
|
mouseEvent(QEvent::MouseButtonRelease, Qt::LeftButton, p1, window, parentWindow);
|
|
|
|
|
|
|
|
QCOMPARE(root->property("tapCount").toInt(), 1);
|
|
|
|
|
|
|
|
QTest::touchEvent(window, touchDevice).press(0, p1, parentWindow).commit();
|
|
|
|
QTest::touchEvent(window, touchDevice).release(0, p1, parentWindow).commit();
|
|
|
|
|
|
|
|
QCOMPARE(root->property("tapCount").toInt(), 2);
|
|
|
|
}
|
|
|
|
|
QQuickItem: ignore double-clicks by default; remove allowDoubleClick
Because Qt has a pattern that events arrive pre-accepted, most
event-handling functions in QQuickItem call ignore(), and it's up to
subclasses to override those functions to allow the event to remain
accepted, if they choose to handle it. So it was odd that
QQuickItem::mouseDoubleClickEvent() did not call ignore().
Pointer handlers don't handle MouseButtonDblClick events, so
QQuickDeliveryAgent does not send those events to handlers. Since
0e3adb65b0e9c44fa6e202630ff57c907ecf0820 though, we disallowed delivery
of double-click events to Items after any handler has already accepted
the single point in a mouse event. This caused some inconsistencies; in
fact the allowDoubleClick variable was getting thrashed a lot, making it
hard to reason about the logic. Items that contained handlers behaved
differently than items that did not. One scenario being fixed here was
absurd: a parent Rectangle (which never handles pointer events on its
own) got an implicit grab in deliverMatchingPointsToItem() and thus
stole the grab from a handler (!), during delivery of a
MouseButtonDblClick (!!), just because the event happened to remain
accepted, even though no item or handler reacted to it directly.
The Rectangle needs to ignore() the event to avoid that, just as all
Items now do by default. Then it turns out that we don't need a stateful
allowDoubleClick anymore: the logic is more consistent without it.
Items can handle double-clicks, but they don't by default, as with any
other pointer event. Pointer handlers don't handle MouseButtonDblClick
because they detect double-clicks in their own way, and that's enforced
by simply not sending those events to handlers. Passive grabs should be
retained regardless of the interloper MouseButtonDblClick event: items
that handle it cannot cancel a handler's passive grab. They can steal a
handler's exclusive grab, but that should be prevented in other ways,
such as ignoring the event so that there is no accidental implicit grab.
Reverts 0e3adb65b0e9c44fa6e202630ff57c907ecf0820. DeliveryAgent no longer
calls clearPassiveGrabbers() directly as QQuickWindow did then; and it
also no longer delivers MouseButtonDblClick the same as a press event.
QSinglePointEvent::isBeginEvent() returns false in that case, so
deliverPressOrReleaseEvent() is not called.
A couple of existing tests now need to avoid generating double-clicks,
but they were not trying to test that anyway. New tests are added (test
coverage of double-clicks has been unfortunately sparse so far).
Pick-to: 6.3
Fixes: QTBUG-102625
Change-Id: If74baff68ffc46b8b403d37f4e10ddf6b159d40c
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
2022-05-30 21:04:22 +00:00
|
|
|
void tst_TapHandler::nestedDoubleTap_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<QQuickTapHandler::GesturePolicy>("childGesturePolicy");
|
|
|
|
|
|
|
|
QTest::newRow("DragThreshold") << QQuickTapHandler::GesturePolicy::DragThreshold;
|
|
|
|
QTest::newRow("WithinBounds") << QQuickTapHandler::GesturePolicy::WithinBounds;
|
|
|
|
QTest::newRow("ReleaseWithinBounds") << QQuickTapHandler::GesturePolicy::ReleaseWithinBounds;
|
|
|
|
QTest::newRow("DragWithinBounds") << QQuickTapHandler::GesturePolicy::DragWithinBounds;
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_TapHandler::nestedDoubleTap() // QTBUG-102625
|
|
|
|
{
|
|
|
|
QFETCH(QQuickTapHandler::GesturePolicy, childGesturePolicy);
|
|
|
|
|
|
|
|
QQuickView window;
|
|
|
|
QVERIFY(QQuickTest::showView(window, testFileUrl("nested.qml")));
|
|
|
|
QQuickItem *root = window.rootObject();
|
|
|
|
QQuickTapHandler *parentTapHandler = root->findChild<QQuickTapHandler*>("parentTapHandler");
|
|
|
|
QVERIFY(parentTapHandler);
|
|
|
|
QSignalSpy parentSpy(parentTapHandler, &QQuickTapHandler::doubleTapped);
|
|
|
|
QQuickTapHandler *childTapHandler = root->findChild<QQuickTapHandler*>("childTapHandler");
|
|
|
|
QVERIFY(childTapHandler);
|
|
|
|
QSignalSpy childSpy(childTapHandler, &QQuickTapHandler::doubleTapped);
|
|
|
|
childTapHandler->setGesturePolicy(childGesturePolicy);
|
|
|
|
|
|
|
|
QTest::mouseDClick(&window, Qt::LeftButton, Qt::NoModifier, QPoint(150, 100));
|
|
|
|
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(childSpy.size(), 1);
|
QQuickItem: ignore double-clicks by default; remove allowDoubleClick
Because Qt has a pattern that events arrive pre-accepted, most
event-handling functions in QQuickItem call ignore(), and it's up to
subclasses to override those functions to allow the event to remain
accepted, if they choose to handle it. So it was odd that
QQuickItem::mouseDoubleClickEvent() did not call ignore().
Pointer handlers don't handle MouseButtonDblClick events, so
QQuickDeliveryAgent does not send those events to handlers. Since
0e3adb65b0e9c44fa6e202630ff57c907ecf0820 though, we disallowed delivery
of double-click events to Items after any handler has already accepted
the single point in a mouse event. This caused some inconsistencies; in
fact the allowDoubleClick variable was getting thrashed a lot, making it
hard to reason about the logic. Items that contained handlers behaved
differently than items that did not. One scenario being fixed here was
absurd: a parent Rectangle (which never handles pointer events on its
own) got an implicit grab in deliverMatchingPointsToItem() and thus
stole the grab from a handler (!), during delivery of a
MouseButtonDblClick (!!), just because the event happened to remain
accepted, even though no item or handler reacted to it directly.
The Rectangle needs to ignore() the event to avoid that, just as all
Items now do by default. Then it turns out that we don't need a stateful
allowDoubleClick anymore: the logic is more consistent without it.
Items can handle double-clicks, but they don't by default, as with any
other pointer event. Pointer handlers don't handle MouseButtonDblClick
because they detect double-clicks in their own way, and that's enforced
by simply not sending those events to handlers. Passive grabs should be
retained regardless of the interloper MouseButtonDblClick event: items
that handle it cannot cancel a handler's passive grab. They can steal a
handler's exclusive grab, but that should be prevented in other ways,
such as ignoring the event so that there is no accidental implicit grab.
Reverts 0e3adb65b0e9c44fa6e202630ff57c907ecf0820. DeliveryAgent no longer
calls clearPassiveGrabbers() directly as QQuickWindow did then; and it
also no longer delivers MouseButtonDblClick the same as a press event.
QSinglePointEvent::isBeginEvent() returns false in that case, so
deliverPressOrReleaseEvent() is not called.
A couple of existing tests now need to avoid generating double-clicks,
but they were not trying to test that anyway. New tests are added (test
coverage of double-clicks has been unfortunately sparse so far).
Pick-to: 6.3
Fixes: QTBUG-102625
Change-Id: If74baff68ffc46b8b403d37f4e10ddf6b159d40c
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
2022-05-30 21:04:22 +00:00
|
|
|
// If the child gets by with a passive grab, both handlers see tap and double-tap.
|
|
|
|
// If the child takes an exclusive grab and stops event propagation, the parent doesn't see them.
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(parentSpy.size(),
|
QQuickItem: ignore double-clicks by default; remove allowDoubleClick
Because Qt has a pattern that events arrive pre-accepted, most
event-handling functions in QQuickItem call ignore(), and it's up to
subclasses to override those functions to allow the event to remain
accepted, if they choose to handle it. So it was odd that
QQuickItem::mouseDoubleClickEvent() did not call ignore().
Pointer handlers don't handle MouseButtonDblClick events, so
QQuickDeliveryAgent does not send those events to handlers. Since
0e3adb65b0e9c44fa6e202630ff57c907ecf0820 though, we disallowed delivery
of double-click events to Items after any handler has already accepted
the single point in a mouse event. This caused some inconsistencies; in
fact the allowDoubleClick variable was getting thrashed a lot, making it
hard to reason about the logic. Items that contained handlers behaved
differently than items that did not. One scenario being fixed here was
absurd: a parent Rectangle (which never handles pointer events on its
own) got an implicit grab in deliverMatchingPointsToItem() and thus
stole the grab from a handler (!), during delivery of a
MouseButtonDblClick (!!), just because the event happened to remain
accepted, even though no item or handler reacted to it directly.
The Rectangle needs to ignore() the event to avoid that, just as all
Items now do by default. Then it turns out that we don't need a stateful
allowDoubleClick anymore: the logic is more consistent without it.
Items can handle double-clicks, but they don't by default, as with any
other pointer event. Pointer handlers don't handle MouseButtonDblClick
because they detect double-clicks in their own way, and that's enforced
by simply not sending those events to handlers. Passive grabs should be
retained regardless of the interloper MouseButtonDblClick event: items
that handle it cannot cancel a handler's passive grab. They can steal a
handler's exclusive grab, but that should be prevented in other ways,
such as ignoring the event so that there is no accidental implicit grab.
Reverts 0e3adb65b0e9c44fa6e202630ff57c907ecf0820. DeliveryAgent no longer
calls clearPassiveGrabbers() directly as QQuickWindow did then; and it
also no longer delivers MouseButtonDblClick the same as a press event.
QSinglePointEvent::isBeginEvent() returns false in that case, so
deliverPressOrReleaseEvent() is not called.
A couple of existing tests now need to avoid generating double-clicks,
but they were not trying to test that anyway. New tests are added (test
coverage of double-clicks has been unfortunately sparse so far).
Pick-to: 6.3
Fixes: QTBUG-102625
Change-Id: If74baff68ffc46b8b403d37f4e10ddf6b159d40c
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
2022-05-30 21:04:22 +00:00
|
|
|
childGesturePolicy == QQuickTapHandler::GesturePolicy::DragThreshold ? 1 : 0);
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(root->property("taps").toList().size(),
|
QQuickItem: ignore double-clicks by default; remove allowDoubleClick
Because Qt has a pattern that events arrive pre-accepted, most
event-handling functions in QQuickItem call ignore(), and it's up to
subclasses to override those functions to allow the event to remain
accepted, if they choose to handle it. So it was odd that
QQuickItem::mouseDoubleClickEvent() did not call ignore().
Pointer handlers don't handle MouseButtonDblClick events, so
QQuickDeliveryAgent does not send those events to handlers. Since
0e3adb65b0e9c44fa6e202630ff57c907ecf0820 though, we disallowed delivery
of double-click events to Items after any handler has already accepted
the single point in a mouse event. This caused some inconsistencies; in
fact the allowDoubleClick variable was getting thrashed a lot, making it
hard to reason about the logic. Items that contained handlers behaved
differently than items that did not. One scenario being fixed here was
absurd: a parent Rectangle (which never handles pointer events on its
own) got an implicit grab in deliverMatchingPointsToItem() and thus
stole the grab from a handler (!), during delivery of a
MouseButtonDblClick (!!), just because the event happened to remain
accepted, even though no item or handler reacted to it directly.
The Rectangle needs to ignore() the event to avoid that, just as all
Items now do by default. Then it turns out that we don't need a stateful
allowDoubleClick anymore: the logic is more consistent without it.
Items can handle double-clicks, but they don't by default, as with any
other pointer event. Pointer handlers don't handle MouseButtonDblClick
because they detect double-clicks in their own way, and that's enforced
by simply not sending those events to handlers. Passive grabs should be
retained regardless of the interloper MouseButtonDblClick event: items
that handle it cannot cancel a handler's passive grab. They can steal a
handler's exclusive grab, but that should be prevented in other ways,
such as ignoring the event so that there is no accidental implicit grab.
Reverts 0e3adb65b0e9c44fa6e202630ff57c907ecf0820. DeliveryAgent no longer
calls clearPassiveGrabbers() directly as QQuickWindow did then; and it
also no longer delivers MouseButtonDblClick the same as a press event.
QSinglePointEvent::isBeginEvent() returns false in that case, so
deliverPressOrReleaseEvent() is not called.
A couple of existing tests now need to avoid generating double-clicks,
but they were not trying to test that anyway. New tests are added (test
coverage of double-clicks has been unfortunately sparse so far).
Pick-to: 6.3
Fixes: QTBUG-102625
Change-Id: If74baff68ffc46b8b403d37f4e10ddf6b159d40c
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
2022-05-30 21:04:22 +00:00
|
|
|
childGesturePolicy == QQuickTapHandler::GesturePolicy::DragThreshold ? 4 : 2);
|
|
|
|
}
|
|
|
|
|
2017-03-22 08:54:57 +00:00
|
|
|
QTEST_MAIN(tst_TapHandler)
|
|
|
|
|
|
|
|
#include "tst_qquicktaphandler.moc"
|
|
|
|
|