From cf71d02152437c0f70b02bd0a3c4c3c963affe9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Tue, 11 Jul 2023 15:15:09 +0200 Subject: [PATCH] Fix pointer delivery to child items of items with clip:true QQuickDeliveryAgentPrivate::pointerTargets() can visit a lot of items and their handlers, before actual event delivery really starts. One optimization in place since 6adc36115f4fc658fb907ee8af3013f2609ae761 is that if an item is clipped, and the point is outside its bounds, we can be sure that it's also irrelevant to the item's children, because the parts of any children that may be under that point are clipped away and invisible. At the time that was written, QQuickItem::contains() was only doing a simple bounding-rect check. Since then, bf74a908cb0591c2adc024a6f93d566c7348c125 added containmentMask; and we should also keep in mind the precedence of the PointerHandler.margin property (currently, TapHandler.margin does not expand the sensitive area beyond a clipped Rectangle, or beyond the containmentMask either). So it seems we now need to check clipRect().contains() explicitly: a child item may be outside its parent's containmentMask, and containmentMask does not affect clipping, so one would expect to still be able to interact with the child. The current definition of clipRect() is from 9b62f4c27ac3fb3dc563c7f4657094c14d752bac. It's virtual, but documented as "the region intended to remain visible if clip is true". Fixes: QTBUG-115179 Change-Id: I6ae8f492b99725459cdff2a89ac8508da5167102 Reviewed-by: Shawn Rutledge (cherry picked from commit f33146ed0abb67cfd82b491ba63e7279da9d95b5) Reviewed-by: Qt Cherry-pick Bot --- src/quick/util/qquickdeliveryagent.cpp | 2 +- .../qquickpointerhandler/data/clip.qml | 36 ++++++++++++ .../tst_qquickpointerhandler.cpp | 58 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/auto/quick/pointerhandlers/qquickpointerhandler/data/clip.qml diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 8d88ee91ca..572e65b257 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -1889,7 +1889,7 @@ QVector QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *ite qCDebug(lcPtrLoc) << q << "point" << point.id() << point.scenePosition() << "->" << itemPos << ": relevant?" << relevant << "to" << item << point; // if the item clips, we can potentially return early if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { - if (!relevant) + if (!item->clipRect().contains(itemPos)) return targets; } diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/clip.qml b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/clip.qml new file mode 100644 index 0000000000..7bc3907c8c --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/clip.qml @@ -0,0 +1,36 @@ +import QtQuick +import Qt.test 1.0 + +Item { + width: 200 + height: 200 + + Rectangle { + id: circle + y: 0 + width: 100 + height: width + radius: width/2 + color: "#3e1" + clip: true + + // Rectangle contains() is not affected by its 'radius' property + containmentMask: QtObject { + property alias radius: circle.radius + function contains(point: point) : bool { + return (Math.pow(point.x - radius, 2) + Math.pow(point.y - radius, 2)) < Math.pow(radius, 2) + } + } + EventHandler { + objectName: "circle eventHandler" + } + Rectangle { + width: circle.width/2 + height: width + color: "red" + EventHandler { + objectName: "eventHandler" + } + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp index 8c73d0d296..c63572c83c 100644 --- a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp @@ -233,6 +233,7 @@ private slots: void reparenting(); void grabberSceneChange_data(); void grabberSceneChange(); + void clip(); protected: bool eventFilter(QObject *, QEvent *event) override @@ -800,6 +801,63 @@ void tst_PointerHandlers::grabberSceneChange() QTest::mouseMove(window, p1 + QPoint(5, 5)); } +void tst_PointerHandlers::clip() +{ + QScopedPointer windowPtr; + createView(windowPtr, "clip.qml"); + QQuickView * window = windowPtr.data(); + QVERIFY(window); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + EventHandler *handler = window->contentItem()->findChild("eventHandler"); + EventHandler *circleHandler = window->contentItem()->findChild("circle eventHandler"); + + QCOMPARE(handler->pressEventCount, 0); + QCOMPARE(circleHandler->pressEventCount, 0); + QCOMPARE(handler->releaseEventCount, 0); + QCOMPARE(circleHandler->releaseEventCount, 0); + + const QPoint rectPt = QPoint(1, 1); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, rectPt); + QCOMPARE(handler->pressEventCount, 1); + QCOMPARE(circleHandler->pressEventCount, 0); + + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, rectPt); + QCOMPARE(handler->releaseEventCount, 1); + QCOMPARE(circleHandler->releaseEventCount, 0); + + + handler->pressEventCount = 0; + circleHandler->pressEventCount = 0; + handler->releaseEventCount = 0; + circleHandler->releaseEventCount = 0; + + const QPoint rectAndCirclePt = QPoint(49 ,49); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, rectAndCirclePt); + QCOMPARE(handler->pressEventCount, 1); + QCOMPARE(circleHandler->pressEventCount, 1); + + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, rectAndCirclePt); + QCOMPARE(handler->releaseEventCount, 1); + QCOMPARE(circleHandler->releaseEventCount, 1); + + + handler->pressEventCount = 0; + circleHandler->pressEventCount = 0; + handler->releaseEventCount = 0; + circleHandler->releaseEventCount = 0; + + const QPoint circlePt = QPoint(51 ,51); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, circlePt); + QCOMPARE(handler->pressEventCount, 0); + QCOMPARE(circleHandler->pressEventCount, 1); + + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, circlePt); + QCOMPARE(handler->releaseEventCount, 0); + QCOMPARE(circleHandler->releaseEventCount, 1); +} + QTEST_MAIN(tst_PointerHandlers) #include "tst_qquickpointerhandler.moc"