From 8fbf85da86efc1607807304c11ef4cd604b2a533 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:52:54 +1000 Subject: [PATCH] fix: notif list viewport jumping around on delegate creation --- modules/sidebar/NotifDockList.qml | 2 ++ .../src/Caelestia/Components/lazylistview.cpp | 33 +++++++++++++++++++ .../src/Caelestia/Components/lazylistview.hpp | 7 ++++ 3 files changed, 42 insertions(+) diff --git a/modules/sidebar/NotifDockList.qml b/modules/sidebar/NotifDockList.qml index ef60c4f8..4e2ae6e8 100644 --- a/modules/sidebar/NotifDockList.qml +++ b/modules/sidebar/NotifDockList.qml @@ -22,6 +22,7 @@ LazyListView { cacheBuffer: 400 asynchronous: true + onViewportAdjustNeeded: d => container.contentY += d useCustomViewport: true viewport: Qt.rect(0, container.contentY, width, container.height) @@ -66,6 +67,7 @@ LazyListView { clearTimer.start(); } + LazyListView.trackViewport: notifInner.expanded || notifInner.nonAnimHeight < notifInner.implicitHeight LazyListView.preferredHeight: closed ? 0 : notifInner.nonAnimHeight LazyListView.visibleHeight: notifInner.implicitHeight implicitHeight: notifInner.implicitHeight diff --git a/plugin/src/Caelestia/Components/lazylistview.cpp b/plugin/src/Caelestia/Components/lazylistview.cpp index edcef17d..c35dbb6b 100644 --- a/plugin/src/Caelestia/Components/lazylistview.cpp +++ b/plugin/src/Caelestia/Components/lazylistview.cpp @@ -62,6 +62,17 @@ void LazyListViewAttached::setRemoving(bool removing) { emit removingChanged(); } +bool LazyListViewAttached::trackViewport() const { + return m_trackViewport; +} + +void LazyListViewAttached::setTrackViewport(bool track) { + if (m_trackViewport == track) + return; + m_trackViewport = track; + emit trackViewportChanged(); +} + // --- LazyListView --- LazyListView::LazyListView(QQuickItem* parent) @@ -628,11 +639,21 @@ void LazyListView::syncDelegates() { if (entry.item) { const qreal h = delegateHeight(entry.item); if (!m_layout[i].heightKnown || !qFuzzyCompare(m_layout[i].height + 1.0, h + 1.0)) { + const qreal oldLayoutH = m_layout[i].heightKnown ? m_layout[i].height : effectiveEstimatedHeight(); if (m_layout[i].heightKnown) untrackHeight(m_layout[i].height); m_layout[i].height = h; m_layout[i].heightKnown = true; trackHeight(h); + + // Compensate if tracked item materializes above viewport + auto* att = + qobject_cast(qmlAttachedPropertiesObject(entry.item, false)); + if (att && att->trackViewport()) { + const qreal vpTop = m_useCustomViewport ? m_viewport.y() : m_contentY; + if (m_layout[i].targetY < vpTop) + emit viewportAdjustNeeded(h - oldLayoutH); + } layoutChanged = true; } entry.item->setY(m_layout[i].targetY - m_contentY); @@ -745,6 +766,18 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { if (wasKnown) untrackHeight(oldH); trackHeight(h); + + // If this tracked item is above the viewport, emit a + // compensation delta so the consumer can adjust scroll. + if (wasKnown) { + auto* att = qobject_cast(qmlAttachedPropertiesObject(item, false)); + if (att && att->trackViewport()) { + const qreal vpTop = m_useCustomViewport ? m_viewport.y() : m_contentY; + if (m_layout[idx].targetY < vpTop) + emit viewportAdjustNeeded(h - oldH); + } + } + if (!m_relayoutPending) { m_relayoutPending = true; QTimer::singleShot(0, this, [this] { diff --git a/plugin/src/Caelestia/Components/lazylistview.hpp b/plugin/src/Caelestia/Components/lazylistview.hpp index f4f28c63..891960d3 100644 --- a/plugin/src/Caelestia/Components/lazylistview.hpp +++ b/plugin/src/Caelestia/Components/lazylistview.hpp @@ -21,6 +21,7 @@ class LazyListViewAttached : public QObject { Q_PROPERTY(qreal visibleHeight READ visibleHeight WRITE setVisibleHeight NOTIFY visibleHeightChanged) Q_PROPERTY(bool adding READ adding NOTIFY addingChanged) Q_PROPERTY(bool removing READ removing NOTIFY removingChanged) + Q_PROPERTY(bool trackViewport READ trackViewport WRITE setTrackViewport NOTIFY trackViewportChanged) public: explicit LazyListViewAttached(QObject* parent = nullptr); @@ -37,17 +38,22 @@ public: [[nodiscard]] bool removing() const; void setRemoving(bool removing); + [[nodiscard]] bool trackViewport() const; + void setTrackViewport(bool track); + signals: void preferredHeightChanged(); void visibleHeightChanged(); void addingChanged(); void removingChanged(); + void trackViewportChanged(); private: qreal m_preferredHeight = -1; qreal m_visibleHeight = -1; bool m_adding = false; bool m_removing = false; + bool m_trackViewport = false; }; class LazyListView : public QQuickItem { @@ -198,6 +204,7 @@ signals: void moveCurveChanged(); void countChanged(); void settledChanged(); + void viewportAdjustNeeded(qreal delta); protected: void componentComplete() override;