aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Arne Vestbø <[email protected]>2023-12-02 00:10:22 +0100
committerTor Arne Vestbø <[email protected]>2023-12-07 06:49:26 +0100
commit5b5c8aa4615fb8fb5fa27374479f0807fa73d64b (patch)
tree4f2855ee4db868cab592597be6554c0a25001ebe
parent6eba6dd95082d5087662f9755d2f01ecb741ddc6 (diff)
Ensure ItemObservesViewport works when item is moved to new ancestor
When an item has ItemObservesViewport set, it will receive transformChanged callbacks when any of its ancestors changes their position, size, etc. This relies on every ancestor having the subtreeTransformChangedEnabled flag set, which was previously ensured when the item called setFlag(). But when the item then moved to a different ancestor hierarchy, we would only set subtreeTransformChangedEnabled on the new parent, but not any of its ancestors, which meant that the item would no longer pick up changes beyond the immediate parent. We would also not send a transformChanged in response to the reparenting itself, which is critical, as the item's clip or scene positions may have changed in response to the new parent. Both of these cases are now handled as part of the ItemChildAddedChange of the new parent. Change-Id: I5000fb46d7638ce9674d4879890cf4344e5ce538 Reviewed-by: Volker Hilsheimer <[email protected]> Reviewed-by: Shawn Rutledge <[email protected]>
-rw-r--r--src/quick/items/qquickitem.cpp38
-rw-r--r--src/quick/items/qquickitem_p.h2
-rw-r--r--tests/auto/quick/qquickitem/tst_qquickitem.cpp72
3 files changed, 101 insertions, 11 deletions
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
index 8b1188e4d4..968c44e997 100644
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -6727,8 +6727,18 @@ void QQuickItemPrivate::itemChange(QQuickItem::ItemChange change, const QQuickIt
switch (change) {
case QQuickItem::ItemChildAddedChange: {
q->itemChange(change, data);
- if (!subtreeTransformChangedEnabled)
- subtreeTransformChangedEnabled = true;
+ // The newly added child or any of its descendants may have
+ // ItemObservesViewport set, in which case we need to both
+ // inform the item that the transform has changed, and re-apply
+ // subtreeTransformChangedEnabled to both this item and its
+ // ancestors.
+ if (QQuickItemPrivate::get(data.item)->transformChanged(q)) {
+ if (!subtreeTransformChangedEnabled) {
+ qCDebug(lcVP) << "turned on transformChanged notification for subtree of" << q;
+ subtreeTransformChangedEnabled = true;
+ }
+ enableSubtreeChangeNotificationsForParentHierachy();
+ }
notifyChangeListeners(QQuickItemPrivate::Children, &QQuickItemChangeListener::itemChildAdded, q, data.item);
break;
}
@@ -6958,15 +6968,21 @@ void QQuickItem::setFlag(Flag flag, bool enabled)
// We don't return early if the flag did not change. That's useful in case
// we need to intentionally trigger this parent-chain traversal again.
- if (enabled && flag == ItemObservesViewport) {
- QQuickItem *par = parentItem();
- while (par) {
- auto parPriv = QQuickItemPrivate::get(par);
- if (!parPriv->subtreeTransformChangedEnabled)
- qCDebug(lcVP) << "turned on transformChanged notification for subtree of" << par;
- parPriv->subtreeTransformChangedEnabled = true;
- par = par->parentItem();
- }
+ if (enabled && flag == ItemObservesViewport)
+ d->enableSubtreeChangeNotificationsForParentHierachy();
+}
+
+void QQuickItemPrivate::enableSubtreeChangeNotificationsForParentHierachy()
+{
+ Q_Q(QQuickItem);
+
+ QQuickItem *par = q->parentItem();
+ while (par) {
+ auto parPriv = QQuickItemPrivate::get(par);
+ if (!parPriv->subtreeTransformChangedEnabled)
+ qCDebug(lcVP) << "turned on transformChanged notification for subtree of" << par;
+ parPriv->subtreeTransformChangedEnabled = true;
+ par = par->parentItem();
}
}
diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h
index 205169066a..63c8896680 100644
--- a/src/quick/items/qquickitem_p.h
+++ b/src/quick/items/qquickitem_p.h
@@ -685,6 +685,8 @@ public:
void itemChange(QQuickItem::ItemChange, const QQuickItem::ItemChangeData &);
+ void enableSubtreeChangeNotificationsForParentHierachy();
+
virtual void mirrorChange() {}
void setHasCursorInChild(bool hasCursor);
diff --git a/tests/auto/quick/qquickitem/tst_qquickitem.cpp b/tests/auto/quick/qquickitem/tst_qquickitem.cpp
index 2eb16c5a4e..3a4f7e4364 100644
--- a/tests/auto/quick/qquickitem/tst_qquickitem.cpp
+++ b/tests/auto/quick/qquickitem/tst_qquickitem.cpp
@@ -230,6 +230,8 @@ private slots:
void objectCastInDestructor();
void listsAreNotLists();
+ void transformChanged();
+
private:
enum PaintOrderOp {
@@ -2605,6 +2607,76 @@ void tst_qquickitem::listsAreNotLists()
QCOMPARE(children.count(&children), 0);
}
+class TransformItemPrivate;
+class TransformItem : public QQuickItem {
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(TransformItem)
+public:
+ TransformItem(QQuickItem *parent = nullptr);
+
+ bool transformChanged = false;
+};
+
+class TransformItemPrivate :public QQuickItemPrivate
+{
+protected:
+ Q_DECLARE_PUBLIC(TransformItem)
+
+ bool transformChanged(QQuickItem *transformedItem) override
+ {
+ Q_Q(TransformItem);
+ q->transformChanged = true;
+ return true;
+ }
+};
+
+TransformItem::TransformItem(QQuickItem *parent)
+ : QQuickItem(*(new TransformItemPrivate), parent)
+{
+}
+
+void tst_qquickitem::transformChanged()
+{
+ QQuickItem rootItem;
+ QQuickItem *parents[2][3] = {};
+ for (int i = 0; i < 2; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ auto *item = new QQuickItem;
+ item->setObjectName(QString("Item-%1.%2").arg(i).arg(j));
+ item->setParentItem(j > 0 ? parents[i][j - 1] : &rootItem);
+ parents[i][j] = item;
+ }
+ }
+
+ // Setting the root item's position will result in transformChanged,
+ // which will clear the subtreeTransformChangedEnabled flag that is
+ // by default set to true, since there are no children that observe
+ // the viewport.
+ parents[0][0]->setPosition(QPoint(100, 100));
+ parents[1][0]->setPosition(QPoint(200, 200));
+
+ TransformItem transformItem;
+ transformItem.setParentItem(parents[0][2]);
+ transformItem.setFlag(QQuickItem::ItemObservesViewport);
+ QCOMPARE(transformItem.mapToScene(QPoint(0, 0)), parents[0][0]->position());
+
+ parents[0][0]->setPosition(QPoint(110, 110));
+ QVERIFY2(transformItem.transformChanged,
+ "Moving an ancestor should trigger transformChanged");
+
+ transformItem.transformChanged = false;
+ transformItem.setParentItem(parents[1][2]);
+ QVERIFY2(transformItem.transformChanged,
+ "Reparenting the item should result in a transformChanged");
+ QCOMPARE(transformItem.mapToScene(QPoint(0, 0)), parents[1][0]->position());
+
+ transformItem.transformChanged = false;
+ parents[1][0]->setPosition(QPoint(220, 220));
+ QVERIFY2(transformItem.transformChanged,
+ "Changing one of the new ancestors should result in transformChanged");
+ QCOMPARE(transformItem.mapToScene(QPoint(0, 0)), parents[1][0]->position());
+}
+
QTEST_MAIN(tst_qquickitem)
#include "tst_qquickitem.moc"