perf(lazylistview): collect pending indices and hoist layoutSize cast in updatePolishdbus workaround for session

This commit is contained in:
2026-06-30 00:23:23 +02:00
parent b939224e9d
commit 3eecbc16bc
+268 -198
View File
@@ -1,6 +1,7 @@
#include "lazylistview.hpp" #include "lazylistview.hpp"
#include <algorithm> #include <algorithm>
#include <cmath>
#include <qqmlcontext.h> #include <qqmlcontext.h>
#include <qtimer.h> #include <qtimer.h>
@@ -13,11 +14,7 @@ constexpr int ASYNC_BATCH_DESTROY = 4;
namespace ZShell::components { namespace ZShell::components {
// --- LazyListViewAttached --- LazyListViewAttached::LazyListViewAttached(QObject* parent) : QObject(parent) {}
LazyListViewAttached::LazyListViewAttached(QObject* parent)
: QObject(parent) {
}
qreal LazyListViewAttached::preferredHeight() const { qreal LazyListViewAttached::preferredHeight() const {
return m_preferredHeight; return m_preferredHeight;
@@ -85,10 +82,7 @@ void LazyListViewAttached::setTrackViewport(bool track) {
emit trackViewportChanged(); emit trackViewportChanged();
} }
// --- LazyListView --- LazyListView::LazyListView(QQuickItem* parent) : QQuickItem(parent) {
LazyListView::LazyListView(QQuickItem* parent)
: QQuickItem(parent) {
setFlag(ItemHasContents, false); setFlag(ItemHasContents, false);
} }
@@ -103,8 +97,6 @@ LazyListView::~LazyListView() {
destroyDelegate(entry); destroyDelegate(entry);
} }
// --- Model & Delegate ---
QAbstractItemModel* LazyListView::model() const { QAbstractItemModel* LazyListView::model() const {
return m_model; return m_model;
} }
@@ -138,8 +130,6 @@ void LazyListView::setDelegate(QQmlComponent* delegate) {
emit delegateChanged(); emit delegateChanged();
} }
// --- Layout ---
qreal LazyListView::spacing() const { qreal LazyListView::spacing() const {
return m_spacing; return m_spacing;
} }
@@ -172,8 +162,6 @@ void LazyListView::setContentY(qreal contentY) {
polish(); polish();
} }
// --- Viewport ---
QRectF LazyListView::viewport() const { QRectF LazyListView::viewport() const {
return m_viewport; return m_viewport;
} }
@@ -211,8 +199,6 @@ void LazyListView::setCacheBuffer(qreal buffer) {
polish(); polish();
} }
// --- Sizing ---
qreal LazyListView::estimatedHeight() const { qreal LazyListView::estimatedHeight() const {
return m_estimatedHeight; return m_estimatedHeight;
} }
@@ -258,7 +244,8 @@ qreal LazyListView::delegateHeight(QQuickItem* item) {
if (!item) if (!item)
return 0; return 0;
auto* attached = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false)); auto* attached = qobject_cast<LazyListViewAttached*>(
qmlAttachedPropertiesObject<LazyListView>(item, false));
if (attached && attached->preferredHeight() >= 0) if (attached && attached->preferredHeight() >= 0)
return attached->preferredHeight(); return attached->preferredHeight();
@@ -269,7 +256,8 @@ qreal LazyListView::delegateVisibleHeight(QQuickItem* item) {
if (!item) if (!item)
return 0; return 0;
auto* attached = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false)); auto* attached = qobject_cast<LazyListViewAttached*>(
qmlAttachedPropertiesObject<LazyListView>(item, false));
if (attached) { if (attached) {
if (attached->visibleHeight() >= 0) if (attached->visibleHeight() >= 0)
return attached->visibleHeight(); return attached->visibleHeight();
@@ -283,12 +271,11 @@ qreal LazyListView::delegateVisibleHeight(QQuickItem* item) {
bool LazyListView::isDelegateReady(QQuickItem* item) { bool LazyListView::isDelegateReady(QQuickItem* item) {
if (!item) if (!item)
return false; return false;
auto* att = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false)); auto* att = qobject_cast<LazyListViewAttached*>(
qmlAttachedPropertiesObject<LazyListView>(item, false));
return !att || att->ready(); return !att || att->ready();
} }
// --- Animation Durations ---
int LazyListView::removeDuration() const { int LazyListView::removeDuration() const {
return m_removeDuration; return m_removeDuration;
} }
@@ -311,21 +298,18 @@ void LazyListView::setReadyDelay(int delay) {
emit readyDelayChanged(); emit readyDelayChanged();
} }
// --- State ---
int LazyListView::count() const { int LazyListView::count() const {
return m_model ? m_model->rowCount() : 0; return m_model ? m_model->rowCount() : 0;
} }
// --- QQuickItem Overrides ---
void LazyListView::componentComplete() { void LazyListView::componentComplete() {
QQuickItem::componentComplete(); QQuickItem::componentComplete();
m_componentComplete = true; m_componentComplete = true;
resetContent(); resetContent();
} }
void LazyListView::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) { void LazyListView::geometryChange(const QRectF& newGeometry,
const QRectF& oldGeometry) {
QQuickItem::geometryChange(newGeometry, oldGeometry); QQuickItem::geometryChange(newGeometry, oldGeometry);
if (!m_componentComplete) if (!m_componentComplete)
@@ -348,71 +332,84 @@ void LazyListView::updatePolish() {
// Flush pending inserts — make items visible and clear the adding flag // Flush pending inserts — make items visible and clear the adding flag
// so enter animations begin. When readyDelay > 0 the entire insert is // so enter animations begin. When readyDelay > 0 the entire insert is
// deferred so delegates have time to lay out before appearing. // deferred so delegates have time to lay out before appearing.
for (auto& entry : m_delegates) {
if (!entry.pendingInsert || !entry.item) // Collect pending indices first — avoids scanning the entire hash each frame
continue; QList<int> pendingIndices;
for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); ++it) {
if (it->pendingInsert && it->item)
pendingIndices.append(it.key());
}
for (int pendingIdx : pendingIndices) {
auto& entry = m_delegates[pendingIdx];
if (m_readyDelay > 0) { if (m_readyDelay > 0) {
if (!entry.readyDelayStarted) { if (!entry.readyDelayStarted) {
entry.readyDelayStarted = true; entry.readyDelayStarted = true;
auto* item = entry.item; auto* item = entry.item;
QTimer::singleShot(m_readyDelay, this, [this, item] { QTimer::singleShot(m_readyDelay, this, [this, item] {
auto indexIt = m_itemToIndex.find(item); auto indexIt = m_itemToIndex.find(item);
if (indexIt == m_itemToIndex.end()) if (indexIt == m_itemToIndex.end())
return; return;
const int idx = indexIt.value(); const int idx = indexIt.value();
auto it = m_delegates.find(idx); auto it = m_delegates.find(idx);
if (it == m_delegates.end() || it->item != item || !it->pendingInsert) if (it == m_delegates.end() || it->item != item ||
return; !it->pendingInsert)
return;
it->pendingInsert = false; it->pendingInsert = false;
it->readyDelayStarted = false; it->readyDelayStarted = false;
// Set initial y to visual position (based on current visible heights) // Set initial y to visual position (based on current visible heights)
if (idx >= 0 && idx < static_cast<int>(m_layout.size())) { if (idx >= 0 && idx < static_cast<int>(m_layout.size())) {
qreal visualY = 0; qreal visualY = 0;
bool hasVisItem = false; bool hasVisItem = false;
for (int i = 0; i < static_cast<int>(m_layout.size()); ++i) { for (int i = 0; i < static_cast<int>(m_layout.size());
qreal h; ++i) {
auto dit = m_delegates.find(i); qreal h = NAN;
if (dit != m_delegates.end() && dit->item) auto dit = m_delegates.find(i);
h = delegateVisibleHeight(dit->item); if (dit != m_delegates.end() && dit->item)
else h = delegateVisibleHeight(dit->item);
h = m_layout[i].heightKnown ? m_layout[i].height : effectiveEstimatedHeight(); else
if (h > 0) { h = m_layout[i].heightKnown
if (hasVisItem) ? m_layout[i].height
visualY += m_spacing; : effectiveEstimatedHeight();
hasVisItem = true; if (h > 0) {
} if (hasVisItem)
if (i == idx) visualY += m_spacing;
break; hasVisItem = true;
if (h > 0)
visualY += h;
} }
item->setY(visualY - m_contentY); if (i == idx)
break;
if (h > 0)
visualY += h;
} }
item->setY(visualY - m_contentY);
}
item->setVisible(true); item->setVisible(true);
auto* att = auto* att = qobject_cast<LazyListViewAttached*>(
qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false)); qmlAttachedPropertiesObject<LazyListView>(item, false));
if (att) { if (att) {
att->setAdding(false); att->setAdding(false);
att->setReady(true); att->setReady(true);
} }
// Animate from visual position to layout position // Animate from visual position to layout position
if (idx >= 0 && idx < static_cast<int>(m_layout.size())) if (idx >= 0 && idx < static_cast<int>(m_layout.size()))
item->setProperty("y", m_layout[idx].targetY - m_contentY); item->setProperty("y",
m_layout[idx].targetY - m_contentY);
polish(); polish();
}); });
} }
continue; continue;
} }
entry.pendingInsert = false; entry.pendingInsert = false;
entry.item->setVisible(true); entry.item->setVisible(true);
auto* att = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(entry.item, false)); auto* att = qobject_cast<LazyListViewAttached*>(
qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
if (att) { if (att) {
att->setAdding(false); att->setAdding(false);
att->setReady(true); att->setReady(true);
@@ -429,12 +426,13 @@ void LazyListView::updatePolish() {
record.isNew = false; record.isNew = false;
// Position delegates — QML Behavior on y handles the animation // Position delegates — QML Behavior on y handles the animation
const int layoutSize = static_cast<int>(m_layout.size());
for (auto& entry : m_delegates) { for (auto& entry : m_delegates) {
if (!entry.item || entry.pendingRemoval || entry.pendingInsert) if (!entry.item || entry.pendingRemoval || entry.pendingInsert)
continue; continue;
const int idx = entry.modelIndex; const int idx = entry.modelIndex;
if (idx < 0 || idx >= static_cast<int>(m_layout.size())) if (idx < 0 || idx >= layoutSize)
continue; continue;
if (m_layout[idx].heightKnown && qFuzzyIsNull(m_layout[idx].height)) if (m_layout[idx].heightKnown && qFuzzyIsNull(m_layout[idx].height))
@@ -454,7 +452,8 @@ void LazyListView::relayout() {
qreal y = 0; qreal y = 0;
bool hasLayoutItem = false; bool hasLayoutItem = false;
for (auto& record : m_layout) { for (auto& record : m_layout) {
const qreal layoutH = record.heightKnown ? record.height : effectiveEstimatedHeight(); const qreal layoutH = record.heightKnown ? record.height
: effectiveEstimatedHeight();
if (layoutH > 0) { if (layoutH > 0) {
if (hasLayoutItem) if (hasLayoutItem)
y += m_spacing; y += m_spacing;
@@ -476,12 +475,13 @@ void LazyListView::relayout() {
qreal visY = 0; qreal visY = 0;
bool hasVisItem = false; bool hasVisItem = false;
for (int i = 0; i < static_cast<int>(m_layout.size()); ++i) { for (int i = 0; i < static_cast<int>(m_layout.size()); ++i) {
qreal h; qreal h = NAN;
auto dit = m_delegates.find(i); auto dit = m_delegates.find(i);
if (dit != m_delegates.end() && dit->item) if (dit != m_delegates.end() && dit->item)
h = delegateVisibleHeight(dit->item); h = delegateVisibleHeight(dit->item);
else else
h = m_layout[i].heightKnown ? m_layout[i].height : effectiveEstimatedHeight(); h = m_layout[i].heightKnown ? m_layout[i].height
: effectiveEstimatedHeight();
if (h > 0) { if (h > 0) {
if (hasVisItem) if (hasVisItem)
visY += m_spacing; visY += m_spacing;
@@ -542,11 +542,11 @@ QRectF LazyListView::effectiveViewport() const {
std::pair<int, int> LazyListView::computeVisibleRange() const { std::pair<int, int> LazyListView::computeVisibleRange() const {
if (m_layout.isEmpty()) if (m_layout.isEmpty())
return { -1, -1 }; return {-1, -1};
const auto vp = effectiveViewport(); const auto vp = effectiveViewport();
if (vp.isEmpty()) if (vp.isEmpty())
return { -1, -1 }; return {-1, -1};
const qreal vpTop = vp.y(); const qreal vpTop = vp.y();
const qreal vpBottom = vp.y() + vp.height(); const qreal vpBottom = vp.y() + vp.height();
@@ -559,7 +559,9 @@ std::pair<int, int> LazyListView::computeVisibleRange() const {
while (lo <= hi) { while (lo <= hi) {
const int mid = lo + (hi - lo) / 2; const int mid = lo + (hi - lo) / 2;
const auto& record = m_layout[mid]; const auto& record = m_layout[mid];
const qreal itemBottom = record.targetY + (record.heightKnown ? record.height : effectiveEstimatedHeight()); const qreal itemBottom =
record.targetY +
(record.heightKnown ? record.height : effectiveEstimatedHeight());
if (itemBottom >= vpTop) { if (itemBottom >= vpTop) {
first = mid; first = mid;
@@ -570,7 +572,7 @@ std::pair<int, int> LazyListView::computeVisibleRange() const {
} }
if (first >= static_cast<int>(m_layout.size())) if (first >= static_cast<int>(m_layout.size()))
return { -1, -1 }; return {-1, -1};
// Linear scan for last visible item // Linear scan for last visible item
int last = first; int last = first;
@@ -580,7 +582,7 @@ std::pair<int, int> LazyListView::computeVisibleRange() const {
last = i; last = i;
} }
return { first, last }; return {first, last};
} }
// --- Delegate Lifecycle --- // --- Delegate Lifecycle ---
@@ -612,9 +614,12 @@ void LazyListView::syncDelegates() {
} }
// Batch destroy // Batch destroy
const int destroyBudget = m_asynchronous ? ASYNC_BATCH_DESTROY : static_cast<int>(toRemove.size()); const int destroyBudget = m_asynchronous
? ASYNC_BATCH_DESTROY
: static_cast<int>(toRemove.size());
QVector<DelegateEntry> removedEntries; QVector<DelegateEntry> removedEntries;
removedEntries.reserve(std::min(destroyBudget, static_cast<int>(toRemove.size()))); removedEntries.reserve(
std::min(destroyBudget, static_cast<int>(toRemove.size())));
int destroyed = 0; int destroyed = 0;
for (int idx : toRemove) { for (int idx : toRemove) {
if (destroyed >= destroyBudget) if (destroyed >= destroyBudget)
@@ -638,7 +643,8 @@ void LazyListView::syncDelegates() {
} }
// Batch create // Batch create
const int createBudget = m_asynchronous ? ASYNC_BATCH_CREATE : static_cast<int>(toCreate.size()); const int createBudget = m_asynchronous ? ASYNC_BATCH_CREATE
: static_cast<int>(toCreate.size());
int created = 0; int created = 0;
for (int i : toCreate) { for (int i : toCreate) {
if (created >= createBudget) if (created >= createBudget)
@@ -658,8 +664,9 @@ void LazyListView::syncDelegates() {
// Pending inserts need to become visible on the next frame, and // Pending inserts need to become visible on the next frame, and
// async mode may have remaining create/destroy work. // async mode may have remaining create/destroy work.
if (created > 0 || (m_asynchronous && (destroyed < static_cast<int>(toRemove.size()) || if (created > 0 ||
created < static_cast<int>(toCreate.size())))) (m_asynchronous && (destroyed < static_cast<int>(toRemove.size()) ||
created < static_cast<int>(toCreate.size()))))
polish(); polish();
} }
@@ -704,8 +711,10 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
initialProps.insert(QStringLiteral("index"), modelIndex); initialProps.insert(QStringLiteral("index"), modelIndex);
if (!hasModelData) { if (!hasModelData) {
const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key(); const auto role = roleNames.isEmpty() ? Qt::DisplayRole
initialProps.insert(QStringLiteral("modelData"), m_model->data(index, role)); : roleNames.constBegin().key();
initialProps.insert(QStringLiteral("modelData"),
m_model->data(index, role));
} }
m_delegate->setInitialProperties(entry.item, initialProps); m_delegate->setInitialProperties(entry.item, initialProps);
@@ -715,9 +724,10 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
// Only set adding = true for genuinely new model items (not viewport entries). // Only set adding = true for genuinely new model items (not viewport entries).
// Cleared on the next frame in updatePolish when the item becomes visible. // Cleared on the next frame in updatePolish when the item becomes visible.
if (modelIndex < static_cast<int>(m_layout.size()) && m_layout[modelIndex].isNew) { if (modelIndex < static_cast<int>(m_layout.size()) &&
auto* addingAttached = m_layout[modelIndex].isNew) {
qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(entry.item, true)); auto* addingAttached = qobject_cast<LazyListViewAttached*>(
qmlAttachedPropertiesObject<LazyListView>(entry.item, true));
if (addingAttached) if (addingAttached)
addingAttached->setAdding(true); addingAttached->setAdding(true);
} }
@@ -730,84 +740,103 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
// Height-change handler — uses m_itemToIndex for O(1) lookup. // Height-change handler — uses m_itemToIndex for O(1) lookup.
// Ignored while the delegate is not yet ready. // Ignored while the delegate is not yet ready.
auto onHeightChanged = [this, item = entry.item] { auto onHeightChanged = [this, item = entry.item] {
if (!isDelegateReady(item)) if (!isDelegateReady(item))
return; return;
auto indexIt = m_itemToIndex.find(item); auto indexIt = m_itemToIndex.find(item);
if (indexIt == m_itemToIndex.end()) if (indexIt == m_itemToIndex.end())
return; return;
const int idx = indexIt.value(); const int idx = indexIt.value();
auto delegateIt = m_delegates.find(idx); auto delegateIt = m_delegates.find(idx);
if (delegateIt == m_delegates.end() || delegateIt->item != item) if (delegateIt == m_delegates.end() || delegateIt->item != item)
return; return;
const qreal h = delegateHeight(item); const qreal h = delegateHeight(item);
if (idx < static_cast<int>(m_layout.size()) && !qFuzzyCompare(m_layout[idx].height + 1.0, h + 1.0)) { if (idx < static_cast<int>(m_layout.size()) &&
const qreal oldH = m_layout[idx].height; !qFuzzyCompare(m_layout[idx].height + 1.0, h + 1.0)) {
const bool wasKnown = m_layout[idx].heightKnown; const qreal oldH = m_layout[idx].height;
m_layout[idx].height = h; const bool wasKnown = m_layout[idx].heightKnown;
m_layout[idx].heightKnown = true; m_layout[idx].height = h;
if (wasKnown) m_layout[idx].heightKnown = true;
untrackHeight(oldH); if (wasKnown)
trackHeight(h); untrackHeight(oldH);
trackHeight(h);
// If this tracked item is above the viewport, emit a // If this tracked item is above the viewport, emit a
// compensation delta so the consumer can adjust scroll. // compensation delta so the consumer can adjust scroll.
if (wasKnown) { if (wasKnown) {
auto* att = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false)); auto* att = qobject_cast<LazyListViewAttached*>(
if (att && att->trackViewport()) { qmlAttachedPropertiesObject<LazyListView>(item, false));
const qreal vpTop = m_useCustomViewport ? m_viewport.y() : m_contentY; if (att && att->trackViewport()) {
if (m_layout[idx].targetY < vpTop) const qreal vpTop = m_useCustomViewport ? m_viewport.y()
emit viewportAdjustNeeded(h - oldH); : m_contentY;
} if (m_layout[idx].targetY < vpTop)
} emit viewportAdjustNeeded(h - oldH);
}
}
if (!m_relayoutPending) { if (!m_relayoutPending) {
m_relayoutPending = true; m_relayoutPending = true;
QTimer::singleShot(0, this, [this] { QTimer::singleShot(0, this, [this] {
m_relayoutPending = false; m_relayoutPending = false;
relayout(); relayout();
polish(); polish();
}); });
} }
} }
}; };
// Watch implicitHeight as fallback // Watch implicitHeight as fallback
connect(entry.item, &QQuickItem::implicitHeightChanged, this, onHeightChanged); connect(entry.item,
&QQuickItem::implicitHeightChanged,
this,
onHeightChanged);
// Watch attached properties if the delegate uses them // Watch attached properties if the delegate uses them
auto* attached = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(entry.item, false)); auto* attached = qobject_cast<LazyListViewAttached*>(
qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
if (attached) { if (attached) {
connect(attached, &LazyListViewAttached::preferredHeightChanged, this, onHeightChanged); connect(attached,
connect(attached, &LazyListViewAttached::visibleHeightChanged, this, [this] { &LazyListViewAttached::preferredHeightChanged,
polish(); this,
}); onHeightChanged);
connect(attached, &LazyListViewAttached::readyChanged, this, [this, item = entry.item] { connect(attached,
auto indexIt = m_itemToIndex.find(item); &LazyListViewAttached::visibleHeightChanged,
if (indexIt == m_itemToIndex.end()) this,
return; [this] { polish(); });
const int idx = indexIt.value(); connect(attached,
if (idx >= static_cast<int>(m_layout.size())) &LazyListViewAttached::readyChanged,
return; this,
auto* att = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false)); [this, item = entry.item] {
if (!att || !att->ready()) auto indexIt = m_itemToIndex.find(item);
return; if (indexIt == m_itemToIndex.end())
return;
const int idx = indexIt.value();
if (idx >= static_cast<int>(m_layout.size()))
return;
auto* att = qobject_cast<LazyListViewAttached*>(
qmlAttachedPropertiesObject<LazyListView>(item, false));
if (!att || !att->ready())
return;
const qreal h = delegateHeight(item); const qreal h = delegateHeight(item);
const qreal oldLayoutH = m_layout[idx].heightKnown ? m_layout[idx].height : effectiveEstimatedHeight(); const qreal oldLayoutH = m_layout[idx].heightKnown
if (m_layout[idx].heightKnown) ? m_layout[idx].height
untrackHeight(m_layout[idx].height); : effectiveEstimatedHeight();
m_layout[idx].height = h; if (m_layout[idx].heightKnown)
m_layout[idx].heightKnown = true; untrackHeight(m_layout[idx].height);
trackHeight(h); m_layout[idx].height = h;
m_layout[idx].heightKnown = true;
trackHeight(h);
if (att->trackViewport() && !qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) { if (att->trackViewport() &&
const qreal vpTop = m_useCustomViewport ? m_viewport.y() : m_contentY; !qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) {
if (m_layout[idx].targetY < vpTop) const qreal vpTop = m_useCustomViewport ? m_viewport.y()
emit viewportAdjustNeeded(h - oldLayoutH); : m_contentY;
} if (m_layout[idx].targetY < vpTop)
emit viewportAdjustNeeded(h - oldLayoutH);
}
polish(); polish();
}); });
} }
return entry; return entry;
@@ -832,7 +861,8 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) {
for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) { for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) {
const auto name = QString::fromUtf8(it.value()); const auto name = QString::fromUtf8(it.value());
entry.item->setProperty(name.toUtf8().constData(), m_model->data(index, it.key())); entry.item->setProperty(name.toUtf8().constData(),
m_model->data(index, it.key()));
if (name == QStringLiteral("modelData")) if (name == QStringLiteral("modelData"))
hasModelData = true; hasModelData = true;
} }
@@ -840,7 +870,8 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) {
entry.item->setProperty("index", entry.modelIndex); entry.item->setProperty("index", entry.modelIndex);
if (!hasModelData) { if (!hasModelData) {
const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key(); const auto role = roleNames.isEmpty() ? Qt::DisplayRole
: roleNames.constBegin().key();
entry.item->setProperty("modelData", m_model->data(index, role)); entry.item->setProperty("modelData", m_model->data(index, role));
} }
} }
@@ -852,24 +883,46 @@ void LazyListView::connectModel() {
return; return;
m_modelConnections = { m_modelConnections = {
connect(m_model, &QAbstractItemModel::rowsInserted, this, &LazyListView::onRowsInserted), connect(m_model,
connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &LazyListView::onRowsAboutToBeRemoved), &QAbstractItemModel::rowsInserted,
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LazyListView::onRowsRemoved), this,
connect(m_model, &QAbstractItemModel::rowsMoved, this, &LazyListView::onRowsMoved), &LazyListView::onRowsInserted),
connect(m_model, &QAbstractItemModel::dataChanged, this, &LazyListView::onDataChanged), connect(m_model,
connect(m_model, &QAbstractItemModel::modelReset, this, &LazyListView::onModelReset), &QAbstractItemModel::rowsAboutToBeRemoved,
connect(m_model, &QAbstractItemModel::layoutChanged, this, this,
[this] { &LazyListView::onRowsAboutToBeRemoved),
for (auto& entry : m_delegates) connect(m_model,
updateDelegateData(entry); &QAbstractItemModel::rowsRemoved,
polish(); this,
}), &LazyListView::onRowsRemoved),
connect(m_model, &QObject::destroyed, this, connect(m_model,
[this] { &QAbstractItemModel::rowsMoved,
m_model = nullptr; this,
resetContent(); &LazyListView::onRowsMoved),
emit modelChanged(); connect(m_model,
}), &QAbstractItemModel::dataChanged,
this,
&LazyListView::onDataChanged),
connect(m_model,
&QAbstractItemModel::modelReset,
this,
&LazyListView::onModelReset),
connect(m_model,
&QAbstractItemModel::layoutChanged,
this,
[this] {
for (auto& entry : m_delegates)
updateDelegateData(entry);
polish();
}),
connect(m_model,
&QObject::destroyed,
this,
[this] {
m_model = nullptr;
resetContent();
emit modelChanged();
}),
}; };
} }
@@ -909,13 +962,15 @@ void LazyListView::resetContent() {
polish(); polish();
} }
void LazyListView::onRowsInserted(const QModelIndex& parent, int first, int last) { void LazyListView::onRowsInserted(const QModelIndex& parent,
int first,
int last) {
if (parent.isValid()) if (parent.isValid())
return; return;
const int insertCount = last - first + 1; const int insertCount = last - first + 1;
// Insert new layout records // Insert new layout records
m_layout.insert(first, insertCount, ItemRecord{ 0, 0, false, true }); m_layout.insert(first, insertCount, ItemRecord{0, 0, false, true});
// Shift existing delegate indices // Shift existing delegate indices
QHash<int, DelegateEntry> shifted; QHash<int, DelegateEntry> shifted;
@@ -935,7 +990,9 @@ void LazyListView::onRowsInserted(const QModelIndex& parent, int first, int last
polish(); polish();
} }
void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) { void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent,
int first,
int last) {
if (parent.isValid()) if (parent.isValid())
return; return;
@@ -955,22 +1012,24 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, int first,
} }
if (m_removeDuration > 0 && entry.item) { if (m_removeDuration > 0 && entry.item) {
auto* attached = auto* attached = qobject_cast<LazyListViewAttached*>(
qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(entry.item, false)); qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
if (attached) if (attached)
attached->setRemoving(true); attached->setRemoving(true);
// Schedule destruction after the remove animation duration // Schedule destruction after the remove animation duration
auto* item = entry.item; auto* item = entry.item;
QTimer::singleShot(m_removeDuration, this, [this, item] { QTimer::singleShot(m_removeDuration, this, [this, item] {
for (auto it = m_dyingDelegates.begin(); it != m_dyingDelegates.end(); ++it) { for (auto it = m_dyingDelegates.begin();
if (it->item == item) { it != m_dyingDelegates.end();
destroyDelegate(*it); ++it) {
m_dyingDelegates.erase(it); if (it->item == item) {
return; destroyDelegate(*it);
} m_dyingDelegates.erase(it);
return;
} }
}); }
});
m_dyingDelegates.append(std::move(entry)); m_dyingDelegates.append(std::move(entry));
} else { } else {
destroyDelegate(entry); destroyDelegate(entry);
@@ -978,7 +1037,9 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, int first,
} }
} }
void LazyListView::onRowsRemoved(const QModelIndex& parent, int first, int last) { void LazyListView::onRowsRemoved(const QModelIndex& parent,
int first,
int last) {
if (parent.isValid()) if (parent.isValid())
return; return;
@@ -1011,7 +1072,11 @@ void LazyListView::onRowsRemoved(const QModelIndex& parent, int first, int last)
polish(); polish();
} }
void LazyListView::onRowsMoved(const QModelIndex& parent, int start, int end, const QModelIndex& destination, int row) { void LazyListView::onRowsMoved(const QModelIndex& parent,
int start,
int end,
const QModelIndex& destination,
int row) {
if (parent.isValid() || destination.isValid()) if (parent.isValid() || destination.isValid())
return; return;
@@ -1055,7 +1120,9 @@ void LazyListView::onRowsMoved(const QModelIndex& parent, int start, int end, co
polish(); polish();
} }
void LazyListView::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList<int>& roles) { void LazyListView::onDataChanged(const QModelIndex& topLeft,
const QModelIndex& bottomRight,
const QList<int>& roles) {
Q_UNUSED(roles) Q_UNUSED(roles)
if (topLeft.parent().isValid()) if (topLeft.parent().isValid())
@@ -1079,15 +1146,18 @@ void LazyListView::onModelReset() {
// Check if the model data actually changed // Check if the model data actually changed
if (newRows == oldRows) { if (newRows == oldRows) {
const auto roleNames = m_model->roleNames(); const auto roleNames = m_model->roleNames();
const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key(); const auto role = roleNames.isEmpty() ? Qt::DisplayRole
: roleNames.constBegin().key();
bool changed = false; bool changed = false;
for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); ++it) { for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd();
++it) {
if (!it->item || it.key() >= newRows) { if (!it->item || it.key() >= newRows) {
changed = true; changed = true;
break; break;
} }
const auto newData = m_model->data(m_model->index(it.key(), 0), role); const auto newData =
m_model->data(m_model->index(it.key(), 0), role);
const auto oldData = it->item->property("modelData"); const auto oldData = it->item->property("modelData");
if (newData != oldData) { if (newData != oldData) {
changed = true; changed = true;