aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2023-11-10 17:07:15 -0700
committerShawn Rutledge <shawn.rutledge@qt.io>2023-11-18 08:23:02 -0700
commitcd7c5f94a0ed64c52d31998957134d099313932a (patch)
treecac7b87e6f50341d9d73ac329c44a9ab45ec02bd
parent03d58031c7233e0579f463b9484af8227b0938fd (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>
-rw-r--r--src/quick/handlers/qquickpointerhandler.cpp3
-rw-r--r--src/quick/handlers/qquickpointerhandler_p_p.h1
-rw-r--r--src/quick/items/qquickwindow.cpp6
-rw-r--r--src/quick/util/qquickdeliveryagent.cpp8
-rw-r--r--tests/auto/quick/pointerhandlers/qquickhoverhandler/data/changingCursor.qml37
-rw-r--r--tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp29
-rw-r--r--tests/auto/quick/qquickmousearea/data/cursorUpdating.qml71
-rw-r--r--tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp38
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()