QQuickHoverHandler: listen for parent changes, and update hasHoverInChild
A QQuickHoverHandler is normally created by the QML engine, but can sometimes also be created directly from C++. And for the latter case, QQuickHoverHandler::componentComplete() will not be called. This causes a problem, since it takes care of subscribing for hover events on the parent item. To support creating hover handlers from c++, we therefore need to also subscribe to hover events from the constructor. Moreover, since the parentItem can change at runtime, we also need a virtual function that informs it when the parent item changes, so that we can remove hover subscription from the old parent, and subscribe for it on the new parent. Pick-to: 6.4 Change-Id: I52f3cd16d6bbfbbe2e4c3c019efdc7f06c5f2c31 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
This commit is contained in:
parent
e306b4932e
commit
7db67e59d4
|
@ -42,35 +42,58 @@ class QQuickHoverHandlerPrivate : public QQuickSinglePointHandlerPrivate
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void onEnabledChanged() override;
|
void onEnabledChanged() override;
|
||||||
|
void onParentChanged(QQuickItem *oldParent, QQuickItem *newParent) override;
|
||||||
|
|
||||||
|
void updateHasHoverInChild(QQuickItem *item, bool hasHover);
|
||||||
};
|
};
|
||||||
|
|
||||||
void QQuickHoverHandlerPrivate::onEnabledChanged()
|
void QQuickHoverHandlerPrivate::onEnabledChanged()
|
||||||
{
|
{
|
||||||
Q_Q(QQuickHoverHandler);
|
Q_Q(QQuickHoverHandler);
|
||||||
|
|
||||||
if (auto parent = q->parentItem()) {
|
if (auto parent = q->parentItem())
|
||||||
QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(parent);
|
updateHasHoverInChild(parent, enabled);
|
||||||
itemPriv->setHasHoverInChild(enabled);
|
|
||||||
// The DA needs to resolve which items and handlers should now be hovered or unhovered.
|
|
||||||
// Marking the parent item dirty ensures that flushFrameSynchronousEvents() will be called from the render loop,
|
|
||||||
// even if this change is not in response to a mouse event and no item has already marked itself dirty.
|
|
||||||
itemPriv->dirty(QQuickItemPrivate::Content);
|
|
||||||
}
|
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
q->setHovered(false);
|
q->setHovered(false);
|
||||||
|
|
||||||
|
QQuickSinglePointHandlerPrivate::onEnabledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QQuickHoverHandlerPrivate::onParentChanged(QQuickItem *oldParent, QQuickItem *newParent)
|
||||||
|
{
|
||||||
|
if (oldParent)
|
||||||
|
updateHasHoverInChild(oldParent, false);
|
||||||
|
if (newParent)
|
||||||
|
updateHasHoverInChild(newParent, true);
|
||||||
|
|
||||||
|
QQuickSinglePointHandlerPrivate::onParentChanged(oldParent, newParent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QQuickHoverHandlerPrivate::updateHasHoverInChild(QQuickItem *item, bool hasHover)
|
||||||
|
{
|
||||||
|
QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(item);
|
||||||
|
itemPriv->setHasHoverInChild(hasHover);
|
||||||
|
// The DA needs to resolve which items and handlers should now be hovered or unhovered.
|
||||||
|
// Marking the parent item dirty ensures that flushFrameSynchronousEvents() will be called from the render loop,
|
||||||
|
// even if this change is not in response to a mouse event and no item has already marked itself dirty.
|
||||||
|
itemPriv->dirty(QQuickItemPrivate::Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickHoverHandler::QQuickHoverHandler(QQuickItem *parent)
|
QQuickHoverHandler::QQuickHoverHandler(QQuickItem *parent)
|
||||||
: QQuickSinglePointHandler(*(new QQuickHoverHandlerPrivate), parent)
|
: QQuickSinglePointHandler(*(new QQuickHoverHandlerPrivate), parent)
|
||||||
{
|
{
|
||||||
|
Q_D(QQuickHoverHandler);
|
||||||
// Tell QQuickPointerDeviceHandler::wantsPointerEvent() to ignore button state
|
// Tell QQuickPointerDeviceHandler::wantsPointerEvent() to ignore button state
|
||||||
d_func()->acceptedButtons = Qt::NoButton;
|
d->acceptedButtons = Qt::NoButton;
|
||||||
|
if (parent)
|
||||||
|
d->updateHasHoverInChild(parent, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickHoverHandler::~QQuickHoverHandler()
|
QQuickHoverHandler::~QQuickHoverHandler()
|
||||||
{
|
{
|
||||||
|
Q_D(QQuickHoverHandler);
|
||||||
if (auto parent = parentItem())
|
if (auto parent = parentItem())
|
||||||
QQuickItemPrivate::get(parent)->setHasHoverInChild(false);
|
d->updateHasHoverInChild(parent, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -107,9 +130,13 @@ bool QQuickHoverHandler::event(QEvent *event)
|
||||||
|
|
||||||
void QQuickHoverHandler::componentComplete()
|
void QQuickHoverHandler::componentComplete()
|
||||||
{
|
{
|
||||||
|
Q_D(QQuickHoverHandler);
|
||||||
QQuickSinglePointHandler::componentComplete();
|
QQuickSinglePointHandler::componentComplete();
|
||||||
if (auto par = parentItem())
|
|
||||||
QQuickItemPrivate::get(par)->setHasHoverInChild(true);
|
if (d->enabled) {
|
||||||
|
if (auto parent = parentItem())
|
||||||
|
d->updateHasHoverInChild(parent, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event)
|
bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event)
|
||||||
|
|
|
@ -597,15 +597,18 @@ QQuickItem *QQuickPointerHandler::parentItem() const
|
||||||
|
|
||||||
void QQuickPointerHandler::setParentItem(QQuickItem *p)
|
void QQuickPointerHandler::setParentItem(QQuickItem *p)
|
||||||
{
|
{
|
||||||
|
Q_D(QQuickPointerHandler);
|
||||||
if (QObject::parent() == p)
|
if (QObject::parent() == p)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
qCDebug(lcHandlerParent) << "reparenting handler" << this << ":" << parent() << "->" << p;
|
qCDebug(lcHandlerParent) << "reparenting handler" << this << ":" << parent() << "->" << p;
|
||||||
if (auto *oldParent = static_cast<QQuickItem *>(QObject::parent()))
|
auto *oldParent = static_cast<QQuickItem *>(QObject::parent());
|
||||||
|
if (oldParent)
|
||||||
QQuickItemPrivate::get(oldParent)->removePointerHandler(this);
|
QQuickItemPrivate::get(oldParent)->removePointerHandler(this);
|
||||||
setParent(p);
|
setParent(p);
|
||||||
if (p)
|
if (p)
|
||||||
QQuickItemPrivate::get(p)->addPointerHandler(this);
|
QQuickItemPrivate::get(p)->addPointerHandler(this);
|
||||||
|
d->onParentChanged(oldParent, p);
|
||||||
emit parentChanged();
|
emit parentChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ public:
|
||||||
bool dragOverThreshold(QVector2D delta) const;
|
bool dragOverThreshold(QVector2D delta) const;
|
||||||
bool dragOverThreshold(const QEventPoint &point) const;
|
bool dragOverThreshold(const QEventPoint &point) const;
|
||||||
|
|
||||||
|
virtual void onParentChanged(QQuickItem * /*oldParent*/, QQuickItem * /*newParent*/) {}
|
||||||
virtual void onEnabledChanged() {}
|
virtual void onEnabledChanged() {}
|
||||||
|
|
||||||
static QVector<QObject *> &deviceDeliveryTargets(const QInputDevice *device);
|
static QVector<QObject *> &deviceDeliveryTargets(const QInputDevice *device);
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Window 2.15
|
||||||
|
|
||||||
|
Window {
|
||||||
|
width: 320
|
||||||
|
height: 240
|
||||||
|
visible: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
objectName: "childItem"
|
||||||
|
width: 100
|
||||||
|
height: 100
|
||||||
|
color: "lightblue"
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ private slots:
|
||||||
void window();
|
void window();
|
||||||
void deviceCursor_data();
|
void deviceCursor_data();
|
||||||
void deviceCursor();
|
void deviceCursor();
|
||||||
|
void addHandlerFromCpp();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void createView(QScopedPointer<QQuickView> &window, const char *fileName);
|
void createView(QScopedPointer<QQuickView> &window, const char *fileName);
|
||||||
|
@ -567,6 +568,69 @@ void tst_HoverHandler::deviceCursor()
|
||||||
QCOMPARE(airbrushEraserHandler->isHovered(), true); // there was no fresh QTabletEvent to tell it not to be hovered
|
QCOMPARE(airbrushEraserHandler->isHovered(), true); // there was no fresh QTabletEvent to tell it not to be hovered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_HoverHandler::addHandlerFromCpp()
|
||||||
|
{
|
||||||
|
// Check that you can create a hover handler from c++, and add it
|
||||||
|
// as a child of an existing item. Continue to check that you can
|
||||||
|
// also change the parent item at runtime.
|
||||||
|
QQmlEngine engine;
|
||||||
|
QQmlComponent component(&engine);
|
||||||
|
component.loadUrl(testFileUrl("nohandler.qml"));
|
||||||
|
QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create()));
|
||||||
|
QVERIFY(!window.isNull());
|
||||||
|
window->show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
|
||||||
|
|
||||||
|
QQuickItem *childItem = window->findChild<QQuickItem *>("childItem");
|
||||||
|
QVERIFY(childItem);
|
||||||
|
|
||||||
|
// Move mouse outside child
|
||||||
|
const QPoint outside(200, 200);
|
||||||
|
const QPoint inside(50, 50);
|
||||||
|
QTest::mouseMove(window.data(), outside);
|
||||||
|
|
||||||
|
QQuickHoverHandler *handler = new QQuickHoverHandler(childItem);
|
||||||
|
QSignalSpy spy(handler, &QQuickHoverHandler::hoveredChanged);
|
||||||
|
|
||||||
|
// Move mouse inside child
|
||||||
|
QTest::mouseMove(window.data(), inside);
|
||||||
|
QVERIFY(handler->isHovered());
|
||||||
|
QCOMPARE(spy.count(), 1);
|
||||||
|
|
||||||
|
// Move mouse outside child
|
||||||
|
QTest::mouseMove(window.data(), outside);
|
||||||
|
QVERIFY(!handler->isHovered());
|
||||||
|
QCOMPARE(spy.count(), 2);
|
||||||
|
|
||||||
|
// Remove the parent item from the handler
|
||||||
|
spy.clear();
|
||||||
|
handler->setParentItem(nullptr);
|
||||||
|
|
||||||
|
// Move mouse inside child
|
||||||
|
QTest::mouseMove(window.data(), inside);
|
||||||
|
QVERIFY(!handler->isHovered());
|
||||||
|
QCOMPARE(spy.count(), 0);
|
||||||
|
|
||||||
|
// Move mouse outside child
|
||||||
|
QTest::mouseMove(window.data(), outside);
|
||||||
|
QVERIFY(!handler->isHovered());
|
||||||
|
QCOMPARE(spy.count(), 0);
|
||||||
|
|
||||||
|
// Reparent back the item to the handler
|
||||||
|
spy.clear();
|
||||||
|
handler->setParentItem(childItem);
|
||||||
|
|
||||||
|
// Move mouse inside child
|
||||||
|
QTest::mouseMove(window.data(), inside);
|
||||||
|
QVERIFY(handler->isHovered());
|
||||||
|
QCOMPARE(spy.count(), 1);
|
||||||
|
|
||||||
|
// Move mouse outside child
|
||||||
|
QTest::mouseMove(window.data(), outside);
|
||||||
|
QVERIFY(!handler->isHovered());
|
||||||
|
QCOMPARE(spy.count(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_HoverHandler)
|
QTEST_MAIN(tst_HoverHandler)
|
||||||
|
|
||||||
#include "tst_qquickhoverhandler.moc"
|
#include "tst_qquickhoverhandler.moc"
|
||||||
|
|
Loading…
Reference in New Issue