replace ListView with Flickable+Column+Repeater

ListView.positionViewAtEnd() causes freezes by forcing instantiation
of all delegates to measure total height. Flickable+Column avoids this
- contentHeight is computed directly from Column.implicitHeight
- Scroll to bottom is just contentY = contentHeight - height
- No virtualization needed with only 20 blocks
- Eliminates all positionViewAtEnd and _scrolling guard complexity
This commit is contained in:
Levi Neely 2026-07-16 06:51:37 +02:00
parent 79656596e4
commit d7a66f1827
1 changed files with 41 additions and 45 deletions

View File

@ -363,24 +363,16 @@ ApplicationWindow {
value: dbus.chatText || ""
}
ListView {
id: chatList
Flickable {
id: chatFlick
anchors.fill: parent
anchors.margins: 4
clip: true
spacing: 8
cacheBuffer: 200
model: chatModel
verticalLayoutDirection: ListView.TopToBottom
property bool _scrolling: false
Component.onCompleted: {
Qt.callLater(function() { chatList.positionViewAtEnd() })
}
contentWidth: width
contentHeight: chatColumn.implicitHeight
boundsBehavior: Flickable.StopAtBounds
onContentYChanged: {
if (_scrolling) return
let bottom = contentHeight - height
parent.atBottom = (bottom <= 0 || contentY >= bottom - 4)
@ -388,36 +380,38 @@ ApplicationWindow {
if (contentY < 100 && contentHeight > height) {
let oldHeight = contentHeight
if (chatModel.loadEarlier()) {
// Maintain scroll position after prepend
Qt.callLater(function() {
chatList.contentY += (chatList.contentHeight - oldHeight)
chatFlick.contentY += (chatFlick.contentHeight - oldHeight)
})
}
}
}
onContentHeightChanged: {
if (parent.atBottom && !_scrolling) {
_scrolling = true
Qt.callLater(function() {
chatList.positionViewAtEnd()
chatList._scrolling = false
})
if (parent.atBottom) {
contentY = Math.max(0, contentHeight - height)
}
}
// Placeholder when empty
Column {
id: chatColumn
width: chatFlick.width
spacing: 8
// Placeholder
Label {
anchors.centerIn: parent
visible: chatList.count === 0
visible: chatRepeater.count === 0
text: dbus.activeSessionId
? "(waiting for output...)"
: "Select or create a session"
color: palette.placeholderText
}
Repeater {
id: chatRepeater
model: chatModel
delegate: Loader {
width: chatList.width
height: item ? item.implicitHeight : 0
width: chatColumn.width
property var block: model
sourceComponent: {
switch (blockType) {
@ -433,6 +427,8 @@ ApplicationWindow {
}
}
}
}
}
ToolButton {
anchors.right: parent.right
@ -441,7 +437,7 @@ ApplicationWindow {
icon.name: "go-down"
visible: !parent.atBottom
onClicked: {
chatList.contentY = Math.max(0, chatList.contentHeight - chatList.height)
chatFlick.contentY = Math.max(0, chatFlick.contentHeight - chatFlick.height)
parent.atBottom = true
}
ToolTip.text: "Scroll to bottom"