diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2023-11-10 17:07:15 -0700 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2023-11-18 08:23:02 -0700 |
commit | cd7c5f94a0ed64c52d31998957134d099313932a (patch) | |
tree | cac7b87e6f50341d9d73ac329c44a9ab45ec02bd | |
parent | 03d58031c7233e0579f463b9484af8227b0938fd (diff) |
Update cursor if frame-synchronous hover update discovers hover change
Also mark cursor as dirty and force update after shape change.
Done-with: Matthias Rauter <matthias.rauter@qt.io>
Fixes: QTBUG-53987
Fixes: QTBUG-90457
Task-number: QTBUG-54019
Pick-to: 6.5 6.6
Change-Id: I64d9f5d0a39dbf141a8e82bee824b47a8884139b
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
8 files changed, 191 insertions, 2 deletions
diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp index a00ebecae3..c01cbd039f 100644 --- a/src/quick/handlers/qquickpointerhandler.cpp +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -197,11 +197,13 @@ void QQuickPointerHandler::setCursorShape(Qt::CursorShape shape) return; d->cursorShape = shape; d->cursorSet = true; + d->cursorDirty = true; if (auto *parent = parentItem()) { QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(parent); itemPriv->hasCursorHandler = true; itemPriv->setHasCursorInChild(true); } + emit cursorShapeChanged(); } @@ -877,6 +879,7 @@ QQuickPointerHandlerPrivate::QQuickPointerHandlerPrivate() , hadKeepMouseGrab(false) , hadKeepTouchGrab(false) , cursorSet(false) + , cursorDirty(false) { } diff --git a/src/quick/handlers/qquickpointerhandler_p_p.h b/src/quick/handlers/qquickpointerhandler_p_p.h index 36797ef8b3..6ff2e133e4 100644 --- a/src/quick/handlers/qquickpointerhandler_p_p.h +++ b/src/quick/handlers/qquickpointerhandler_p_p.h @@ -57,6 +57,7 @@ public: bool hadKeepMouseGrab : 1; // some handlers override target()->setKeepMouseGrab(); this remembers previous state bool hadKeepTouchGrab : 1; // some handlers override target()->setKeepTouchGrab(); this remembers previous state bool cursorSet : 1; + bool cursorDirty : 1; }; QT_END_NAMESPACE diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 8e67121641..5c19a9bb81 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -12,6 +12,7 @@ #include <QtQuick/private/qsgrenderer_p.h> #include <QtQuick/private/qsgplaintexture_p.h> #include <QtQuick/private/qquickpointerhandler_p.h> +#include <QtQuick/private/qquickpointerhandler_p_p.h> #include <private/qsgrenderloop_p.h> #include <private/qsgrhisupport_p.h> #include <private/qquickrendercontrol_p.h> @@ -1677,11 +1678,14 @@ void QQuickWindowPrivate::updateCursor(const QPointF &scenePos, QQuickItem *root if (!rootItem) rootItem = contentItem; auto cursorItemAndHandler = findCursorItemAndHandler(rootItem, scenePos); - if (cursorItem != cursorItemAndHandler.first || cursorHandler != cursorItemAndHandler.second) { + if (cursorItem != cursorItemAndHandler.first || cursorHandler != cursorItemAndHandler.second || + (cursorItemAndHandler.second && QQuickPointerHandlerPrivate::get(cursorItemAndHandler.second)->cursorDirty)) { QWindow *renderWindow = QQuickRenderControl::renderWindowFor(q); QWindow *window = renderWindow ? renderWindow : q; cursorItem = cursorItemAndHandler.first; cursorHandler = cursorItemAndHandler.second; + if (cursorHandler) + QQuickPointerHandlerPrivate::get(cursorItemAndHandler.second)->cursorDirty = false; if (cursorItem) { const auto cursor = QQuickItemPrivate::get(cursorItem)->effectiveCursor(cursorHandler); qCDebug(lcHoverTrace) << "setting cursor" << cursor << "from" << cursorHandler << "or" << cursorItem; diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 947321755f..de110b2660 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -1723,7 +1723,13 @@ void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win) if (frameSynchronousHoverEnabled && !win->mouseGrabberItem() && !lastMousePosition.isNull() && QQuickWindowPrivate::get(win)->dirtyItemList) { qCDebug(lcHoverTrace) << q << "delivering frame-sync hover to root @" << lastMousePosition; - deliverHoverEvent(lastMousePosition, lastMousePosition, QGuiApplication::keyboardModifiers(), 0); + if (deliverHoverEvent(lastMousePosition, lastMousePosition, QGuiApplication::keyboardModifiers(), 0)) { +#if QT_CONFIG(cursor) + QQuickWindowPrivate::get(rootItem->window())->updateCursor( + sceneTransform ? sceneTransform->map(lastMousePosition) : lastMousePosition, rootItem); +#endif + } + qCDebug(lcHoverTrace) << q << "frame-sync hover delivery done"; } #else diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/changingCursor.qml b/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/changingCursor.qml new file mode 100644 index 0000000000..75bfbf5c34 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/changingCursor.qml @@ -0,0 +1,37 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.15 + + +Rectangle { + id: brownRect + objectName: "brownRect" + + width: 400 + height: 400 + + HoverHandler { + id: hh + cursorShape: parent.colorIndex == 0 ? + Qt.CrossCursor : + Qt.OpenHandCursor + } + + property list<color> colors: ["beige", "brown"] + property int colorIndex: 0 + + color: colors[colorIndex] + + Timer { + id: colorTimer + interval: 200 + running: true + repeat: true + + onTriggered: { + parent.colorIndex = (parent.colorIndex + 1) % parent.colors.length; + parent.color = parent.colors[parent.colorIndex]; + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp index e488c0486f..85f1cbda57 100644 --- a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp @@ -48,6 +48,7 @@ private slots: void deviceCursor(); void addHandlerFromCpp(); void ensureHoverHandlerWorksWhenItemHasHoverDisabled(); + void changeCursor(); private: void createView(QScopedPointer<QQuickView> &window, const char *fileName); @@ -671,6 +672,34 @@ void tst_HoverHandler::ensureHoverHandlerWorksWhenItemHasHoverDisabled() QCOMPARE(spy.size(), 2); } +void tst_HoverHandler::changeCursor() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "changingCursor.qml"); + QQuickView * window = windowPtr.data(); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickItem *item = window->findChild<QQuickItem *>("brownRect"); + QVERIFY(item); + QQuickHoverHandler *hh = item->findChild<QQuickHoverHandler *>(); + QVERIFY(hh); + + QPoint itemCenter(item->mapToScene(QPointF(item->width() / 2, item->height() / 2)).toPoint()); + QSignalSpy hoveredSpy(hh, SIGNAL(hoveredChanged())); + + QTest::mouseMove(window, itemCenter); + + QTRY_COMPARE(hoveredSpy.size(), 1); + +#if QT_CONFIG(cursor) + QTRY_COMPARE(window->cursor().shape(), Qt::CrossCursor); + QTRY_COMPARE(window->cursor().shape(), Qt::OpenHandCursor); + QTRY_COMPARE(window->cursor().shape(), Qt::CrossCursor); + QTRY_COMPARE(window->cursor().shape(), Qt::OpenHandCursor); +#endif +} + QTEST_MAIN(tst_HoverHandler) #include "tst_qquickhoverhandler.moc" diff --git a/tests/auto/quick/qquickmousearea/data/cursorUpdating.qml b/tests/auto/quick/qquickmousearea/data/cursorUpdating.qml new file mode 100644 index 0000000000..2d06543c78 --- /dev/null +++ b/tests/auto/quick/qquickmousearea/data/cursorUpdating.qml @@ -0,0 +1,71 @@ +import QtQuick + +Item { + width: 600 + height: 600 + + ListModel { + id: cursorsModel + ListElement { cursorShape: Qt.ArrowCursor; text: "Arrow" } + ListElement { cursorShape: Qt.UpArrowCursor; text: "UpArrow" } + ListElement { cursorShape: Qt.CrossCursor; text: "Cross" } + ListElement { cursorShape: Qt.WaitCursor; text: "Wait" } + ListElement { cursorShape: Qt.IBeamCursor; text: "IBeam" } + ListElement { cursorShape: Qt.SizeVerCursor; text: "SizeVer" } + ListElement { cursorShape: Qt.SizeHorCursor; text: "SizeHor" } + ListElement { cursorShape: Qt.SizeBDiagCursor; text: "SizeBDiag" } + ListElement { cursorShape: Qt.SizeFDiagCursor; text: "SizeFDiag" } + ListElement { cursorShape: Qt.SizeAllCursor; text: "SizeAll" } + ListElement { cursorShape: Qt.BlankCursor; text: "Blank" } + ListElement { cursorShape: Qt.SplitVCursor; text: "SplitV" } + ListElement { cursorShape: Qt.SplitHCursor; text: "SplitH" } + ListElement { cursorShape: Qt.PointingHandCursor; text: "PointingHand" } + ListElement { cursorShape: Qt.ForbiddenCursor; text: "Forbidden" } + ListElement { cursorShape: Qt.WhatsThisCursor; text: "WhatsThis" } + ListElement { cursorShape: Qt.BusyCursor; text: "Busy" } + ListElement { cursorShape: Qt.OpenHandCursor; text: "OpenHand" } + ListElement { cursorShape: Qt.ClosedHandCursor; text: "ClosedHand" } + ListElement { cursorShape: Qt.DragCopyCursor; text: "DragCopy" } + ListElement { cursorShape: Qt.DragMoveCursor; text: "DragMove" } + ListElement { cursorShape: Qt.DragLinkCursor; text: "DragLink" } + } + + Flickable { + anchors.fill: parent + contentHeight: flow.height + Flow { + id: flow + width: parent.width + Repeater { + model: cursorsModel + Rectangle { + id: root + color: "white" + border.width: 5 + border.color: "black" + + width: 200 + height: 200 + + Text { + id: textItem + anchors.fill: parent + anchors.margins: parent.width * 0.1 + text: model.text + fontSizeMode: Text.Fit + minimumPixelSize: 10; font.pixelSize: height + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: model.cursorShape + } + } + } + } + } +} diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index ec2b9695dc..8d4c76b24e 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -23,6 +23,11 @@ Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") +static bool isPlatformWayland() +{ + return !QGuiApplication::platformName().compare(QLatin1String("wayland"), Qt::CaseInsensitive); +} + class CircleMask : public QObject { Q_OBJECT @@ -121,6 +126,7 @@ private slots: void changeAxis(); #if QT_CONFIG(cursor) void cursorShape(); + void cursorUpdating(); #endif void moveAndReleaseWithoutPress(); void nestedStopAtBounds(); @@ -1901,6 +1907,38 @@ void tst_QQuickMouseArea::cursorShape() QCOMPARE(mouseArea->cursor().shape(), Qt::WaitCursor); QCOMPARE(spy.size(), 2); } + +void tst_QQuickMouseArea::cursorUpdating() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("cursorUpdating.qml"))); + QQuickItem *root = window.rootObject(); + QVERIFY(root); + QQuickFlickable *flickable = root->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + QQuickItemPrivate *rootPrivate = QQuickItemPrivate::get(root); + QVERIFY(rootPrivate->subtreeCursorEnabled); + + QTest::mouseMove(&window, QPoint(40, 40)); + QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); + + QTest::mouseMove(&window, QPoint(240, 40)); + QCOMPARE(window.cursor().shape(), Qt::UpArrowCursor); + + if (isPlatformWayland()) + QSKIP("Wayland: QCursor::setPos() doesn't work."); + + // QTBUG-53987: with the cursor physically hovering, use wheel to + // position a different item that requests a different cursor + const QPoint p(240, 40); + const QPoint pg = window.mapToGlobal(p); + QCursor::setPos(pg); + QWheelEvent wheelEvent(p, pg, QPoint(60, -400), QPoint(0, -600), + Qt::NoButton, Qt::ControlModifier, Qt::NoScrollPhase, false); + QGuiApplication::sendEvent(&window, &wheelEvent); + QTRY_VERIFY(flickable->contentY() > 300); + QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); +} #endif void tst_QQuickMouseArea::moveAndReleaseWithoutPress() |