feat: listview async delegate creation and destruction

This commit is contained in:
2 * r + 2 * t 2026-04-03 18:33:30 +11:00
parent ea776c20e0
commit 700ad61969
3 changed files with 81 additions and 26 deletions

View file

@ -33,6 +33,7 @@ LazyListView {
implicitHeight: contentHeight implicitHeight: contentHeight
spacing: Math.round(Appearance.spacing.small / 2) spacing: Math.round(Appearance.spacing.small / 2)
asynchronous: true
removeDuration: Appearance.anim.durations.normal removeDuration: Appearance.anim.durations.normal

View file

@ -4,6 +4,13 @@
#include <qpropertyanimation.h> #include <qpropertyanimation.h>
#include <qtimer.h> #include <qtimer.h>
namespace {
constexpr int ASYNC_BATCH_CREATE = 2;
constexpr int ASYNC_BATCH_DESTROY = 4;
} // namespace
namespace caelestia::components { namespace caelestia::components {
// --- LazyListViewAttached --- // --- LazyListViewAttached ---
@ -195,6 +202,17 @@ void LazyListView::setEstimatedHeight(qreal height) {
polish(); polish();
} }
bool LazyListView::asynchronous() const {
return m_asynchronous;
}
void LazyListView::setAsynchronous(bool async) {
if (m_asynchronous == async)
return;
m_asynchronous = async;
emit asynchronousChanged();
}
qreal LazyListView::effectiveEstimatedHeight() const { qreal LazyListView::effectiveEstimatedHeight() const {
if (m_estimatedHeight >= 0) if (m_estimatedHeight >= 0)
return m_estimatedHeight; return m_estimatedHeight;
@ -531,27 +549,46 @@ void LazyListView::syncDelegates() {
visibleIndices.insert(i); visibleIndices.insert(i);
} }
// Destroy delegates outside visible range (if not animating) // Collect delegates to destroy (outside visible range and not animating)
QList<int> toRemove; QList<int> toRemove;
for (auto it = m_delegates.begin(); it != m_delegates.end(); ++it) { for (auto it = m_delegates.begin(); it != m_delegates.end(); ++it) {
if (!visibleIndices.contains(it.key()) && !it->animation) { if (!visibleIndices.contains(it.key()) && !it->animation)
toRemove.append(it.key()); toRemove.append(it.key());
} }
}
for (int idx : toRemove) {
auto entry = m_delegates.take(idx);
destroyDelegate(entry);
}
// Create delegates for newly visible indices // Batch destroy
const int destroyBudget = m_asynchronous ? ASYNC_BATCH_DESTROY : static_cast<int>(toRemove.size());
QVector<DelegateEntry> removedEntries;
removedEntries.reserve(std::min(destroyBudget, static_cast<int>(toRemove.size())));
int destroyed = 0;
for (int idx : toRemove) {
if (destroyed >= destroyBudget)
break;
removedEntries.append(m_delegates.take(idx));
++destroyed;
}
for (auto& entry : removedEntries)
destroyDelegate(entry);
// Collect indices to create
QList<int> toCreate;
if (first >= 0) { if (first >= 0) {
for (int i = first; i <= last; ++i) { for (int i = first; i <= last; ++i) {
if (m_delegates.contains(i)) if (!m_delegates.contains(i))
continue; toCreate.append(i);
}
}
// Batch create
const int createBudget = m_asynchronous ? ASYNC_BATCH_CREATE : static_cast<int>(toCreate.size());
int created = 0;
bool layoutChanged = false;
for (int i : toCreate) {
if (created >= createBudget)
break;
auto entry = createDelegate(i); auto entry = createDelegate(i);
if (entry.item) { if (entry.item) {
// Measure height (prefer attached preferredHeight, fall back to implicitHeight)
const qreal h = delegateHeight(entry.item); const qreal h = delegateHeight(entry.item);
if (!m_layout[i].heightKnown || !qFuzzyCompare(m_layout[i].height, h)) { if (!m_layout[i].heightKnown || !qFuzzyCompare(m_layout[i].height, h)) {
if (m_layout[i].heightKnown) if (m_layout[i].heightKnown)
@ -559,13 +596,21 @@ void LazyListView::syncDelegates() {
m_layout[i].height = h; m_layout[i].height = h;
m_layout[i].heightKnown = true; m_layout[i].heightKnown = true;
trackHeight(h); trackHeight(h);
layoutChanged = true;
} }
// Position immediately so it doesn't flash at y=0
entry.item->setY(m_layout[i].targetY - m_contentY); entry.item->setY(m_layout[i].targetY - m_contentY);
m_delegates.insert(i, std::move(entry)); m_delegates.insert(i, std::move(entry));
++created;
} }
} }
}
if (layoutChanged)
relayout();
// If async and there's remaining work, schedule another pass
if (m_asynchronous &&
(destroyed < static_cast<int>(toRemove.size()) || created < static_cast<int>(toCreate.size())))
polish();
} }
LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {

View file

@ -73,6 +73,9 @@ class LazyListView : public QQuickItem {
// Sizing // Sizing
Q_PROPERTY(qreal estimatedHeight READ estimatedHeight WRITE setEstimatedHeight NOTIFY estimatedHeightChanged) Q_PROPERTY(qreal estimatedHeight READ estimatedHeight WRITE setEstimatedHeight NOTIFY estimatedHeightChanged)
// Async
Q_PROPERTY(bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY asynchronousChanged)
// Add Animation // Add Animation
Q_PROPERTY(int addDuration READ addDuration WRITE setAddDuration NOTIFY addDurationChanged) Q_PROPERTY(int addDuration READ addDuration WRITE setAddDuration NOTIFY addDurationChanged)
Q_PROPERTY(QEasingCurve addCurve READ addCurve WRITE setAddCurve NOTIFY addCurveChanged) Q_PROPERTY(QEasingCurve addCurve READ addCurve WRITE setAddCurve NOTIFY addCurveChanged)
@ -130,6 +133,10 @@ public:
[[nodiscard]] qreal estimatedHeight() const; [[nodiscard]] qreal estimatedHeight() const;
void setEstimatedHeight(qreal height); void setEstimatedHeight(qreal height);
// Async
[[nodiscard]] bool asynchronous() const;
void setAsynchronous(bool async);
// Add Animation // Add Animation
[[nodiscard]] int addDuration() const; [[nodiscard]] int addDuration() const;
void setAddDuration(int duration); void setAddDuration(int duration);
@ -178,6 +185,7 @@ signals:
void useCustomViewportChanged(); void useCustomViewportChanged();
void cacheBufferChanged(); void cacheBufferChanged();
void estimatedHeightChanged(); void estimatedHeightChanged();
void asynchronousChanged();
void addDurationChanged(); void addDurationChanged();
void addCurveChanged(); void addCurveChanged();
void addFromOpacityChanged(); void addFromOpacityChanged();
@ -261,6 +269,7 @@ private:
qreal m_estimatedHeight = -1; qreal m_estimatedHeight = -1;
qreal m_knownHeightSum = 0; qreal m_knownHeightSum = 0;
int m_knownHeightCount = 0; int m_knownHeightCount = 0;
bool m_asynchronous = false;
int m_addDuration = 300; int m_addDuration = 300;
QEasingCurve m_addCurve; QEasingCurve m_addCurve;