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 Change-Id: I64d9f5d0a39dbf141a8e82bee824b47a8884139b Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io> (cherry picked from commitcd7c5f94a0
) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> (cherry picked from commit4271d4586b
)
This commit is contained in:
parent
821aa3dcc2
commit
66610bcb57
|
@ -182,11 +182,13 @@ void QQuickPointerHandler::setCursorShape(Qt::CursorShape shape)
|
||||||
return;
|
return;
|
||||||
d->cursorShape = shape;
|
d->cursorShape = shape;
|
||||||
d->cursorSet = true;
|
d->cursorSet = true;
|
||||||
|
d->cursorDirty = true;
|
||||||
if (auto *parent = parentItem()) {
|
if (auto *parent = parentItem()) {
|
||||||
QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(parent);
|
QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(parent);
|
||||||
itemPriv->hasCursorHandler = true;
|
itemPriv->hasCursorHandler = true;
|
||||||
itemPriv->setHasCursorInChild(true);
|
itemPriv->setHasCursorInChild(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit cursorShapeChanged();
|
emit cursorShapeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -765,6 +767,7 @@ QQuickPointerHandlerPrivate::QQuickPointerHandlerPrivate()
|
||||||
, hadKeepMouseGrab(false)
|
, hadKeepMouseGrab(false)
|
||||||
, hadKeepTouchGrab(false)
|
, hadKeepTouchGrab(false)
|
||||||
, cursorSet(false)
|
, cursorSet(false)
|
||||||
|
, cursorDirty(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ public:
|
||||||
bool hadKeepMouseGrab : 1; // some handlers override target()->setKeepMouseGrab(); this remembers previous state
|
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 hadKeepTouchGrab : 1; // some handlers override target()->setKeepTouchGrab(); this remembers previous state
|
||||||
bool cursorSet : 1;
|
bool cursorSet : 1;
|
||||||
|
bool cursorDirty : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <QtQuick/private/qsgrenderer_p.h>
|
#include <QtQuick/private/qsgrenderer_p.h>
|
||||||
#include <QtQuick/private/qsgplaintexture_p.h>
|
#include <QtQuick/private/qsgplaintexture_p.h>
|
||||||
#include <QtQuick/private/qquickpointerhandler_p.h>
|
#include <QtQuick/private/qquickpointerhandler_p.h>
|
||||||
|
#include <QtQuick/private/qquickpointerhandler_p_p.h>
|
||||||
#include <private/qsgrenderloop_p.h>
|
#include <private/qsgrenderloop_p.h>
|
||||||
#include <private/qsgrhisupport_p.h>
|
#include <private/qsgrhisupport_p.h>
|
||||||
#include <private/qquickrendercontrol_p.h>
|
#include <private/qquickrendercontrol_p.h>
|
||||||
|
@ -1684,11 +1685,14 @@ void QQuickWindowPrivate::updateCursor(const QPointF &scenePos, QQuickItem *root
|
||||||
if (!rootItem)
|
if (!rootItem)
|
||||||
rootItem = contentItem;
|
rootItem = contentItem;
|
||||||
auto cursorItemAndHandler = findCursorItemAndHandler(rootItem, scenePos);
|
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 *renderWindow = QQuickRenderControl::renderWindowFor(q);
|
||||||
QWindow *window = renderWindow ? renderWindow : q;
|
QWindow *window = renderWindow ? renderWindow : q;
|
||||||
cursorItem = cursorItemAndHandler.first;
|
cursorItem = cursorItemAndHandler.first;
|
||||||
cursorHandler = cursorItemAndHandler.second;
|
cursorHandler = cursorItemAndHandler.second;
|
||||||
|
if (cursorHandler)
|
||||||
|
QQuickPointerHandlerPrivate::get(cursorItemAndHandler.second)->cursorDirty = false;
|
||||||
if (cursorItem) {
|
if (cursorItem) {
|
||||||
const auto cursor = QQuickItemPrivate::get(cursorItem)->effectiveCursor(cursorHandler);
|
const auto cursor = QQuickItemPrivate::get(cursorItem)->effectiveCursor(cursorHandler);
|
||||||
qCDebug(lcHoverTrace) << "setting cursor" << cursor << "from" << cursorHandler << "or" << cursorItem;
|
qCDebug(lcHoverTrace) << "setting cursor" << cursor << "from" << cursorHandler << "or" << cursorItem;
|
||||||
|
|
|
@ -1686,7 +1686,13 @@ void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win)
|
||||||
if (frameSynchronousHoverEnabled && !win->mouseGrabberItem() &&
|
if (frameSynchronousHoverEnabled && !win->mouseGrabberItem() &&
|
||||||
!lastMousePosition.isNull() && QQuickWindowPrivate::get(win)->dirtyItemList) {
|
!lastMousePosition.isNull() && QQuickWindowPrivate::get(win)->dirtyItemList) {
|
||||||
qCDebug(lcHoverTrace) << q << "delivering frame-sync hover to root @" << lastMousePosition;
|
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";
|
qCDebug(lcHoverTrace) << q << "frame-sync hover delivery done";
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ private slots:
|
||||||
void deviceCursor();
|
void deviceCursor();
|
||||||
void addHandlerFromCpp();
|
void addHandlerFromCpp();
|
||||||
void ensureHoverHandlerWorksWhenItemHasHoverDisabled();
|
void ensureHoverHandlerWorksWhenItemHasHoverDisabled();
|
||||||
|
void changeCursor();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void createView(QScopedPointer<QQuickView> &window, const char *fileName);
|
void createView(QScopedPointer<QQuickView> &window, const char *fileName);
|
||||||
|
@ -671,6 +672,34 @@ void tst_HoverHandler::ensureHoverHandlerWorksWhenItemHasHoverDisabled()
|
||||||
QCOMPARE(spy.size(), 2);
|
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)
|
QTEST_MAIN(tst_HoverHandler)
|
||||||
|
|
||||||
#include "tst_qquickhoverhandler.moc"
|
#include "tst_qquickhoverhandler.moc"
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,11 @@
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
|
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
|
||||||
|
|
||||||
|
static bool isPlatformWayland()
|
||||||
|
{
|
||||||
|
return !QGuiApplication::platformName().compare(QLatin1String("wayland"), Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
class CircleMask : public QObject
|
class CircleMask : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -121,6 +126,7 @@ private slots:
|
||||||
void changeAxis();
|
void changeAxis();
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
void cursorShape();
|
void cursorShape();
|
||||||
|
void cursorUpdating();
|
||||||
#endif
|
#endif
|
||||||
void moveAndReleaseWithoutPress();
|
void moveAndReleaseWithoutPress();
|
||||||
void nestedStopAtBounds();
|
void nestedStopAtBounds();
|
||||||
|
@ -1901,6 +1907,38 @@ void tst_QQuickMouseArea::cursorShape()
|
||||||
QCOMPARE(mouseArea->cursor().shape(), Qt::WaitCursor);
|
QCOMPARE(mouseArea->cursor().shape(), Qt::WaitCursor);
|
||||||
QCOMPARE(spy.size(), 2);
|
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
|
#endif
|
||||||
|
|
||||||
void tst_QQuickMouseArea::moveAndReleaseWithoutPress()
|
void tst_QQuickMouseArea::moveAndReleaseWithoutPress()
|
||||||
|
|
Loading…
Reference in New Issue