diff --git a/ChatView/qml/chatparts/ChatItem.qml b/ChatView/qml/chatparts/ChatItem.qml index 7a3cdfc..83a99a3 100644 --- a/ChatView/qml/chatparts/ChatItem.qml +++ b/ChatView/qml/chatparts/ChatItem.qml @@ -356,10 +356,9 @@ Rectangle { smooth: true mipmap: true - BusyIndicator { + QoABusyIndicator { anchors.centerIn: parent running: imageDisplay.status === Image.Loading - visible: running } Text { diff --git a/ChatView/qml/controls/BottomBar.qml b/ChatView/qml/controls/BottomBar.qml index 984470e..e831054 100644 --- a/ChatView/qml/controls/BottomBar.qml +++ b/ChatView/qml/controls/BottomBar.qml @@ -109,7 +109,7 @@ Rectangle { visible: root.isCompressing spacing: 6 - BusyIndicator { + QoABusyIndicator { id: compressBusyIndicator anchors.verticalCenter: parent.verticalCenter @@ -168,7 +168,7 @@ Rectangle { width: 15 } - BusyIndicator { + QoABusyIndicator { id: sendBusyIndicator anchors.left: parent.left @@ -177,7 +177,6 @@ Rectangle { width: 14 height: 14 running: root.isProcessing - visible: root.isProcessing } QoAToolTip { diff --git a/UIControls/CMakeLists.txt b/UIControls/CMakeLists.txt index 4864a3c..2aede1b 100644 --- a/UIControls/CMakeLists.txt +++ b/UIControls/CMakeLists.txt @@ -10,6 +10,7 @@ qt_add_qml_module(QodeAssistUIControls QML_FILES qml/Badge.qml qml/QoAButton.qml + qml/QoABusyIndicator.qml qml/QoABusyOverlay.qml qml/QoATextSlider.qml qml/QoAComboBox.qml diff --git a/UIControls/qml/QoABusyIndicator.qml b/UIControls/qml/QoABusyIndicator.qml new file mode 100644 index 0000000..608d73d --- /dev/null +++ b/UIControls/qml/QoABusyIndicator.qml @@ -0,0 +1,76 @@ +// Copyright (C) 2024-2026 Petr Mironychev +// SPDX-License-Identifier: GPL-3.0-or-later +// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE + +import QtQuick + +Item { + id: root + + property bool running: true + property color color: palette.highlightedText + property real lineWidth: Math.max(1.5, Math.min(width, height) * 0.13) + + implicitWidth: 24 + implicitHeight: 24 + + visible: opacity > 0 + opacity: running ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { duration: 150 } + } + + Canvas { + id: canvas + + anchors.fill: parent + antialiasing: true + + onPaint: { + const ctx = getContext("2d") + ctx.reset() + + const cx = width / 2 + const cy = height / 2 + const lw = root.lineWidth + const radius = Math.min(width, height) / 2 - lw / 2 - 1 + if (radius <= 0) + return + + const segments = 90 + const sweep = Math.PI * 1.5 + + ctx.lineCap = "butt" + ctx.lineWidth = lw + for (let i = 0; i < segments; ++i) { + const a0 = (i / segments) * sweep + const a1 = ((i + 1) / segments) * sweep + 0.012 + const t = i / (segments - 1) + ctx.beginPath() + ctx.strokeStyle = Qt.rgba(root.color.r, root.color.g, root.color.b, 0.08 + 0.92 * t) + ctx.arc(cx, cy, radius, a0, a1, false) + ctx.stroke() + } + + ctx.lineCap = "round" + ctx.strokeStyle = root.color + ctx.beginPath() + ctx.arc(cx, cy, radius, sweep - 0.001, sweep, false) + ctx.stroke() + } + + RotationAnimator { + target: canvas + from: 0 + to: 360 + duration: 850 + loops: Animation.Infinite + running: root.visible + } + } + + onColorChanged: canvas.requestPaint() + onLineWidthChanged: canvas.requestPaint() + onWidthChanged: canvas.requestPaint() + onHeightChanged: canvas.requestPaint() +} diff --git a/UIControls/qml/QoABusyOverlay.qml b/UIControls/qml/QoABusyOverlay.qml index abd67a9..6140c2e 100644 --- a/UIControls/qml/QoABusyOverlay.qml +++ b/UIControls/qml/QoABusyOverlay.qml @@ -3,7 +3,6 @@ // Additional attribution terms under GPLv3 §7(b) apply — see LICENSE import QtQuick -import QtQuick.Controls Rectangle { id: root @@ -26,11 +25,11 @@ Rectangle { anchors.centerIn: parent spacing: 10 - BusyIndicator { + QoABusyIndicator { anchors.horizontalCenter: parent.horizontalCenter running: root.active - implicitWidth: 36 - implicitHeight: 36 + width: 36 + height: 36 } Text {