aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Arve Sæther <jan-arve.saether@qt.io>2020-04-15 16:18:03 +0200
committerJan Arve Sæther <jan-arve.saether@qt.io>2020-05-26 14:43:35 +0200
commita5d2fd816bcbee6026894927ae5d049536bfc7ea (patch)
treee294fed2da99b2cda21440a314262986940ea91f
parentf77a9830c8eda7728de49a101be66fc171d3ff8d (diff)
Improve performance when dynamically adding items to a layout
This was especially noticeable when a Repeater was populating the children of a layout, and its model was dynamically changed: When the model was changed, the repeater removed one item at a time until all items were removed, then applied the new model and then added all the new items for the new model. The layout reacted to that by doing a full sync of the QML layout into the internal gridlayout engine each time an item got removed or added. For very large layouts (or layouts that have complex size hints to calculate), this caused a major slowdown. This patch fixes that by postponing the sync until we get a updatePolish(), basically replacing most calls to updateLayoutItems() (which does the sync) with a call to invalidate() (which schedules a polish). It will also get rid of some binding loop warnings due to this change. This means that there is a small change in behavior: impicitWidth, implicitHeight and Layout.{min,max}imum{Width,Height} might in some cases be incorrect until the updatePolish() have been done. (This is however consistent with how Row/Column/Grid behaves) The creation test in qmlbench was quite simple, so it did not suffer from the most severe performance issues, but we did not regress: > compareresults: auto/creation/layouts/delegates_rowlayout.qml: improvement by 3.91% auto/creation/layouts/delegates_columnlayout.qml: improvement by 6.59% auto/creation/layouts/delegates_gridlayout.qml: improvement by 1.83% Overall average of differences: 4.11% And for the gridlayout_large.qml (Repeater with 1000 dynamically changing items): > compareresults: auto/layouts/gridlayout_large.qml: improvement by 66477.78% Overall average of differences: 66477.78% [ChangeLog][Qt Quick Layouts] Performance improvements to Qt Quick Layouts. This has the small side-effect that size hints (implicitWidth/implicithHeight etc) changes are not immediately emitted after a layout has been modified (e.g item added) Fixes: QTBUG-71839 Fixes: QTBUG-65121 Fixes: QTBUG-66017 Change-Id: I6922efe449134246df66b177992e4442747bc8fb Reviewed-by: Mitch Curtis <mitch.curtis@qt.io> (cherry picked from commit cc77a0bc549ce8f0b218661e7ae0e82e5b89e0da) Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
-rw-r--r--src/imports/layouts/qquickgridlayoutengine_p.h3
-rw-r--r--src/imports/layouts/qquicklayout.cpp105
-rw-r--r--src/imports/layouts/qquicklayout_p.h25
-rw-r--r--src/imports/layouts/qquicklinearlayout.cpp86
-rw-r--r--src/imports/layouts/qquickstacklayout.cpp53
-rw-r--r--src/imports/layouts/qquickstacklayout_p.h1
-rw-r--r--tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml60
-rw-r--r--tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml89
-rw-r--r--tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml2
9 files changed, 301 insertions, 123 deletions
diff --git a/src/imports/layouts/qquickgridlayoutengine_p.h b/src/imports/layouts/qquickgridlayoutengine_p.h
index ca3b518ee5..5529d1049c 100644
--- a/src/imports/layouts/qquickgridlayoutengine_p.h
+++ b/src/imports/layouts/qquickgridlayoutengine_p.h
@@ -112,8 +112,9 @@ public:
const QSizeF newSize = r.size();
m_item->setPosition(r.topLeft());
if (newSize == oldSize) {
+ // We need to enforce a rearrange when the geometry is the same
if (QQuickLayout *lay = qobject_cast<QQuickLayout *>(m_item)) {
- if (lay->arrangementIsDirty())
+ if (lay->invalidatedArrangement())
lay->rearrange(newSize);
}
} else {
diff --git a/src/imports/layouts/qquicklayout.cpp b/src/imports/layouts/qquicklayout.cpp
index 43c39290c3..a34b77ca11 100644
--- a/src/imports/layouts/qquicklayout.cpp
+++ b/src/imports/layouts/qquicklayout.cpp
@@ -41,6 +41,7 @@
#include <QEvent>
#include <QtCore/qcoreapplication.h>
#include <QtCore/private/qnumeric_p.h>
+#include <QtCore/qstack.h>
#include <QtCore/qmath.h>
#include <QtQml/qqmlinfo.h>
#include <limits>
@@ -380,7 +381,7 @@ void QQuickLayoutAttached::setRow(int row)
{
if (row >= 0 && row != m_row) {
m_row = row;
- repopulateLayout();
+ invalidateItem();
emit rowChanged();
}
}
@@ -401,7 +402,7 @@ void QQuickLayoutAttached::setColumn(int column)
{
if (column >= 0 && column != m_column) {
m_column = column;
- repopulateLayout();
+ invalidateItem();
emit columnChanged();
}
}
@@ -628,7 +629,7 @@ void QQuickLayoutAttached::setRowSpan(int span)
{
if (span != m_rowSpan) {
m_rowSpan = span;
- repopulateLayout();
+ invalidateItem();
emit rowSpanChanged();
}
}
@@ -648,7 +649,7 @@ void QQuickLayoutAttached::setColumnSpan(int span)
{
if (span != m_columnSpan) {
m_columnSpan = span;
- repopulateLayout();
+ invalidateItem();
emit columnSpanChanged();
}
}
@@ -669,20 +670,12 @@ qreal QQuickLayoutAttached::sizeHint(Qt::SizeHint which, Qt::Orientation orienta
void QQuickLayoutAttached::invalidateItem()
{
- if (!m_changesNotificationEnabled)
- return;
qCDebug(lcQuickLayouts) << "QQuickLayoutAttached::invalidateItem";
if (QQuickLayout *layout = parentLayout()) {
layout->invalidate(item());
}
}
-void QQuickLayoutAttached::repopulateLayout()
-{
- if (QQuickLayout *layout = parentLayout())
- layout->updateLayoutItems();
-}
-
QQuickLayout *QQuickLayoutAttached::parentLayout() const
{
QQuickItem *parentItem = item();
@@ -700,10 +693,41 @@ QQuickItem *QQuickLayoutAttached::item() const
return qobject_cast<QQuickItem *>(parent());
}
+qreal QQuickLayoutPrivate::getImplicitWidth() const
+{
+ Q_Q(const QQuickLayout);
+ if (q->invalidated()) {
+ QQuickLayoutPrivate *that = const_cast<QQuickLayoutPrivate*>(this);
+ that->implicitWidth = q->sizeHint(Qt::PreferredSize).width();
+ }
+ return implicitWidth;
+}
+
+qreal QQuickLayoutPrivate::getImplicitHeight() const
+{
+ Q_Q(const QQuickLayout);
+ if (q->invalidated()) {
+ QQuickLayoutPrivate *that = const_cast<QQuickLayoutPrivate*>(this);
+ that->implicitHeight = q->sizeHint(Qt::PreferredSize).height();
+ }
+ return implicitHeight;
+}
+
+void QQuickLayoutPrivate::applySizeHints() const {
+ Q_Q(const QQuickLayout);
+ QQuickLayout *that = const_cast<QQuickLayout*>(q);
+ QQuickLayoutAttached *info = attachedLayoutObject(that, true);
+
+ const QSizeF min = q->sizeHint(Qt::MinimumSize);
+ const QSizeF max = q->sizeHint(Qt::MaximumSize);
+ const QSizeF pref = q->sizeHint(Qt::PreferredSize);
+ info->setMinimumImplicitSize(min);
+ info->setMaximumImplicitSize(max);
+ that->setImplicitSize(pref.width(), pref.height());
+}
QQuickLayout::QQuickLayout(QQuickLayoutPrivate &dd, QQuickItem *parent)
: QQuickItem(dd, parent)
- , m_dirty(false)
, m_inUpdatePolish(false)
, m_polishInsideUpdatePolish(0)
{
@@ -734,6 +758,20 @@ void QQuickLayout::updatePolish()
{
qCDebug(lcQuickLayouts) << "updatePolish() ENTERING" << this;
m_inUpdatePolish = true;
+
+ // Might have become "undirty" before we reach this updatePolish()
+ // (e.g. if somebody queried for implicitWidth it will immediately
+ // calculate size hints)
+ if (invalidated()) {
+ // Ensure that all invalidated layouts are synced and valid again. Since
+ // ensureLayoutItemsUpdated() will also call applySizeHints(), and sizeHint() will call its
+ // childrens sizeHint(), and sizeHint() will call ensureLayoutItemsUpdated(), this will be done
+ // recursive as we want.
+ // Note that we need to call ensureLayoutItemsUpdated() *before* we query width() and height(),
+ // because width()/height() might return their implicitWidth/implicitHeight (e.g. for a layout
+ // with no explicitly specified size, (nor anchors.fill: parent))
+ ensureLayoutItemsUpdated();
+ }
rearrange(QSizeF(width(), height()));
m_inUpdatePolish = false;
qCDebug(lcQuickLayouts) << "updatePolish() LEAVING" << this;
@@ -750,10 +788,13 @@ void QQuickLayout::componentComplete()
void QQuickLayout::invalidate(QQuickItem * /*childItem*/)
{
- if (m_dirty)
+ Q_D(QQuickLayout);
+ if (invalidated())
return;
- m_dirty = true;
+ qCDebug(lcQuickLayouts) << "QQuickLayout::invalidate()" << this;
+ d->m_dirty = true;
+ d->m_dirtyArrangement = true;
if (!qobject_cast<QQuickLayout *>(parentItem())) {
@@ -775,7 +816,6 @@ void QQuickLayout::invalidate(QQuickItem * /*childItem*/)
bool QQuickLayout::shouldIgnoreItem(QQuickItem *child, QQuickLayoutAttached *&info, QSizeF *sizeHints) const
{
- Q_D(const QQuickLayout);
bool ignoreItem = true;
QQuickItemPrivate *childPrivate = QQuickItemPrivate::get(child);
if (childPrivate->explicitVisible) {
@@ -794,8 +834,6 @@ bool QQuickLayout::shouldIgnoreItem(QQuickItem *child, QQuickLayoutAttached *&in
if (!ignoreItem && childPrivate->isTransparentForPositioner())
ignoreItem = true;
- if (ignoreItem)
- d->m_ignoredItems << child;
return ignoreItem;
}
@@ -806,6 +844,17 @@ void QQuickLayout::checkAnchors(QQuickItem *item) const
qmlWarning(item) << "Detected anchors on an item that is managed by a layout. This is undefined behavior; use Layout.alignment instead.";
}
+void QQuickLayout::ensureLayoutItemsUpdated() const
+{
+ Q_D(const QQuickLayout);
+ if (!invalidated())
+ return;
+ const_cast<QQuickLayout*>(this)->updateLayoutItems();
+ d->m_dirty = false;
+ d->applySizeHints();
+}
+
+
void QQuickLayout::itemChange(ItemChange change, const ItemChangeData &value)
{
if (change == ItemChildAddedChange) {
@@ -816,14 +865,14 @@ void QQuickLayout::itemChange(ItemChange change, const ItemChangeData &value)
d->m_hasItemChangeListeners = true;
qCDebug(lcQuickLayouts) << "ChildAdded" << item;
if (isReady())
- updateLayoutItems();
+ invalidate();
} else if (change == ItemChildRemovedChange) {
QQuickItem *item = value.item;
qmlobject_disconnect(item, QQuickItem, SIGNAL(baselineOffsetChanged(qreal)), this, QQuickLayout, SLOT(invalidateSenderItem()));
QQuickItemPrivate::get(item)->removeItemChangeListener(this, changeTypes);
qCDebug(lcQuickLayouts) << "ChildRemoved" << item;
if (isReady())
- updateLayoutItems();
+ invalidate();
}
QQuickItem::itemChange(change, value);
}
@@ -877,10 +926,20 @@ void QQuickLayout::deactivateRecur()
}
}
+bool QQuickLayout::invalidated() const
+{
+ return d_func()->m_dirty;
+}
+
+bool QQuickLayout::invalidatedArrangement() const
+{
+ return d_func()->m_dirtyArrangement;
+}
+
void QQuickLayout::itemSiblingOrderChanged(QQuickItem *item)
{
Q_UNUSED(item);
- updateLayoutItems();
+ invalidate();
}
void QQuickLayout::itemImplicitWidthChanged(QQuickItem *item)
@@ -909,7 +968,7 @@ void QQuickLayout::itemVisibilityChanged(QQuickItem *item)
void QQuickLayout::rearrange(const QSizeF &/*size*/)
{
- m_dirty = false;
+ d_func()->m_dirtyArrangement = false;
}
@@ -1188,7 +1247,7 @@ void QQuickLayout::dumpLayoutTreeRecursive(int level, QString &buf) const
buf += formatLine("%1 {").arg(QQmlMetaType::prettyTypeName(this));
++level;
buf += formatLine("// Effective calculated values:");
- buf += formatLine("sizeHintDirty: %2").arg(m_dirty);
+ buf += formatLine("sizeHintDirty: %2").arg(invalidated());
QSizeF min = sizeHint(Qt::MinimumSize);
buf += formatLine("sizeHint.min : [%1, %2]").arg(f2s(min.width()), 5).arg(min.height(), 5);
QSizeF pref = sizeHint(Qt::PreferredSize);
diff --git a/src/imports/layouts/qquicklayout_p.h b/src/imports/layouts/qquicklayout_p.h
index 52442b68a0..fb3b39d65d 100644
--- a/src/imports/layouts/qquicklayout_p.h
+++ b/src/imports/layouts/qquicklayout_p.h
@@ -79,13 +79,13 @@ public:
virtual void setAlignment(QQuickItem *item, Qt::Alignment align) = 0;
virtual void invalidate(QQuickItem * childItem = 0);
virtual void updateLayoutItems() = 0;
+ void ensureLayoutItemsUpdated() const;
// iterator
virtual QQuickItem *itemAt(int index) const = 0;
virtual int itemCount() const = 0;
virtual void rearrange(const QSizeF &);
- bool arrangementIsDirty() const { return m_dirty; }
static void effectiveSizeHints_helper(QQuickItem *item, QSizeF *cachedSizeHints, QQuickLayoutAttached **info, bool useFallbackToWidthOrHeight);
static QLayoutPolicy::Policy effectiveSizePolicy_helper(QQuickItem *item, Qt::Orientation orientation, QQuickLayoutAttached *info);
@@ -97,6 +97,8 @@ public:
bool isReady() const;
void deactivateRecur();
+ bool invalidated() const;
+ bool invalidatedArrangement() const;
/* QQuickItemChangeListener */
void itemSiblingOrderChanged(QQuickItem *item) override;
@@ -121,7 +123,6 @@ protected slots:
void invalidateSenderItem();
private:
- unsigned m_dirty : 1;
unsigned m_inUpdatePolish : 1;
unsigned m_polishInsideUpdatePolish : 2;
@@ -135,9 +136,26 @@ class QQuickLayoutPrivate : public QQuickItemPrivate
{
Q_DECLARE_PUBLIC(QQuickLayout)
public:
- QQuickLayoutPrivate() : m_isReady(false), m_disableRearrange(true), m_hasItemChangeListeners(false) {}
+ QQuickLayoutPrivate() : m_dirty(true), m_dirtyArrangement(true), m_isReady(false), m_disableRearrange(true), m_hasItemChangeListeners(false) {}
+
+ qreal getImplicitWidth() const override;
+ qreal getImplicitHeight() const override;
+
+ void applySizeHints() const;
protected:
+ /* m_dirty == true means that something in the layout was changed,
+ but its state has not been synced to the internal grid layout engine. It is usually:
+ 1. A child item was added or removed from the layout (or made visible/invisble)
+ 2. A child item got one of its size hints changed
+ */
+ mutable unsigned m_dirty : 1;
+ /* m_dirtyArrangement == true means that the layout still needs a rearrange despite that
+ * m_dirty == false. This is only used for the case that a layout has been invalidated,
+ * but its new size is the same as the old size (in that case the child layout won't get
+ * a geometryChanged() notification, which rearrange() usually reacts to)
+ */
+ mutable unsigned m_dirtyArrangement : 1;
unsigned m_isReady : 1;
unsigned m_disableRearrange : 1;
unsigned m_hasItemChangeListeners : 1; // if false, we don't need to remove its item change listeners...
@@ -285,7 +303,6 @@ signals:
private:
void invalidateItem();
- void repopulateLayout();
QQuickLayout *parentLayout() const;
QQuickItem *item() const;
private:
diff --git a/src/imports/layouts/qquicklinearlayout.cpp b/src/imports/layouts/qquicklinearlayout.cpp
index 18caa18fa5..4df264f264 100644
--- a/src/imports/layouts/qquicklinearlayout.cpp
+++ b/src/imports/layouts/qquicklinearlayout.cpp
@@ -254,6 +254,7 @@ void QQuickGridLayoutBase::setOrientation(Qt::Orientation orientation)
QSizeF QQuickGridLayoutBase::sizeHint(Qt::SizeHint whichSizeHint) const
{
Q_D(const QQuickGridLayoutBase);
+ ensureLayoutItemsUpdated();
return d->engine.sizeHint(whichSizeHint, QSizeF(), d->styleInfo);
}
@@ -315,14 +316,27 @@ QQuickGridLayoutBase::~QQuickGridLayoutBase()
void QQuickGridLayoutBase::componentComplete()
{
- qCDebug(lcQuickLayouts) << objectName() << "QQuickGridLayoutBase::componentComplete()" << parent();
+ qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::componentComplete()" << this << parent();
QQuickLayout::componentComplete();
- updateLayoutItems();
+
+ /* The layout is invalid when it is constructed, but during construction of the layout and
+ its children (in the "static/from QML" case which this is trying to cover) things
+ change and as a consequence invalidate() and ensureLayoutItemsUpdated() might be called.
+ As soon as ensureLayoutItemsUpdated() is called it will set d->dirty = false.
+ However, a subsequent invalidate() will return early if the component is not completed
+ because it knows that componentComplete() will take care of doing the proper layouting
+ (so it won't set d->dirty = true). When we then call ensureLayoutItemsUpdated() again here
+ it sees that its not dirty and assumes everything up-to-date. For those cases we therefore
+ need to call invalidate() in advance
+ */
+ invalidate();
+ ensureLayoutItemsUpdated();
QQuickItem *par = parentItem();
if (qobject_cast<QQuickLayout*>(par))
return;
rearrange(QSizeF(width(), height()));
+ qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::componentComplete(). COMPLETED" << this << parent();
}
/*
@@ -342,13 +356,12 @@ void QQuickGridLayoutBase::componentComplete()
}
}
- 1. l2->invalidateChildItem(a) is called on l2, where item refers to "a".
+ 1. l2->invalidate(a) is called on l2, where item refers to "a".
(this will dirty the cached size hints of item "a")
- 2. l2->invalidate() is called
- this will :
+ 2. The layout engine will invalidate:
i) invalidate the layout engine
- ii) dirty the cached size hints of item "l2" (by calling parentLayout()->invalidateChildItem
-
+ ii) dirty the cached size hints of item "l2" (by calling parentLayout()->invalidate(l2)
+ The recursion continues to the topmost layout
*/
/*!
\internal
@@ -363,43 +376,29 @@ void QQuickGridLayoutBase::invalidate(QQuickItem *childItem)
Q_D(QQuickGridLayoutBase);
if (!isReady())
return;
+ qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::invalidate()" << this << ", invalidated:" << invalidated();
+ if (invalidated()) {
+ return;
+ }
+ qCDebug(lcQuickLayouts) << "d->m_rearranging:" << d->m_rearranging;
if (d->m_rearranging) {
d->m_invalidateAfterRearrange << childItem;
return;
}
- qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::invalidate()";
-
if (childItem) {
if (QQuickGridLayoutItem *layoutItem = d->engine.findLayoutItem(childItem))
layoutItem->invalidate();
- if (d->m_ignoredItems.contains(childItem)) {
- updateLayoutItems();
- return;
- }
}
// invalidate engine
d->engine.invalidate();
- QQuickLayout::invalidate(this);
+ qCDebug(lcQuickLayouts) << "calling QQuickLayout::invalidate();";
+ QQuickLayout::invalidate();
- QQuickLayoutAttached *info = attachedLayoutObject(this);
-
- const QSizeF min = sizeHint(Qt::MinimumSize);
- const QSizeF pref = sizeHint(Qt::PreferredSize);
- const QSizeF max = sizeHint(Qt::MaximumSize);
-
- const bool old = info->setChangesNotificationEnabled(false);
- info->setMinimumImplicitSize(min);
- info->setMaximumImplicitSize(max);
- info->setChangesNotificationEnabled(old);
- if (pref.width() == implicitWidth() && pref.height() == implicitHeight()) {
- // In case setImplicitSize does not emit implicit{Width|Height}Changed
- if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem()))
- parentLayout->invalidate(this);
- } else {
- setImplicitSize(pref.width(), pref.height());
- }
+ if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem()))
+ parentLayout->invalidate(this);
+ qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::invalidate() LEAVING" << this;
}
void QQuickGridLayoutBase::updateLayoutItems()
@@ -412,23 +411,25 @@ void QQuickGridLayoutBase::updateLayoutItems()
return;
}
- qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::updateLayoutItems()";
+ qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::updateLayoutItems ENTERING" << this;
d->engine.deleteItems();
insertLayoutItems();
-
- invalidate();
- qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::updateLayoutItems() LEAVING";
+ qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::updateLayoutItems() LEAVING" << this;
}
QQuickItem *QQuickGridLayoutBase::itemAt(int index) const
{
Q_D(const QQuickGridLayoutBase);
+ qCDebug(lcQuickLayouts).nospace() << "QQuickGridLayoutBase::itemAt(" << index << ")";
+ ensureLayoutItemsUpdated();
+ qCDebug(lcQuickLayouts).nospace() << "QQuickGridLayoutBase::itemAt(" << index << ") LEAVING";
return static_cast<QQuickGridLayoutItem*>(d->engine.itemAt(index))->layoutItem();
}
int QQuickGridLayoutBase::itemCount() const
{
Q_D(const QQuickGridLayoutBase);
+ ensureLayoutItemsUpdated();
return d->engine.itemCount();
}
@@ -460,7 +461,7 @@ void QQuickGridLayoutBase::itemVisibilityChanged(QQuickItem *item)
if (!isReady())
return;
qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::itemVisibilityChanged()";
- updateLayoutItems();
+ invalidate(item);
}
void QQuickGridLayoutBase::rearrange(const QSizeF &size)
@@ -469,6 +470,9 @@ void QQuickGridLayoutBase::rearrange(const QSizeF &size)
if (!isReady())
return;
+ ensureLayoutItemsUpdated();
+
+ qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::rearrange" << d->m_recurRearrangeCounter << this;
const auto refCounter = qScopeGuard([&d] {
--(d->m_recurRearrangeCounter);
});
@@ -502,7 +506,7 @@ void QQuickGridLayoutBase::rearrange(const QSizeF &size)
d->m_invalidateAfterRearrange.clear();
if (d->m_updateAfterRearrange) {
- updateLayoutItems();
+ ensureLayoutItemsUpdated();
d->m_updateAfterRearrange = false;
}
}
@@ -582,7 +586,7 @@ void QQuickGridLayout::setColumns(int columns)
if (d->columns == columns)
return;
d->columns = columns;
- updateLayoutItems();
+ invalidate();
emit columnsChanged();
}
@@ -605,7 +609,7 @@ void QQuickGridLayout::setRows(int rows)
if (d->rows == rows)
return;
d->rows = rows;
- updateLayoutItems();
+ invalidate();
emit rowsChanged();
}
@@ -643,7 +647,7 @@ void QQuickGridLayout::setFlow(QQuickGridLayout::Flow flow)
return;
d->flow = flow;
// If flow is changed, the layout needs to be repopulated
- updateLayoutItems();
+ invalidate();
emit flowChanged();
}
@@ -664,7 +668,6 @@ void QQuickGridLayout::insertLayoutItems()
if (flowBound < 0)
flowBound = std::numeric_limits<int>::max();
- d->m_ignoredItems.clear();
QSizeF sizeHints[Qt::NSizeHints];
const auto items = childItems();
for (QQuickItem *child : items) {
@@ -852,7 +855,6 @@ void QQuickLinearLayout::setSpacing(qreal space)
void QQuickLinearLayout::insertLayoutItems()
{
Q_D(QQuickLinearLayout);
- d->m_ignoredItems.clear();
QSizeF sizeHints[Qt::NSizeHints];
const auto items = childItems();
for (QQuickItem *child : items) {
diff --git a/src/imports/layouts/qquickstacklayout.cpp b/src/imports/layouts/qquickstacklayout.cpp
index 4c1d611409..d6ee4afe84 100644
--- a/src/imports/layouts/qquickstacklayout.cpp
+++ b/src/imports/layouts/qquickstacklayout.cpp
@@ -114,6 +114,7 @@ QQuickStackLayout::QQuickStackLayout(QQuickItem *parent) :
int QQuickStackLayout::count() const
{
Q_D(const QQuickStackLayout);
+ ensureLayoutItemsUpdated();
return d->count;
}
@@ -126,6 +127,7 @@ int QQuickStackLayout::count() const
int QQuickStackLayout::currentIndex() const
{
Q_D(const QQuickStackLayout);
+ ensureLayoutItemsUpdated();
return d->currentIndex;
}
@@ -133,6 +135,7 @@ void QQuickStackLayout::setCurrentIndex(int index)
{
Q_D(QQuickStackLayout);
if (index != d->currentIndex) {
+ ensureLayoutItemsUpdated();
QQuickItem *prev = itemAt(d->currentIndex);
QQuickItem *next = itemAt(index);
d->currentIndex = index;
@@ -149,11 +152,12 @@ void QQuickStackLayout::setCurrentIndex(int index)
}
}
+
void QQuickStackLayout::componentComplete()
{
QQuickLayout::componentComplete(); // will call our geometryChange(), (where isComponentComplete() == true)
- updateLayoutItems();
+ ensureLayoutItemsUpdated();
QQuickItem *par = parentItem();
if (qobject_cast<QQuickLayout*>(par))
@@ -162,8 +166,21 @@ void QQuickStackLayout::componentComplete()
rearrange(QSizeF(width(), height()));
}
+void QQuickStackLayout::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
+{
+ QQuickLayout::itemChange(change, value);
+
+ if (change == ItemChildRemovedChange) {
+ invalidate();
+ } else if (change == ItemChildAddedChange) {
+ invalidate();
+ }
+}
+
QSizeF QQuickStackLayout::sizeHint(Qt::SizeHint whichSizeHint) const
{
+ Q_D(const QQuickStackLayout);
+ ensureLayoutItemsUpdated();
QSizeF &askingFor = m_cachedSizeHints[whichSizeHint];
if (!askingFor.isValid()) {
QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize];
@@ -186,11 +203,13 @@ QSizeF QQuickStackLayout::sizeHint(Qt::SizeHint whichSizeHint) const
// Not sure how descent makes sense here...
}
}
+ d->m_dirty = false;
return askingFor;
}
int QQuickStackLayout::indexOf(QQuickItem *childItem) const
{
+ ensureLayoutItemsUpdated();
if (childItem) {
int indexOfItem = 0;
const auto items = childItems();
@@ -237,13 +256,6 @@ void QQuickStackLayout::setAlignment(QQuickItem * /*item*/, Qt::Alignment /*alig
void QQuickStackLayout::invalidate(QQuickItem *childItem)
{
- Q_D(QQuickStackLayout);
- if (d->m_ignoredItems.contains(childItem)) {
- // If an invalid item gets a valid size, it should be included, as it was added to the layout
- updateLayoutItems();
- return;
- }
-
const int indexOfChild = indexOf(childItem);
if (indexOfChild >= 0 && indexOfChild < m_cachedItemSizeHints.count()) {
m_cachedItemSizeHints[indexOfChild].min() = QSizeF();
@@ -255,23 +267,8 @@ void QQuickStackLayout::invalidate(QQuickItem *childItem)
m_cachedSizeHints[i] = QSizeF();
QQuickLayout::invalidate(this);
- QQuickLayoutAttached *info = attachedLayoutObject(this);
-
- const QSizeF min = sizeHint(Qt::MinimumSize);
- const QSizeF pref = sizeHint(Qt::PreferredSize);
- const QSizeF max = sizeHint(Qt::MaximumSize);
-
- const bool old = info->setChangesNotificationEnabled(false);
- info->setMinimumImplicitSize(min);
- info->setMaximumImplicitSize(max);
- info->setChangesNotificationEnabled(old);
- if (pref.width() == implicitWidth() && pref.height() == implicitHeight()) {
- // In case setImplicitSize does not emit implicit{Width|Height}Changed
- if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem()))
- parentLayout->invalidate(this);
- } else {
- setImplicitSize(pref.width(), pref.height());
- }
+ if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem()))
+ parentLayout->invalidate(this);
}
void QQuickStackLayout::updateLayoutItems()
@@ -295,8 +292,6 @@ void QQuickStackLayout::updateLayoutItems()
checkAnchors(child);
child->setVisible(d->currentIndex == i);
}
-
- invalidate();
}
void QQuickStackLayout::rearrange(const QSizeF &newSize)
@@ -304,7 +299,9 @@ void QQuickStackLayout::rearrange(const QSizeF &newSize)
Q_D(QQuickStackLayout);
if (newSize.isNull() || !newSize.isValid())
return;
- (void)sizeHint(Qt::PreferredSize); // Make sure m_cachedItemSizeHints are valid
+
+ qCDebug(lcQuickLayouts) << "QQuickStackLayout::rearrange";
+ ensureLayoutItemsUpdated();
if (d->currentIndex == -1 || d->currentIndex >= m_cachedItemSizeHints.count())
return;
diff --git a/src/imports/layouts/qquickstacklayout_p.h b/src/imports/layouts/qquickstacklayout_p.h
index 537d54900f..0319e259d4 100644
--- a/src/imports/layouts/qquickstacklayout_p.h
+++ b/src/imports/layouts/qquickstacklayout_p.h
@@ -61,6 +61,7 @@ public:
void setCurrentIndex(int index);
void componentComplete() override;
+ void itemChange(ItemChange change, const ItemChangeData &value) override;
QSizeF sizeHint(Qt::SizeHint whichSizeHint) const override;
void setAlignment(QQuickItem *item, Qt::Alignment align) override;
void invalidate(QQuickItem *childItem = 0) override;
diff --git a/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml
index be94fca8d4..0fa84a1617 100644
--- a/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml
+++ b/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
@@ -48,7 +48,7 @@
**
****************************************************************************/
-import QtQuick 2.2
+import QtQuick 2.6
import QtTest 1.0
import QtQuick.Layouts 1.1
@@ -1106,5 +1106,61 @@ Item {
layout.destroy()
}
+ // ------------------
+ Component {
+ id: replaceCell_QTBUG_65121
+ GridLayout {
+ id: gridLayout
+ anchors.fill: parent
+ columns: 2
+ property var categories: ['one', 'two', 'three']
+ property var values: [1, 2, 3]
+ Repeater {
+ model: gridLayout.categories
+ Item {
+ Layout.row: index
+ Layout.column: 0
+ Layout.preferredWidth: label.width
+ Layout.fillHeight: true
+ Text {
+ id: label
+ height: parent.height
+ anchors.right: parent.right
+ text: modelData
+ verticalAlignment: Text.AlignVCenter
+ font.pointSize: 27
+ leftPadding: 10
+ }
+ }
+ }
+ Repeater {
+ model: gridLayout.values
+ Item {
+ Layout.row: index
+ Layout.column: 1
+ Layout.preferredWidth: label.width
+ Layout.fillHeight: true
+ Text {
+ id: label
+ height: parent.height
+ anchors.right: parent.right
+ text: modelData
+ verticalAlignment: Text.AlignVCenter
+ font.pointSize: 27
+ leftPadding: 10
+ }
+ }
+ }
+ }
+ }
+ function test_replaceCell_QTBUG_65121() {
+ var layout = createTemporaryObject(replaceCell_QTBUG_65121, container)
+ verify(layout)
+ layout.categories = ["eleven", "twelve"]
+ layout.values = [11, 12]
+ verify(isPolishScheduled(layout))
+ verify(waitForItemPolished(layout))
+ // Shouldn't be any warnings, but no way to verify this currently: QTBUG-70029
+ }
}
}
diff --git a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml
index 590249cf91..85fe54eca6 100644
--- a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml
+++ b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
@@ -69,6 +69,14 @@ Item {
}
Component {
+ id: rectangle_Component
+ Rectangle {
+ width: 100
+ height: 50
+ }
+ }
+
+ Component {
id: itemsWithAnchorsLayout_Component
RowLayout {
spacing: 2
@@ -193,8 +201,8 @@ Item {
} '
var tmp = Qt.createQmlObject(test_layoutStr, container, '');
- tryCompare(tmp, 'implicitWidth', 15);
- compare(tmp.implicitHeight, 20);
+ waitForRendering(tmp)
+ compare(tmp.implicitWidth, 15);
compare(tmp.height, 20);
tmp.width = 30
compare(tmp.r1.width, 10);
@@ -242,6 +250,46 @@ Item {
col.destroy()
}
+ Component {
+ id: propagateImplicitWidthToParent_Component
+ Item {
+ width: 200
+ height: 20
+
+ // These might trigger a updateLayoutItems() before its component is completed...
+ implicitWidth: row.implicitWidth
+ implicitHeight: row.implicitHeight
+ RowLayout {
+ id : row
+ anchors.fill: parent
+ property alias r1: _r1
+ property alias r2: _r2
+ spacing: 0
+ Rectangle {
+ id: _r1
+ color: "red"
+ implicitWidth: 50
+ implicitHeight: 20
+ }
+ Rectangle {
+ id: _r2
+ color: "green"
+ implicitWidth: 50
+ implicitHeight: 20
+ Layout.fillWidth: true
+ }
+ }
+ }
+ }
+
+ function test_propagateImplicitWidthToParent() {
+ var item = createTemporaryObject(propagateImplicitWidthToParent_Component, container)
+ var row = item.children[0]
+ compare(row.width, 200)
+ compare(itemRect(row.r1), [0, 0, 50, 20])
+ compare(itemRect(row.r2), [50, 0, 150, 20])
+ }
+
function test_implicitSize() {
var test_layoutStr =
'import QtQuick 2.2; \
@@ -272,6 +320,14 @@ Item {
var row = Qt.createQmlObject(test_layoutStr, container, '');
compare(row.implicitWidth, 50 + 10 + 40);
compare(row.implicitHeight, 6);
+ var r2 = row.children[2]
+ r2.implicitWidth = 20
+ verify(waitForRendering(row))
+ compare(row.implicitWidth, 50 + 10 + 20)
+ var r3 = rectangle_Component.createObject(container)
+ r3.implicitWidth = 30
+ r3.parent = row
+ compare(row.implicitWidth, 50 + 10 + 20 + 30)
row.destroy()
}
@@ -382,7 +438,7 @@ Item {
function test_addAndRemoveItems()
{
- var layout = layout_addAndRemoveItems_Component.createObject(container)
+ var layout = createTemporaryObject(layout_addAndRemoveItems_Component, container)
compare(layout.implicitWidth, 0)
compare(layout.implicitHeight, 0)
@@ -423,8 +479,6 @@ Item {
wait(0)
compare(layout.implicitWidth, 0)
compare(layout.implicitHeight, 0)
-
- layout.destroy()
}
Component {
@@ -543,6 +597,9 @@ Item {
Rectangle {
id: r1
color: "red"
+ implicitWidth: 1
+ implicitHeight: 1
+
Layout.minimumWidth: 1
Layout.preferredWidth: 2
Layout.maximumWidth: 3
@@ -622,13 +679,13 @@ Item {
child.Layout.minimumWidth = -1
compare(itemSizeHints(layout), [0, 2, 3])
child.Layout.preferredWidth = -1
- compare(itemSizeHints(layout), [0, 0, 3])
+ compare(itemSizeHints(layout), [0, 1, 3])
child.Layout.maximumWidth = -1
- compare(itemSizeHints(layout), [0, 0, Number.POSITIVE_INFINITY])
+ compare(itemSizeHints(layout), [0, 1, Number.POSITIVE_INFINITY])
layout.Layout.maximumWidth = 1000
- compare(itemSizeHints(layout), [0, 0, 1000])
+ compare(itemSizeHints(layout), [0, 1, 1000])
layout.Layout.maximumWidth = -1
- compare(itemSizeHints(layout), [0, 0, Number.POSITIVE_INFINITY])
+ compare(itemSizeHints(layout), [0, 1, Number.POSITIVE_INFINITY])
layout.implicitWidthChangedCount = 0
child.Layout.minimumWidth = 10
@@ -914,14 +971,6 @@ Item {
layout.destroy() // Do not crash
}
- Component {
- id: rectangle_Component
- Rectangle {
- width: 100
- height: 50
- }
- }
-
function test_destroyImplicitInvisibleLayout()
{
var root = rectangle_Component.createObject(container)
@@ -1179,10 +1228,6 @@ Item {
function test_rowlayoutWithTextItems() {
var layout = createTemporaryObject(rowlayoutWithTextItems_Component, container)
waitForRendering(layout)
- for (var i = 0; i < 3; i++) {
- ignoreWarning(/Qt Quick Layouts: Detected recursive rearrange. Aborting after two iterations./)
- }
- ignoreWarning(/Qt Quick Layouts: Polish loop detected. Aborting after two iterations./)
layout.width = layout.width - 2 // set the size to be smaller than its "minimum size"
waitForRendering(layout) // do not exit before all warnings have been received
diff --git a/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml b/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml
index 8234ac6ef7..3a41bdb3e0 100644
--- a/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml
+++ b/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.