MouseArea: fix containsMouse behavior during visibility changes

QQuickItem returns whether it contains QGuiApplicationPrivate's
lastCursorPosition. Since that position stores the coordinate last seen
by Qt (the window border), it will always be within the window, and
within an item that covers that part of the window's border.

However, QQuickWindow stores the lastMousePosition as well, and resets
that value when it receives a QEvent::Leave. We can use that to test
whether the  window that contains the item has seen a Leave event, in
which case the item is definitely not under the mouse.

Notes on the test: That we use QPointF() as the "reset" value leave the
small possibility that the cursor might be at position 0,0 of the window
(ie inside the window), and the QQuickItem there will not be under the
mouse. We can't confirm this (through an expected failure test), as
QTest::mouseMove interprets a QPoint(0, 0) as "center of the window".

And since we can't simulate mouse moves outside a window's boundary
using QTest::mouseMove, the test needs to explicitly synthesize a
QEvent::Leave for the window.

Fixes: QTBUG-87197
Pick-to: 6.1 6.0 5.15
Change-Id: I04870d6e914092275d9d790312fc702fb99f2935
Done-with: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Volker Hilsheimer 2021-02-12 17:11:47 +01:00 committed by Tor Arne Vestbø
parent 55cd062090
commit ba1246c543
3 changed files with 66 additions and 0 deletions

View File

@ -7415,6 +7415,12 @@ bool QQuickItem::isUnderMouse() const
if (!d->window)
return false;
// QQuickWindow handles QEvent::Leave to reset the lastMousePosition
// FIXME: Using QPointF() as the reset value means an item will not be
// under the mouse if the mouse is at 0,0 of the window.
if (QQuickWindowPrivate::get(d->window)->lastMousePosition == QPointF())
return false;
QPointF cursorPos = QGuiApplicationPrivate::lastCursorPosition;
return contains(mapFromScene(d->window->mapFromGlobal(cursorPos.toPoint())));
}

View File

@ -0,0 +1,14 @@
import QtQuick
Rectangle {
width: 200
height: 200
visible: true
MouseArea {
id: mouseArea
objectName: "mouseArea"
anchors.fill: parent
hoverEnabled: true
visible: false
}
}

View File

@ -154,6 +154,7 @@ private slots:
void nestedEventDelivery();
void settingHiddenInPressUngrabs();
void negativeZStackingOrder();
void containsMouseAndVisibility();
private:
int startDragDistance() const {
@ -2298,6 +2299,51 @@ void tst_QQuickMouseArea::negativeZStackingOrder() // QTBUG-83114
QVERIFY(order.at(0) == "parentMouseArea");
}
// QTBUG-87197
void tst_QQuickMouseArea::containsMouseAndVisibility()
{
QQuickView window;
QVERIFY(QQuickTest::showView(window, testFileUrl("containsMouse.qml")));
QQuickMouseArea *mouseArea = window.rootObject()->findChild<QQuickMouseArea*>("mouseArea");
QVERIFY(mouseArea != nullptr);
QVERIFY(!mouseArea->isVisible());
QTest::mouseMove(&window, QPoint(10, 10));
QTRY_VERIFY(!mouseArea->hovered());
mouseArea->setVisible(true);
QVERIFY(mouseArea->isVisible());
QTRY_VERIFY(mouseArea->hovered());
/* we (ab-)use QPointF() as the 'reset' value in QQuickWindow's leave-event handling,
but can't verify that this leaves an invalid interpretation of states for position
QPoint(0, 0) as QTest::mouseMove interprets a null-position as "center of the window".
So instead, verify the current (unexpectedly expected) behavior as far as testing is
concern.
*/
QTest::mouseMove(&window, QPoint(0, 0));
QTRY_VERIFY(mouseArea->hovered());
QTRY_VERIFY(mouseArea->isUnderMouse());
// move to the edge (can't move outside)
QTest::mouseMove(&window, QPoint(window.width() - 1, window.height() / 2));
// then pretend we left
QEvent event(QEvent::Leave);
QGuiApplication::sendEvent(&window, &event);
QVERIFY(!mouseArea->hovered());
// toggle mouse area visibility - the hover state should not change
mouseArea->setVisible(false);
QVERIFY(!mouseArea->isVisible());
QVERIFY(!mouseArea->hovered());
mouseArea->setVisible(true);
QVERIFY(mouseArea->isVisible());
QVERIFY(!mouseArea->hovered());
}
QTEST_MAIN(tst_QQuickMouseArea)
#include "tst_qquickmousearea.moc"