From 8b38ecc29b251605c23e1eb95ecdb8d8636bb955 Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:54:21 +0200 Subject: [PATCH] feat: Add Chat preview scroll bar --- ChatView/CMakeLists.txt | 1 + ChatView/qml/RootItem.qml | 48 +++++++++ ChatView/qml/parts/ChatPreviewBar.qml | 135 ++++++++++++++++++++++++++ ChatView/qml/parts/TopBar.qml | 8 ++ 4 files changed, 192 insertions(+) create mode 100644 ChatView/qml/parts/ChatPreviewBar.qml diff --git a/ChatView/CMakeLists.txt b/ChatView/CMakeLists.txt index fbe8bb6..97e7f8b 100644 --- a/ChatView/CMakeLists.txt +++ b/ChatView/CMakeLists.txt @@ -17,6 +17,7 @@ qt_add_qml_module(QodeAssistChatView qml/parts/TopBar.qml qml/parts/BottomBar.qml qml/parts/AttachedFilesPlace.qml + qml/parts/ChatPreviewBar.qml RESOURCES icons/attach-file-light.svg icons/attach-file-dark.svg diff --git a/ChatView/qml/RootItem.qml b/ChatView/qml/RootItem.qml index 6b6f139..6f4fe2e 100644 --- a/ChatView/qml/RootItem.qml +++ b/ChatView/qml/RootItem.qml @@ -76,6 +76,10 @@ ChatRootView { text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved") } openChatHistory.onClicked: root.openChatHistoryFolder() + expandScrollbar { + text: scroll.isPreviewMode ? "»" : "«" + onClicked: scroll.isPreviewMode = !scroll.isPreviewMode + } } ListView { @@ -114,6 +118,50 @@ ChatRootView { ScrollBar.vertical: QQC.ScrollBar { id: scroll + + property bool isPreviewMode: false + readonly property int previewWidth: 30 + + implicitWidth: isPreviewMode ? scroll.previewWidth : 16 + + contentItem: Rectangle { + implicitWidth: scroll.isPreviewMode ? scroll.previewWidth : 6 + implicitHeight: 100 + radius: 3 + color: scroll.pressed ? palette.dark : + scroll.hovered ? palette.mid : + palette.button + + Behavior on implicitWidth { + NumberAnimation { duration: 150 } + } + } + + background: Rectangle { + color: scroll.isPreviewMode ? "transparent" : + palette.window.hslLightness > 0.5 ? + Qt.darker(palette.window, 1.1) : + Qt.lighter(palette.window, 1.1) + radius: 3 + } + + ChatPreviewBar { + anchors.fill: parent + targetView: chatListView + visible: parent.isPreviewMode + opacity: parent.isPreviewMode ? 1 : 0 + + Behavior on opacity { + NumberAnimation { duration: 150 } + } + } + + Behavior on implicitWidth { + NumberAnimation { + duration: 150 + easing.type: Easing.InOutQuad + } + } } onCountChanged: { diff --git a/ChatView/qml/parts/ChatPreviewBar.qml b/ChatView/qml/parts/ChatPreviewBar.qml new file mode 100644 index 0000000..7dcee4f --- /dev/null +++ b/ChatView/qml/parts/ChatPreviewBar.qml @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2025 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +import QtQuick +import QtQuick.Controls + +Rectangle { + id: root + + property ListView targetView: null + property int previewWidth: 50 + property color userMessageColor: "#92BD6C" + property color assistantMessageColor: palette.button + + width: previewWidth + color: palette.window.hslLightness > 0.5 ? + Qt.darker(palette.window, 1.1) : + Qt.lighter(palette.window, 1.1) + + Behavior on opacity { + NumberAnimation { + duration: 150 + easing.type: Easing.InOutQuad + } + } + + Column { + id: previewContainer + anchors.fill: parent + anchors.margins: 2 + spacing: 2 + + Repeater { + model: targetView ? targetView.model : null + + Rectangle { + required property int index + required property var model + + width: parent.width + height: { + if (!targetView || !targetView.count) return 0 + const availableHeight = root.height - ((targetView.count - 1) * previewContainer.spacing) + return availableHeight / targetView.count + } + + radius: 4 + color: model.roleType === ChatModel.User ? + userMessageColor : + assistantMessageColor + + opacity: root.opacity + transform: Translate { + x: root.opacity * 50 - 50 + } + + Behavior on transform { + NumberAnimation { + duration: 150 + easing.type: Easing.InOutQuad + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (targetView) { + targetView.positionViewAtIndex(index, ListView.Center) + } + } + + HoverHandler { + id: hover + } + } + + Rectangle { + anchors.fill: parent + color: palette.highlight + opacity: hover.hovered ? 0.2 : 0 + radius: parent.radius + + Behavior on opacity { + NumberAnimation { duration: 150 } + } + } + + Rectangle { + anchors.fill: parent + color: palette.highlight + opacity: { + if (!targetView) return 0 + const viewY = targetView.contentY + const viewHeight = targetView.height + const totalHeight = targetView.contentHeight + const itemPosition = index / targetView.count * totalHeight + const itemHeight = totalHeight / targetView.count + + return (itemPosition + itemHeight > viewY && + itemPosition < viewY + viewHeight) ? 0.2 : 0 + } + radius: parent.radius + + Behavior on opacity { + NumberAnimation { duration: 150 } + } + } + + ToolTip.visible: hover.hovered + ToolTip.text: { + const maxPreviewLength = 100 + return model.content.length > maxPreviewLength ? + model.content.substring(0, maxPreviewLength) + "..." : + model.content + } + } + } + } +} diff --git a/ChatView/qml/parts/TopBar.qml b/ChatView/qml/parts/TopBar.qml index d57aee5..59fcfe3 100644 --- a/ChatView/qml/parts/TopBar.qml +++ b/ChatView/qml/parts/TopBar.qml @@ -30,6 +30,7 @@ Rectangle { property alias tokensBadge: tokensBadgeId property alias recentPath: recentPathId property alias openChatHistory: openChatHistoryId + property alias expandScrollbar: expandScrollbarId color: palette.window.hslLightness > 0.5 ? Qt.darker(palette.window, 1.1) : @@ -84,5 +85,12 @@ Rectangle { Badge { id: tokensBadgeId } + + QoAButton { + id: expandScrollbarId + + width: 16 + height: 16 + } } }