Drawer: don't get stuck in open state after touches outside

If the Drawer does not cover the whole window, it's possible to tap
outside the dimmer overlay. In that case, next time you tap on the
dimmer, you hit this early return in handleRelease in which
pressPoint.isNull(), so it was not setting touchId back to -1 and doing
the other cleanup/reset tasks.  This is a touch release, and touchId
should always be eventually reset after a touch release, regardless of
the code path.

Now QQuickDrawerPrivate::handleRelease() has a QScopeGuard to ensure
that cleanup is done regardless of early returns. That's still not
enough; but when we receive TouchEnd in QPopupPriv::handleTouchEvent(),
it means that _all_ touchpoints are released. So as a fallback for
multi-touch cases, we now ensure that all releases are handled and
stored state is reset, regardless of whether an individual point ID
matches the stored touchId.

Fixes: QTBUG-103811
Change-Id: Ia637bae00d8edb7f7f4c8fb4337b4c3d72fe4e60
Reviewed-by: Doris Verria <doris.verria@qt.io>
(cherry picked from commit 730cdc5043)
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
This commit is contained in:
Shawn Rutledge 2022-12-16 07:56:15 +01:00
parent da7cb43b7c
commit 0568b3c4d4
4 changed files with 62 additions and 10 deletions

View File

@ -488,6 +488,12 @@ bool QQuickDrawerPrivate::handleMove(QQuickItem *item, const QPointF &point, ulo
bool QQuickDrawerPrivate::handleRelease(QQuickItem *item, const QPointF &point, ulong timestamp)
{
auto cleanup = qScopeGuard([this] {
popupItem->setKeepMouseGrab(false);
popupItem->setKeepTouchGrab(false);
pressPoint = QPointF();
touchId = -1;
});
if (pressPoint.isNull())
return false;
if (!popupItem->keepMouseGrab() && !popupItem->keepTouchGrab()) {
@ -547,14 +553,8 @@ bool QQuickDrawerPrivate::handleRelease(QQuickItem *item, const QPointF &point,
}
}
bool wasGrabbed = popupItem->keepMouseGrab() || popupItem->keepTouchGrab();
popupItem->setKeepMouseGrab(false);
popupItem->setKeepTouchGrab(false);
pressPoint = QPointF();
touchId = -1;
return wasGrabbed;
// the cleanup() lambda will run before return
return popupItem->keepMouseGrab() || popupItem->keepTouchGrab();
}
void QQuickDrawerPrivate::handleUngrab()

View File

@ -445,7 +445,7 @@ bool QQuickPopupPrivate::handleTouchEvent(QQuickItem *item, QTouchEvent *event)
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
for (const QTouchEvent::TouchPoint &point : event->points()) {
if (!acceptTouch(point))
if (event->type() != QEvent::TouchEnd && !acceptTouch(point))
return blockInput(item, point.position());
switch (point.state()) {

View File

@ -0,0 +1,13 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
width: 400; height: 400
Drawer {
edge: Qt.LeftEdge
height: 200
width: 200
modal: true
}
}

View File

@ -46,14 +46,16 @@
#include <QtGui/private/qguiapplication_p.h>
#include <QtQuick/private/qquickwindow_p.h>
#include <QtQuick/private/qquickflickable_p.h>
#include <QtQuick/qquickview.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQuickTestUtils/private/visualtestutils_p.h>
#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
#include <QtQuickTemplates2/private/qquickoverlay_p.h>
#include <QtQuickTemplates2/private/qquickoverlay_p_p.h>
#include <QtQuickTemplates2/private/qquickpopup_p_p.h>
#include <QtQuickTemplates2/private/qquickdrawer_p.h>
#include <QtQuickTemplates2/private/qquickbutton_p.h>
#include <QtQuickTemplates2/private/qquickslider_p.h>
#include <QtQuickTestUtils/private/viewtestutils_p.h>
#include <QtQuickControlsTestUtils/private/controlstestutils_p.h>
#include <QtQuickControlsTestUtils/private/qtest_quickcontrols_p.h>
@ -121,6 +123,8 @@ private slots:
void topEdgeScreenEdge();
void touchOutsideOverlay();
private:
QScopedPointer<QPointingDevice> touchDevice;
};
@ -1393,6 +1397,41 @@ void tst_QQuickDrawer::topEdgeScreenEdge()
QTRY_COMPARE(drawer->position(), 1.0);
}
void tst_QQuickDrawer::touchOutsideOverlay() // QTBUG-103811
{
QQuickView window;
QVERIFY(QQuickTest::showView(window, testFileUrl("itemPartialOverlayModal.qml")));
auto *drawer = window.rootObject()->findChild<QQuickDrawer*>();
QVERIFY(drawer);
QSignalSpy openedSpy(drawer, &QQuickDrawer::opened);
QSignalSpy closedSpy(drawer, &QQuickDrawer::closed);
drawer->open();
QVERIFY(openedSpy.size() == 1 || openedSpy.wait());
QVERIFY(drawer->isOpened());
// tap-dance in bottom area beyond the overlay
QPoint p1(100, 250);
QPoint p2(300, 250);
QTest::touchEvent(&window, touchDevice.data()).press(1, p1);
p1 -= QPoint(1, 0);
QTest::touchEvent(&window, touchDevice.data()).move(1, p1).press(2, p2);
p2 -= QPoint(1, 0);
QTest::touchEvent(&window, touchDevice.data()).release(1, p1).move(2, p2);
QTest::touchEvent(&window, touchDevice.data()).press(1, p1).stationary(2);
QTest::touchEvent(&window, touchDevice.data()).release(1, p1).release(2, p2);
QQuickTouchUtils::flush(&window);
// tap the overlay to try to close the drawer
QVERIFY(drawer->closePolicy().testFlag(QQuickPopup::CloseOnReleaseOutside));
const QPoint p3(300, 100);
QTest::touchEvent(&window, touchDevice.data()).press(3, p3);
QTest::touchEvent(&window, touchDevice.data()).release(3, p3);
QQuickTouchUtils::flush(&window);
QVERIFY(closedSpy.size() == 1 || closedSpy.wait());
QCOMPARE(drawer->isOpened(), false);
}
QTEST_QUICKCONTROLS_MAIN(tst_QQuickDrawer)
#include "tst_qquickdrawer.moc"