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:
parent
79656596e4
commit
d7a66f1827
50
gui/main.qml
50
gui/main.qml
|
|
@ -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"
|
||||
|
|
|
|||
Reference in New Issue