Implement support for passive grabbers with mouse/touch events
Send mouse and touch events through passive grabbers. With support for passive grabbers, the drawer can now make itself a passive grabber for the mouse when it is pressed inside the margin area. It will then see the update events that are delivered to the exclusive grabber, and can try to grab the mouse when the e.g. move has progressed enough. Then the earlier grabber gets an ungrab event that allows it to cancel the action. Done-with: Doris Verria <doris.verria@qt.io> Fixes: QTBUG-59141 Change-Id: Ie8e79c2ec2aa1f5a00056f23856bd0bed19af2d6 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
parent
6d2d4914f8
commit
375e400390
|
@ -912,29 +912,44 @@ void QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers(const QVector<QPointer
|
|||
QQuickPointerHandlerPrivate::deviceDeliveryTargets(pointerEvent->device());
|
||||
QVarLengthArray<QPair<QQuickItem *, bool>, 4> sendFilteredPointerEventResult;
|
||||
hasFiltered.clear();
|
||||
for (auto o : passiveGrabbers) {
|
||||
QQuickPointerHandler *handler = qobject_cast<QQuickPointerHandler *>(o);
|
||||
for (QObject *grabberObject : passiveGrabbers) {
|
||||
// a null pointer in passiveGrabbers is unlikely, unless the grabbing handler was deleted dynamically
|
||||
if (Q_LIKELY(handler) && !eventDeliveryTargets.contains(handler)) {
|
||||
bool alreadyFiltered = false;
|
||||
QQuickItem *par = handler->parentItem();
|
||||
if (Q_UNLIKELY(!grabberObject))
|
||||
continue;
|
||||
// a passiveGrabber might be an item or a handler
|
||||
if (QQuickPointerHandler *handler = qobject_cast<QQuickPointerHandler *>(grabberObject)) {
|
||||
if (handler && !eventDeliveryTargets.contains(handler)) {
|
||||
bool alreadyFiltered = false;
|
||||
QQuickItem *par = handler->parentItem();
|
||||
|
||||
// see if we already have sent a filter event to the parent
|
||||
auto it = std::find_if(sendFilteredPointerEventResult.begin(), sendFilteredPointerEventResult.end(),
|
||||
[par](const QPair<QQuickItem *, bool> &pair) { return pair.first == par; });
|
||||
if (it != sendFilteredPointerEventResult.end()) {
|
||||
// Yes, the event was already filtered to that parent, do not call it again but use
|
||||
// the result of the previous call to determine if we should call the handler.
|
||||
alreadyFiltered = it->second;
|
||||
} else if (par) {
|
||||
alreadyFiltered = sendFilteredPointerEvent(pointerEvent, par);
|
||||
sendFilteredPointerEventResult << qMakePair(par, alreadyFiltered);
|
||||
// see if we already have sent a filter event to the parent
|
||||
auto it = std::find_if(sendFilteredPointerEventResult.begin(), sendFilteredPointerEventResult.end(),
|
||||
[par](const QPair<QQuickItem *, bool> &pair) { return pair.first == par; });
|
||||
if (it != sendFilteredPointerEventResult.end()) {
|
||||
// Yes, the event was sent to that parent for filtering: do not call it again, but use
|
||||
// the result of the previous call to determine whether we should call the handler.
|
||||
alreadyFiltered = it->second;
|
||||
} else if (par) {
|
||||
alreadyFiltered = sendFilteredPointerEvent(pointerEvent, par);
|
||||
sendFilteredPointerEventResult << qMakePair(par, alreadyFiltered);
|
||||
}
|
||||
if (!alreadyFiltered) {
|
||||
if (par)
|
||||
localizePointerEvent(pointerEvent, par);
|
||||
handler->handlePointerEvent(pointerEvent);
|
||||
}
|
||||
}
|
||||
if (!alreadyFiltered) {
|
||||
if (par)
|
||||
localizePointerEvent(pointerEvent, par);
|
||||
handler->handlePointerEvent(pointerEvent);
|
||||
} else if (QQuickItem *grabberItem = static_cast<QQuickItem *>(grabberObject)) {
|
||||
// don't steal the grab if input should remain with the exclusive grabber only
|
||||
if (QQuickItem *excGrabber = static_cast<QQuickItem *>(pointerEvent->exclusiveGrabber(pointerEvent->point(0)))) {
|
||||
if ((isMouseEvent(pointerEvent) && excGrabber->keepMouseGrab())
|
||||
|| (isTouchEvent(pointerEvent) && excGrabber->keepTouchGrab())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
localizePointerEvent(pointerEvent, grabberItem);
|
||||
QCoreApplication::sendEvent(grabberItem, pointerEvent);
|
||||
pointerEvent->accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1184,6 +1199,21 @@ bool QQuickDeliveryAgentPrivate::deliverSinglePointEventUntilAccepted(QPointerEv
|
|||
QVector<QQuickItem *> targetItems = pointerTargets(rootItem, event, point, false, false);
|
||||
point.setAccepted(false);
|
||||
|
||||
// Let passive grabbers see the event. This must be done before we deliver the
|
||||
// event to the target and to handlers that might stop event propagation.
|
||||
// Passive grabbers cannot stop event delivery.
|
||||
for (const auto &passiveGrabber : event->passiveGrabbers(point)) {
|
||||
if (auto *grabberItem = qobject_cast<QQuickItem *>(passiveGrabber)) {
|
||||
if (targetItems.contains(grabberItem))
|
||||
continue;
|
||||
localizePointerEvent(event, grabberItem);
|
||||
QCoreApplication::sendEvent(grabberItem, event);
|
||||
}
|
||||
}
|
||||
// Maintain the invariant that items receive input events in accepted state.
|
||||
// A passive grabber might have explicitly ignored the event.
|
||||
event->accept();
|
||||
|
||||
for (QQuickItem *item : targetItems) {
|
||||
QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
|
||||
localizePointerEvent(event, item);
|
||||
|
|
|
@ -302,29 +302,36 @@ static bool isWithinDragMargin(const QQuickDrawer *drawer, const QPointF &pos)
|
|||
bool QQuickDrawerPrivate::startDrag(QEvent *event)
|
||||
{
|
||||
Q_Q(QQuickDrawer);
|
||||
delayedEnterTransition = false;
|
||||
if (!window || !interactive || dragMargin < 0.0 || qFuzzyIsNull(dragMargin))
|
||||
return false;
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::MouseButtonPress:
|
||||
if (isWithinDragMargin(q, static_cast<QMouseEvent *>(event)->scenePosition())) {
|
||||
prepareEnterTransition();
|
||||
reposition();
|
||||
return handleMouseEvent(window->contentItem(), static_cast<QMouseEvent *>(event));
|
||||
if (QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); isWithinDragMargin(q, mouseEvent->scenePosition())) {
|
||||
// watch future events and grab the mouse once it has moved
|
||||
// sufficiently fast or far (in grabMouse).
|
||||
delayedEnterTransition = true;
|
||||
mouseEvent->addPassiveGrabber(mouseEvent->point(0), popupItem);
|
||||
handleMouseEvent(window->contentItem(), mouseEvent);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
#if QT_CONFIG(quicktemplates2_multitouch)
|
||||
case QEvent::TouchBegin:
|
||||
case QEvent::TouchUpdate:
|
||||
for (const QTouchEvent::TouchPoint &point : static_cast<QTouchEvent *>(event)->points()) {
|
||||
case QEvent::TouchUpdate: {
|
||||
auto *touchEvent = static_cast<QTouchEvent *>(event);
|
||||
for (const QTouchEvent::TouchPoint &point : touchEvent->points()) {
|
||||
if (point.state() == QEventPoint::Pressed && isWithinDragMargin(q, point.scenePosition())) {
|
||||
prepareEnterTransition();
|
||||
reposition();
|
||||
return handleTouchEvent(window->contentItem(), static_cast<QTouchEvent *>(event));
|
||||
delayedEnterTransition = true;
|
||||
touchEvent->addPassiveGrabber(point, popupItem);
|
||||
handleTouchEvent(window->contentItem(), touchEvent);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
|
@ -372,6 +379,12 @@ bool QQuickDrawerPrivate::grabMouse(QQuickItem *item, QMouseEvent *event)
|
|||
}
|
||||
|
||||
if (overThreshold) {
|
||||
if (delayedEnterTransition) {
|
||||
prepareEnterTransition();
|
||||
reposition();
|
||||
delayedEnterTransition = false;
|
||||
}
|
||||
|
||||
popupItem->grabMouse();
|
||||
popupItem->setKeepMouseGrab(true);
|
||||
offset = offsetAt(movePoint);
|
||||
|
@ -418,7 +431,12 @@ bool QQuickDrawerPrivate::grabTouch(QQuickItem *item, QTouchEvent *event)
|
|||
}
|
||||
|
||||
if (overThreshold) {
|
||||
popupItem->grabTouchPoints(QList<int>() << touchId);
|
||||
if (delayedEnterTransition) {
|
||||
prepareEnterTransition();
|
||||
reposition();
|
||||
delayedEnterTransition = false;
|
||||
}
|
||||
event->setExclusiveGrabber(point, popupItem);
|
||||
popupItem->setKeepTouchGrab(true);
|
||||
offset = offsetAt(movePoint);
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ public:
|
|||
qreal position = 0;
|
||||
qreal dragMargin = 0;
|
||||
QQuickVelocityCalculator velocityCalculator;
|
||||
bool delayedEnterTransition = false;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import QtQuick
|
||||
|
||||
import Test
|
||||
|
||||
Item {
|
||||
width: 320
|
||||
height: 240
|
||||
property alias exclusiveGrabber: exGrabber
|
||||
property alias passiveGrabber: psGrabber
|
||||
ExclusiveGrabber {
|
||||
id: exGrabber
|
||||
width: parent.width
|
||||
height: 30
|
||||
color: "blue"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Exclusive Grabber"
|
||||
}
|
||||
}
|
||||
PassiveGrabber {
|
||||
id: psGrabber
|
||||
width: parent.width * 0.3
|
||||
height: parent.height
|
||||
color: "yellow"
|
||||
opacity: 0.5
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Passive Grabber"
|
||||
rotation: 90
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
#include <QtQuick/QQuickItem>
|
||||
#include <QtQuick/QQuickView>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
#include <QtQuick/private/qquickrectangle_p.h>
|
||||
#include <QtQuick/private/qquickflickable_p.h>
|
||||
#include <QtQuick/private/qquickpointhandler_p.h>
|
||||
#include <QtQuick/private/qquickshadereffectsource_p.h>
|
||||
|
@ -153,6 +154,7 @@ public:
|
|||
|
||||
private slots:
|
||||
void passiveGrabberOrder();
|
||||
void passiveGrabberItems();
|
||||
void tapHandlerDoesntOverrideSubsceneGrabber();
|
||||
void touchCompression();
|
||||
void hoverPropagation_nested_data();
|
||||
|
@ -216,6 +218,133 @@ void tst_qquickdeliveryagent::passiveGrabberOrder()
|
|||
QCOMPARE(spy.senders.last(), rootTap);
|
||||
}
|
||||
|
||||
class PassiveGrabberItem : public QQuickRectangle
|
||||
{
|
||||
public:
|
||||
PassiveGrabberItem(QQuickItem *parent = nullptr) : QQuickRectangle(parent) {
|
||||
setAcceptedMouseButtons(Qt::LeftButton);
|
||||
}
|
||||
void mousePressEvent(QMouseEvent *event) override {
|
||||
qCDebug(lcTests) << "Passive grabber pressed";
|
||||
lastPressed = true;
|
||||
event->addPassiveGrabber(event->point(0), this);
|
||||
event->ignore();
|
||||
}
|
||||
void mouseMoveEvent(QMouseEvent *event) override {
|
||||
qCDebug(lcTests) << "Mouse move handled by passive grabber";
|
||||
const QPointF pos = event->scenePosition();
|
||||
const int threshold = 20;
|
||||
bool overThreshold = pos.x() >= threshold;
|
||||
if (overThreshold) {
|
||||
event->setExclusiveGrabber(event->point(0), this);
|
||||
this->setKeepMouseGrab(true);
|
||||
event->accept();
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
void mouseReleaseEvent(QMouseEvent *event) override {
|
||||
qCDebug(lcTests) << "Passive grabber released";
|
||||
lastPressed = false;
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
bool lastPressed = false;
|
||||
};
|
||||
|
||||
class ExclusiveGrabberItem : public QQuickRectangle
|
||||
{
|
||||
public:
|
||||
ExclusiveGrabberItem(QQuickItem *parent = nullptr) : QQuickRectangle(parent) {
|
||||
setAcceptedMouseButtons(Qt::LeftButton);
|
||||
}
|
||||
void mousePressEvent(QMouseEvent *event) override {
|
||||
qCDebug(lcTests) << "Exclusive grabber pressed";
|
||||
lastPressed = true;
|
||||
event->accept();
|
||||
}
|
||||
void mouseMoveEvent(QMouseEvent *event) override {
|
||||
event->accept();
|
||||
}
|
||||
void mouseReleaseEvent(QMouseEvent *event) override {
|
||||
qCDebug(lcTests) << "Exclusive grabber released";
|
||||
lastPressed = false;
|
||||
event->accept();
|
||||
}
|
||||
void mouseUngrabEvent() override {
|
||||
qCDebug(lcTests) << "Exclusive grab ended";
|
||||
ungrabbed = true;
|
||||
}
|
||||
|
||||
bool lastPressed = false;
|
||||
bool ungrabbed = false;
|
||||
};
|
||||
|
||||
void tst_qquickdeliveryagent::passiveGrabberItems()
|
||||
{
|
||||
QQuickView view;
|
||||
QQmlComponent component(view.engine());
|
||||
qmlRegisterType<PassiveGrabberItem>("Test", 1, 0, "PassiveGrabber");
|
||||
qmlRegisterType<ExclusiveGrabberItem>("Test", 1, 0, "ExclusiveGrabber");
|
||||
component.loadUrl(testFileUrl("passiveGrabberItem.qml"));
|
||||
view.setContent(QUrl(), &component, component.create());
|
||||
QQuickItem *root = qobject_cast<QQuickItem*>(view.rootObject());
|
||||
QVERIFY(root);
|
||||
ExclusiveGrabberItem *exclusiveGrabber = root->property("exclusiveGrabber").value<ExclusiveGrabberItem*>();
|
||||
PassiveGrabberItem *passiveGrabber = root->property("passiveGrabber").value<PassiveGrabberItem *>();
|
||||
QVERIFY(exclusiveGrabber);
|
||||
QVERIFY(passiveGrabber);
|
||||
|
||||
view.show();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&view));
|
||||
|
||||
QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, QPoint(exclusiveGrabber->x() + 1, exclusiveGrabber->y() + 1));
|
||||
auto devPriv = QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice());
|
||||
const auto &persistentPoint = devPriv->activePoints.values().first();
|
||||
QTRY_COMPARE(persistentPoint.passiveGrabbers.count(), 1);
|
||||
QCOMPARE(persistentPoint.passiveGrabbers.first(), passiveGrabber);
|
||||
QCOMPARE(persistentPoint.exclusiveGrabber, exclusiveGrabber);
|
||||
QVERIFY(exclusiveGrabber->lastPressed);
|
||||
QVERIFY(passiveGrabber->lastPressed);
|
||||
|
||||
// Mouse move bigger than threshold -> passive grabber becomes exclusive grabber
|
||||
QTest::mouseMove(&view);
|
||||
QTRY_COMPARE(persistentPoint.exclusiveGrabber, passiveGrabber);
|
||||
QVERIFY(exclusiveGrabber->ungrabbed);
|
||||
|
||||
QTest::mouseRelease(&view, Qt::LeftButton);
|
||||
// Only the passive grabber got the release event
|
||||
// since it became the exclusive grabber on mouseMove
|
||||
QTRY_VERIFY(!passiveGrabber->lastPressed);
|
||||
QVERIFY(exclusiveGrabber->lastPressed);
|
||||
QCOMPARE(persistentPoint.passiveGrabbers.count(), 0);
|
||||
QCOMPARE(persistentPoint.exclusiveGrabber, nullptr);
|
||||
|
||||
exclusiveGrabber->lastPressed = false;
|
||||
exclusiveGrabber->ungrabbed = false;
|
||||
passiveGrabber->lastPressed = false;
|
||||
|
||||
QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, QPoint(exclusiveGrabber->x() + 1, exclusiveGrabber->y() + 1));
|
||||
const auto &pressedPoint = devPriv->activePoints.values().first();
|
||||
QTRY_COMPARE(pressedPoint.passiveGrabbers.count(), 1);
|
||||
QCOMPARE(pressedPoint.passiveGrabbers.first(), passiveGrabber);
|
||||
QCOMPARE(pressedPoint.exclusiveGrabber, exclusiveGrabber);
|
||||
QVERIFY(exclusiveGrabber->lastPressed);
|
||||
QVERIFY(passiveGrabber->lastPressed);
|
||||
|
||||
// Mouse move smaller than threshold -> grab remains with the exclusive grabber
|
||||
QTest::mouseMove(&view, QPoint(exclusiveGrabber->x(), exclusiveGrabber->y()));
|
||||
QTRY_COMPARE(pressedPoint.exclusiveGrabber, exclusiveGrabber);
|
||||
|
||||
QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, QPoint(exclusiveGrabber->x(), exclusiveGrabber->y()));
|
||||
|
||||
// Both the passive and the exclusive grabber get the mouseRelease event
|
||||
QTRY_VERIFY(!passiveGrabber->lastPressed);
|
||||
QVERIFY(!exclusiveGrabber->lastPressed);
|
||||
QCOMPARE(pressedPoint.passiveGrabbers.count(), 0);
|
||||
QCOMPARE(pressedPoint.exclusiveGrabber, nullptr);
|
||||
}
|
||||
|
||||
void tst_qquickdeliveryagent::tapHandlerDoesntOverrideSubsceneGrabber() // QTBUG-94012
|
||||
{
|
||||
QQuickView window;
|
||||
|
|
Loading…
Reference in New Issue