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 6adc36115f
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, bf74a908cb 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
9b62f4c27a. 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 <shawn.rutledge@qt.io>
(cherry picked from commit f33146ed0a)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Jan Arve Sæther 2023-07-11 15:15:09 +02:00 committed by Qt Cherry-pick Bot
parent 0b942a8d87
commit cf71d02152
3 changed files with 95 additions and 1 deletions

View File

@ -1889,7 +1889,7 @@ QVector<QQuickItem *> 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;
}

View File

@ -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"
}
}
}
}

View File

@ -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<QQuickView> windowPtr;
createView(windowPtr, "clip.qml");
QQuickView * window = windowPtr.data();
QVERIFY(window);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
EventHandler *handler = window->contentItem()->findChild<EventHandler*>("eventHandler");
EventHandler *circleHandler = window->contentItem()->findChild<EventHandler*>("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"