gui: fix infinite layout loop in chat ListView

The delegate used a ColumnLayout root with parent-dependent width
fallbacks (: 0, : 100) causing two-pass layouts. Combined with
onContentHeightChanged -> positionViewAtEnd(), this created a
feedback loop: height change -> scroll -> refill -> new delegates ->
height change -> ... burning 100% CPU on the main thread.

Fixes:
- Replace onContentHeightChanged with onCountChanged + Qt.callLater
- Replace ColumnLayout root with plain Column
- Bind all child widths to root.width (top-down, no fallbacks)
- Create context Menu lazily on first right-click
- Add reuseItems: true to the ListView
- Standardize bin path to ~/.local/bin
This commit is contained in:
Levi Neely 2026-08-09 00:03:37 +02:00
parent 51b2013ac3
commit 022980bc40
3 changed files with 51 additions and 30 deletions

View File

@ -3,7 +3,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ColumnLayout {
Column {
id: root
spacing: 2
@ -80,7 +80,7 @@ ColumnLayout {
// Clickable header
Rectangle {
Layout.fillWidth: true
width: root.width
height: 22
radius: 3
color: root.headerColor()
@ -115,7 +115,7 @@ ColumnLayout {
// Block content rendered as segments
Column {
Layout.fillWidth: true
width: root.width
visible: root.blockExpanded
spacing: 4
@ -123,7 +123,7 @@ ColumnLayout {
model: root.blockSegments || []
delegate: Loader {
width: parent ? parent.width : 0
width: root.width
active: modelData !== null && modelData !== undefined
sourceComponent: (modelData && modelData.type === "diagram") ? diagramComponent : textComponent
property var segmentData: modelData || {}
@ -137,7 +137,7 @@ ColumnLayout {
TextEdit {
id: textEdit
width: parent ? parent.width : 100
width: root.width
text: segmentData.html || ""
textFormat: Text.RichText
readOnly: true
@ -150,23 +150,11 @@ ColumnLayout {
selectedTextColor: theme.highlightedTextColor
onActiveFocusChanged: {
if (!activeFocus && root.parentListView)
root.parentListView.interactive = true
}
Menu {
id: contextMenu
MenuItem {
text: "Copy"
enabled: textEdit.selectedText.length > 0
onTriggered: textEdit.copy()
}
MenuItem {
text: "Select All"
onTriggered: textEdit.selectAll()
}
if (!activeFocus && root.parentListView)
root.parentListView.interactive = true
}
// Left-click: disable ListView flick while selecting text
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
@ -182,11 +170,41 @@ ColumnLayout {
}
}
// Right-click: lazy context menu
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
cursorShape: Qt.IBeamCursor
onClicked: contextMenu.popup()
onClicked: {
if (!textEdit._menu) {
textEdit._menu = contextMenuComponent.createObject(textEdit)
}
textEdit._menu.popup()
}
}
property Menu _menu: null
Component.onDestruction: {
if (_menu) _menu.destroy()
}
}
}
// Shared context menu component (created on demand)
Component {
id: contextMenuComponent
Menu {
id: ctxMenu
property TextEdit target: parent
MenuItem {
text: "Copy"
enabled: ctxMenu.target ? ctxMenu.target.selectedText.length > 0 : false
onTriggered: if (ctxMenu.target) ctxMenu.target.copy()
}
MenuItem {
text: "Select All"
onTriggered: if (ctxMenu.target) ctxMenu.target.selectAll()
}
}
}
@ -197,7 +215,7 @@ ColumnLayout {
Rectangle {
id: diagramRect
width: parent ? parent.width : 100
width: root.width
height: diagramContent.height + diagramHeader.height + 8
color: "transparent"
border.color: theme.midColor

View File

@ -883,18 +883,21 @@ ApplicationWindow {
visible: ollie.daemonConnected && ollie.activeAgentId !== ""
property bool followTail: true
property bool scrollingNow: false
onContentHeightChanged: {
if (followTail && !scrollingNow && contentHeight > 0) {
scrollingNow = true
positionViewAtEnd()
scrollingNow = false
}
reuseItems: true
onCountChanged: {
if (followTail)
Qt.callLater(chatView.scrollToEnd)
}
onMovementEnded: followTail = atYEnd
function scrollToEnd() {
if (followTail && contentHeight > height)
positionViewAtEnd()
}
delegate: ChatBlockDelegate {
width: chatView.width - 8
parentListView: chatView

View File

@ -1,6 +1,6 @@
# Ollie KDE integration build system
home := env_var('HOME')
bin := home / 'bin'
bin := home / '.local/bin'
kde:
@echo '=== Building ollie-kde (KF6) ==='