From 60a99b1db438fe486d0bcaeafb528888d36a2104 Mon Sep 17 00:00:00 2001 From: flathead Date: Fri, 27 Mar 2026 23:27:29 +0300 Subject: [PATCH 01/13] feat(bar): add resource monitor widget with configurable placement Adds a compact system resource monitor that can be displayed either in the dashboard (existing behaviour) or in the bar. When placed in the bar, the widget shows CPU/RAM/GPU/Disk usage inline with temperature annotations; clicking it opens a detail popup via BarPopup. New settings under System > System Resources: - Enable/disable resource monitor globally - Display Location: Dashboard (default) | Bar - Bar Position: Left (after workspaces) | Right (before presets) - Visible Items: toggle CPU, RAM, GPU, Disk independently - Save button in titlebar; settings buffered until saved - Status text shows "Unsaved changes" / "Saved" feedback Config stored in system.json under resources.{enabled,location,barSide,show}. SystemResources monitor process now also runs when bar mode is active. I18n keys used via tr() helper with English fallbacks for upstream compat. Co-Authored-By: Claude Sonnet 4.6 --- config/Config.qml | 11 + config/defaults/system.js | 11 + modules/bar/BarContent.qml | 32 ++ modules/bar/BarResourceMonitor.qml | 526 ++++++++++++++++++ modules/services/SystemResources.qml | 2 +- .../dashboard/controls/SystemPanel.qml | 460 ++++++++++----- 6 files changed, 913 insertions(+), 129 deletions(-) create mode 100644 modules/bar/BarResourceMonitor.qml diff --git a/config/Config.qml b/config/Config.qml index 4ced9dda..a31e9fd9 100644 --- a/config/Config.qml +++ b/config/Config.qml @@ -946,6 +946,17 @@ Singleton { property bool autoStart: false property bool syncSpotify: false } + property JsonObject resources: JsonObject { + property bool enabled: true + property string location: "dashboard" + property string barSide: "left" + property JsonObject show: JsonObject { + property bool cpu: true + property bool ram: true + property bool gpu: true + property bool disk: true + } + } } } diff --git a/config/defaults/system.js b/config/defaults/system.js index e3619102..c55d3755 100644 --- a/config/defaults/system.js +++ b/config/defaults/system.js @@ -44,5 +44,16 @@ var data = { "restTime": 300, "autoStart": false, "syncSpotify": false + }, + "resources": { + "enabled": true, + "location": "dashboard", + "barSide": "left", + "show": { + "cpu": true, + "ram": true, + "gpu": true, + "disk": true + } } } diff --git a/modules/bar/BarContent.qml b/modules/bar/BarContent.qml index b9e57ca7..a3d560cd 100644 --- a/modules/bar/BarContent.qml +++ b/modules/bar/BarContent.qml @@ -193,6 +193,12 @@ Item { // Shadow logic for bar components readonly property bool shadowsEnabled: Config.showBackground && (!actualContainBar || (Config.bar && Config.bar.keepBarShadow !== undefined ? Config.bar.keepBarShadow : false)) + // Resource monitor placement — top-level bindings ensure reactive updates on config change + readonly property bool resourceMonitorEnabled: Config.system.resources ? Config.system.resources.enabled !== false : false + readonly property bool resourceMonitorInBar: resourceMonitorEnabled && (Config.system.resources ? Config.system.resources.location === "bar" : false) + readonly property bool resourceMonitorLeft: resourceMonitorInBar && (Config.system.resources ? Config.system.resources.barSide !== "right" : true) + readonly property bool resourceMonitorRight: resourceMonitorInBar && !resourceMonitorLeft + // The hitbox for the mask property alias barHitbox: barMouseArea @@ -459,6 +465,19 @@ Item { } } + // Resource Monitor — left position (after pin button) + Loader { + active: root.resourceMonitorLeft + visible: active + Layout.alignment: Qt.AlignVCenter + sourceComponent: BarResourceMonitor { + bar: root + layerEnabled: root.shadowsEnabled + startRadius: root.outerRadius + endRadius: root.outerRadius + } + } + Item { Layout.fillWidth: true Layout.fillHeight: true @@ -499,6 +518,19 @@ Item { visible: !(root.orientation === "horizontal" && integratedDockEnabled) } + // Resource Monitor — right position (before presets) + Loader { + active: root.resourceMonitorRight + visible: active + Layout.alignment: Qt.AlignVCenter + sourceComponent: BarResourceMonitor { + bar: root + layerEnabled: root.shadowsEnabled + startRadius: root.outerRadius + endRadius: root.outerRadius + } + } + PresetsButton { id: presetsButton startRadius: root.dockAtEnd ? root.innerRadius : root.outerRadius diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml new file mode 100644 index 00000000..37f358c8 --- /dev/null +++ b/modules/bar/BarResourceMonitor.qml @@ -0,0 +1,526 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import qs.modules.theme +import qs.modules.components +import qs.modules.services +import qs.config + +Item { + id: root + + required property var bar + + property bool vertical: bar.orientation === "vertical" + property bool isHovered: false + property bool layerEnabled: true + + property real startRadius: 0 + property real endRadius: 0 + + // I18n helper — works with or without i18n PR merged + function tr(key, fallback) { + try { + const val = I18n.t(key); + return (val && val !== key) ? val : fallback; + } catch (e) { + return fallback; + } + } + + // Resource visibility config with safe defaults + readonly property bool showCpu: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.cpu !== false : true + readonly property bool showRam: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.ram !== false : true + readonly property bool showGpu: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.gpu !== false : true + readonly property bool showDisk: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.disk !== false : true + + function gpuColor(vendor) { + switch ((vendor || "").toLowerCase()) { + case "nvidia": return Colors.green; + case "amd": return Colors.red; + case "intel": return Colors.blue; + default: return Colors.magenta; + } + } + + Layout.preferredWidth: bg.implicitWidth + Layout.preferredHeight: 36 + Layout.alignment: Qt.AlignVCenter + + HoverHandler { + onHoveredChanged: root.isHovered = hovered + } + + StyledRect { + id: bg + variant: popup.isOpen ? "primary" : "bg" + anchors.fill: parent + enableShadow: root.layerEnabled + + implicitWidth: itemsRow.implicitWidth + 16 + implicitHeight: 36 + + topLeftRadius: root.vertical ? root.startRadius : root.startRadius + topRightRadius: root.vertical ? root.startRadius : root.endRadius + bottomLeftRadius: root.vertical ? root.endRadius : root.startRadius + bottomRightRadius: root.vertical ? root.endRadius : root.endRadius + + Rectangle { + anchors.fill: parent + color: Styling.srItem("overprimary") + opacity: popup.isOpen ? 0 : (root.isHovered ? 0.12 : 0) + radius: parent.radius ?? 0 + Behavior on opacity { + enabled: Config.animDuration > 0 + NumberAnimation { duration: Config.animDuration / 2 } + } + } + + RowLayout { + id: itemsRow + anchors.centerIn: parent + spacing: 8 + + // ── CPU ────────────────────────────────────────────────── + Loader { + active: root.showCpu + visible: active + sourceComponent: RowLayout { + spacing: 3 + Text { + text: Icons.cpu + font.family: Icons.font + font.pixelSize: 13 + color: Colors.red + } + Text { + text: Math.round(SystemResources.cpuUsage) + "%" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + color: Colors.overBackground + } + Text { + visible: SystemResources.cpuTemp >= 0 + text: SystemResources.cpuTemp + "°" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + color: Colors.overSurfaceVariant + } + } + } + + // ── Divider CPU|RAM ────────────────────────────────────── + Loader { + active: root.showCpu && root.showRam + visible: active + sourceComponent: Rectangle { + width: 1; height: 16 + color: Colors.outline + opacity: 0.4 + } + } + + // ── RAM ────────────────────────────────────────────────── + Loader { + active: root.showRam + visible: active + sourceComponent: RowLayout { + spacing: 3 + Text { + text: Icons.ram + font.family: Icons.font + font.pixelSize: 13 + color: Colors.cyan + } + Text { + text: Math.round(SystemResources.ramUsage) + "%" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + color: Colors.overBackground + } + } + } + + // ── Divider RAM|GPU ────────────────────────────────────── + Loader { + active: (root.showCpu || root.showRam) && root.showGpu && SystemResources.gpuDetected + visible: active + sourceComponent: Rectangle { + width: 1; height: 16 + color: Colors.outline + opacity: 0.4 + } + } + + // ── GPU(s) ─────────────────────────────────────────────── + Loader { + active: root.showGpu && SystemResources.gpuDetected + visible: active + sourceComponent: RowLayout { + spacing: 4 + Repeater { + model: SystemResources.gpuCount + delegate: RowLayout { + required property int index + spacing: 3 + Text { + text: Icons.gpu + font.family: Icons.font + font.pixelSize: 13 + color: root.gpuColor(SystemResources.gpuVendors[index] || "") + } + Text { + text: Math.round(SystemResources.gpuUsages[index] || 0) + "%" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + color: Colors.overBackground + } + Text { + visible: (SystemResources.gpuTemps[index] ?? -1) >= 0 + text: (SystemResources.gpuTemps[index] || 0) + "°" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + color: Colors.overSurfaceVariant + } + } + } + } + } + + // ── Divider GPU|Disk ───────────────────────────────────── + Loader { + active: (root.showCpu || root.showRam || (root.showGpu && SystemResources.gpuDetected)) && root.showDisk && SystemResources.validDisks.length > 0 + visible: active + sourceComponent: Rectangle { + width: 1; height: 16 + color: Colors.outline + opacity: 0.4 + } + } + + // ── Disk(s) ────────────────────────────────────────────── + Loader { + active: root.showDisk && SystemResources.validDisks.length > 0 + visible: active + sourceComponent: RowLayout { + spacing: 4 + Repeater { + model: SystemResources.validDisks + delegate: RowLayout { + required property string modelData + spacing: 3 + Text { + text: Icons.disk + font.family: Icons.font + font.pixelSize: 13 + color: Colors.yellow + } + Text { + text: Math.round(SystemResources.diskUsage[modelData] || 0) + "%" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + color: Colors.overBackground + } + } + } + } + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: popup.toggle() + } + + StyledToolTip { + show: root.isHovered && !popup.isOpen + tooltipText: root.tr("bar.resources.tooltip", "System Resources") + } + } + + // ── Detail popup ───────────────────────────────────────────────── + BarPopup { + id: popup + anchorItem: bg + bar: root.bar + contentWidth: 220 + contentHeight: popupColumn.implicitHeight + popup.popupPadding * 2 + + ColumnLayout { + id: popupColumn + anchors.fill: parent + spacing: 4 + + // CPU detail + Loader { + active: root.showCpu + visible: active + Layout.fillWidth: true + sourceComponent: StyledRect { + variant: "common" + implicitHeight: detailCpuRow.implicitHeight + 12 + radius: Styling.radius(-2) + + RowLayout { + id: detailCpuRow + anchors { + left: parent.left; right: parent.right + verticalCenter: parent.verticalCenter + leftMargin: 10; rightMargin: 10 + } + spacing: 8 + + Text { + text: Icons.cpu + font.family: Icons.font + font.pixelSize: 16 + color: Colors.red + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 1 + + RowLayout { + Layout.fillWidth: true + + Text { + text: "CPU" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overBackground + Layout.fillWidth: true + } + + Text { + visible: SystemResources.cpuTemp >= 0 + text: SystemResources.cpuTemp + "°C" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + color: Colors.overSurfaceVariant + } + + Text { + text: Math.round(SystemResources.cpuUsage) + "%" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overBackground + } + } + + Text { + text: SystemResources.cpuModel + visible: SystemResources.cpuModel !== "" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + color: Colors.overSurfaceVariant + Layout.fillWidth: true + elide: Text.ElideRight + } + } + } + } + } + + // RAM detail + Loader { + active: root.showRam + visible: active + Layout.fillWidth: true + sourceComponent: StyledRect { + variant: "common" + implicitHeight: detailRamRow.implicitHeight + 12 + radius: Styling.radius(-2) + + RowLayout { + id: detailRamRow + anchors { + left: parent.left; right: parent.right + verticalCenter: parent.verticalCenter + leftMargin: 10; rightMargin: 10 + } + spacing: 8 + + Text { + text: Icons.ram + font.family: Icons.font + font.pixelSize: 16 + color: Colors.cyan + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 1 + + RowLayout { + Layout.fillWidth: true + + Text { + text: "RAM" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overBackground + Layout.fillWidth: true + } + + Text { + text: Math.round(SystemResources.ramUsage) + "%" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overBackground + } + } + + Text { + text: { + const used = (SystemResources.ramUsed / 1024 / 1024).toFixed(1); + const total = (SystemResources.ramTotal / 1024 / 1024).toFixed(1); + return used + " / " + total + " GB"; + } + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + color: Colors.overSurfaceVariant + Layout.fillWidth: true + } + } + } + } + } + + // GPU(s) detail + Loader { + active: root.showGpu && SystemResources.gpuDetected + visible: active + Layout.fillWidth: true + sourceComponent: ColumnLayout { + spacing: 4 + Repeater { + model: SystemResources.gpuCount + delegate: StyledRect { + required property int index + variant: "common" + implicitHeight: detailGpuRow.implicitHeight + 12 + radius: Styling.radius(-2) + Layout.fillWidth: true + + RowLayout { + id: detailGpuRow + anchors { + left: parent.left; right: parent.right + verticalCenter: parent.verticalCenter + leftMargin: 10; rightMargin: 10 + } + spacing: 8 + + Text { + text: Icons.gpu + font.family: Icons.font + font.pixelSize: 16 + color: root.gpuColor(SystemResources.gpuVendors[index] || "") + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 1 + + RowLayout { + Layout.fillWidth: true + + Text { + text: SystemResources.gpuNames[index] || "GPU" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overBackground + Layout.fillWidth: true + elide: Text.ElideRight + } + + Text { + visible: (SystemResources.gpuTemps[index] ?? -1) >= 0 + text: (SystemResources.gpuTemps[index] || 0) + "°C" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + color: Colors.overSurfaceVariant + } + + Text { + text: Math.round(SystemResources.gpuUsages[index] || 0) + "%" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overBackground + } + } + } + } + } + } + } + } + + // Disk(s) detail + Loader { + active: root.showDisk && SystemResources.validDisks.length > 0 + visible: active + Layout.fillWidth: true + sourceComponent: ColumnLayout { + spacing: 4 + Repeater { + model: SystemResources.validDisks + delegate: StyledRect { + required property string modelData + variant: "common" + implicitHeight: detailDiskRow.implicitHeight + 12 + radius: Styling.radius(-2) + Layout.fillWidth: true + + RowLayout { + id: detailDiskRow + anchors { + left: parent.left; right: parent.right + verticalCenter: parent.verticalCenter + leftMargin: 10; rightMargin: 10 + } + spacing: 8 + + Text { + text: Icons.disk + font.family: Icons.font + font.pixelSize: 16 + color: Colors.yellow + } + + RowLayout { + Layout.fillWidth: true + + Text { + text: modelData + font.family: Config.theme.monoFont + font.pixelSize: Styling.fontSize(-1) + color: Colors.overBackground + Layout.fillWidth: true + elide: Text.ElideMiddle + } + + Text { + text: Math.round(SystemResources.diskUsage[modelData] || 0) + "%" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overBackground + } + } + } + } + } + } + } + } + } +} diff --git a/modules/services/SystemResources.qml b/modules/services/SystemResources.qml index c16ff085..83a4ab82 100644 --- a/modules/services/SystemResources.qml +++ b/modules/services/SystemResources.qml @@ -59,7 +59,7 @@ Singleton { // Optimized GPU polling avoids waking dGPUs. property Process monitorProcess: Process { id: monitorProcess - running: GlobalStates.dashboardOpen && GlobalStates.dashboardCurrentTab === 2 && root.validDisks.length > 0 + running: (Config.system.resources && Config.system.resources.enabled !== false) && ((GlobalStates.dashboardOpen && GlobalStates.dashboardCurrentTab === 2) || Config.system.resources.location === "bar") && root.validDisks.length > 0 command: { let cmd = ["python3", Quickshell.shellDir + "/scripts/system_monitor.py", root.updateInterval.toString()]; diff --git a/modules/widgets/dashboard/controls/SystemPanel.qml b/modules/widgets/dashboard/controls/SystemPanel.qml index c3bf0421..218a3224 100644 --- a/modules/widgets/dashboard/controls/SystemPanel.qml +++ b/modules/widgets/dashboard/controls/SystemPanel.qml @@ -17,6 +17,16 @@ Item { property string currentSection: "" + // I18n helper — returns English fallback when I18n is unavailable or key missing + function tr(key, fallback) { + try { + const val = I18n.t(key); + return (val && val !== key) ? val : fallback; + } catch (e) { + return fallback; + } + } + component SectionButton: StyledRect { id: sectionBtn required property string text @@ -84,21 +94,34 @@ Item { width: root.contentWidth anchors.horizontalCenter: parent.horizontalCenter title: root.currentSection === "" ? "System" : (root.currentSection === "system" ? "System Resources" : (root.currentSection.charAt(0).toUpperCase() + root.currentSection.slice(1))) - statusText: "" + statusText: { + if (root.currentSection === "system") { + if (systemSection.savedSuccess) + return root.tr("system.resources.saved", "Saved"); + if (systemSection.hasChanges) + return root.tr("system.resources.unsaved", "Unsaved changes"); + } + return ""; + } + statusColor: systemSection.savedSuccess ? Colors.green : Styling.srItem("overprimary") actions: { + let acts = []; if (root.currentSection !== "") { - return [ - { - icon: Icons.arrowLeft, - tooltip: "Back", - onClicked: function () { - root.currentSection = ""; - } - } - ]; + acts.push({ + icon: Icons.arrowLeft, + tooltip: "Back", + onClicked: function () { root.currentSection = ""; } + }); } - return []; + if (root.currentSection === "system" && systemSection.hasChanges) { + acts.push({ + icon: Icons.accept, + tooltip: root.tr("settings.save", "Save"), + onClicked: function () { systemSection.saveToConfig(); } + }); + } + return acts; } } } @@ -436,157 +459,338 @@ Item { // SYSTEM SECTION // ===================== ColumnLayout { + id: systemSection visible: root.currentSection === "system" - property string settingsSection: "system" Layout.fillWidth: true spacing: 8 - Text { - text: "System Resources" - font.family: Config.theme.font - font.pixelSize: Styling.fontSize(-1) - font.weight: Font.Medium - color: Colors.overSurfaceVariant - Layout.bottomMargin: -4 + // ── Pending state ───────────────────────────────────── + property bool resEnabled: true + property string resLocation: "dashboard" + property string resBarSide: "left" + property bool resShowCpu: true + property bool resShowRam: true + property bool resShowGpu: true + property bool resShowDisk: true + property bool savedSuccess: false + + property bool hasChanges: { + const res = Config.system.resources; + if (!res) return false; + if ((res.enabled !== false) !== resEnabled) return true; + if ((res.location || "dashboard") !== resLocation) return true; + if ((res.barSide || "left") !== resBarSide) return true; + const show = res.show; + if ((show ? show.cpu !== false : true) !== resShowCpu) return true; + if ((show ? show.ram !== false : true) !== resShowRam) return true; + if ((show ? show.gpu !== false : true) !== resShowGpu) return true; + if ((show ? show.disk !== false : true) !== resShowDisk) return true; + return false; } - Text { - text: "Configure which disks to monitor" - font.family: Config.theme.font - font.pixelSize: Styling.fontSize(-2) - color: Colors.overSurfaceVariant - opacity: 0.7 + function resetToConfig() { + const res = Config.system.resources; + if (!res) return; + resEnabled = res.enabled !== false; + resLocation = res.location || "dashboard"; + resBarSide = res.barSide || "left"; + const show = res.show; + resShowCpu = show ? show.cpu !== false : true; + resShowRam = show ? show.ram !== false : true; + resShowGpu = show ? show.gpu !== false : true; + resShowDisk = show ? show.disk !== false : true; + } + + function saveToConfig() { + const res = Config.system.resources; + if (!res) return; + res.enabled = resEnabled; + res.location = resLocation; + res.barSide = resBarSide; + if (res.show) { + res.show.cpu = resShowCpu; + res.show.ram = resShowRam; + res.show.gpu = resShowGpu; + res.show.disk = resShowDisk; + } + savedSuccess = true; + savedTimer.restart(); + } + + Component.onCompleted: resetToConfig() + onVisibleChanged: if (visible) resetToConfig() + + Timer { + id: savedTimer + interval: 2000 + onTriggered: systemSection.savedSuccess = false + } + + // ── Global enable toggle ────────────────────────────── + ToggleRow { + Layout.fillWidth: true + label: root.tr("system.resources.enabled", "Enable Resource Monitor") + checked: systemSection.resEnabled + onToggled: checked => { systemSection.resEnabled = checked; } } - // Disks list + // ── Settings (dimmed when monitor is disabled) ───────── ColumnLayout { Layout.fillWidth: true - spacing: 4 + spacing: 8 + enabled: systemSection.resEnabled + opacity: systemSection.resEnabled ? 1.0 : 0.4 + + Behavior on opacity { + enabled: Config.animDuration > 0 + NumberAnimation { duration: Config.animDuration / 2 } + } - Repeater { - id: disksRepeater - model: Config.system.disks + Text { + text: "System Resources" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overSurfaceVariant + Layout.bottomMargin: -4 + } - delegate: RowLayout { - id: diskRow - required property string modelData - required property int index + Text { + text: root.tr("system.resources.description", "Configure resource display and monitored disks") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + color: Colors.overSurfaceVariant + opacity: 0.7 + } - Layout.fillWidth: true - spacing: 8 + // ── Display Location ────────────────────────────── + Text { + text: root.tr("system.resources.location", "Display Location") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overSurfaceVariant + Layout.topMargin: 4 + Layout.bottomMargin: -4 + } + + SegmentedSwitch { + Layout.fillWidth: true + options: [ + root.tr("system.resources.location_dashboard", "Dashboard"), + root.tr("system.resources.location_bar", "Bar") + ] + currentIndex: systemSection.resLocation === "bar" ? 1 : 0 + onIndexChanged: index => { + systemSection.resLocation = index === 1 ? "bar" : "dashboard"; + } + } + + // ── Bar Side (visible only when location = bar) ─── + Text { + visible: systemSection.resLocation === "bar" + text: root.tr("system.resources.bar_side", "Bar Position") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overSurfaceVariant + Layout.topMargin: 4 + Layout.bottomMargin: -4 + } + + SegmentedSwitch { + visible: systemSection.resLocation === "bar" + Layout.fillWidth: true + options: [ + root.tr("system.resources.bar_side_left", "Left"), + root.tr("system.resources.bar_side_right", "Right") + ] + currentIndex: systemSection.resBarSide === "right" ? 1 : 0 + onIndexChanged: index => { + systemSection.resBarSide = index === 1 ? "right" : "left"; + } + } + + // ── Visible Items ───────────────────────────────── + Text { + text: root.tr("system.resources.show_items", "Visible Items") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overSurfaceVariant + Layout.topMargin: 4 + Layout.bottomMargin: -4 + } + + ToggleRow { + Layout.fillWidth: true + label: root.tr("system.resources.show_cpu", "CPU") + checked: systemSection.resShowCpu + onToggled: checked => { systemSection.resShowCpu = checked; } + } + + ToggleRow { + Layout.fillWidth: true + label: root.tr("system.resources.show_ram", "RAM") + checked: systemSection.resShowRam + onToggled: checked => { systemSection.resShowRam = checked; } + } + + ToggleRow { + Layout.fillWidth: true + label: root.tr("system.resources.show_gpu", "GPU") + checked: systemSection.resShowGpu + onToggled: checked => { systemSection.resShowGpu = checked; } + } + + ToggleRow { + Layout.fillWidth: true + label: root.tr("system.resources.show_disk", "Disk") + checked: systemSection.resShowDisk + onToggled: checked => { systemSection.resShowDisk = checked; } + } + + // ── Monitored Disks ─────────────────────────────── + Text { + text: root.tr("system.resources.disks_title", "Monitored Disks") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + font.weight: Font.Medium + color: Colors.overSurfaceVariant + Layout.topMargin: 4 + Layout.bottomMargin: -4 + } + + // Disks list + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + + Repeater { + id: disksRepeater + model: Config.system.disks + + delegate: RowLayout { + id: diskRow + required property string modelData + required property int index - StyledRect { - variant: "common" Layout.fillWidth: true - Layout.preferredHeight: 36 - radius: Styling.radius(-2) + spacing: 8 + + StyledRect { + variant: "common" + Layout.fillWidth: true + Layout.preferredHeight: 36 + radius: Styling.radius(-2) + + TextInput { + id: diskInput + anchors.fill: parent + anchors.margins: 8 + font.family: Config.theme.monoFont + font.pixelSize: Styling.monoFontSize(0) + color: Colors.overBackground + selectByMouse: true + clip: true + verticalAlignment: TextInput.AlignVCenter + text: diskRow.modelData + + onEditingFinished: { + if (text.trim() !== diskRow.modelData) { + let newDisks = Config.system.disks.slice(); + newDisks[diskRow.index] = text.trim(); + Config.system.disks = newDisks; + } + } - TextInput { - id: diskInput - anchors.fill: parent - anchors.margins: 8 - font.family: Config.theme.monoFont - font.pixelSize: Styling.monoFontSize(0) - color: Colors.overBackground - selectByMouse: true - clip: true - verticalAlignment: TextInput.AlignVCenter - text: diskRow.modelData - - onEditingFinished: { - if (text.trim() !== diskRow.modelData) { + Text { + anchors.verticalCenter: parent.verticalCenter + visible: !diskInput.text && !diskInput.activeFocus + text: "e.g. /, /home..." + font: diskInput.font + color: Colors.overSurfaceVariant + } + } + } + + // Remove button + StyledRect { + id: removeDiskButton + variant: removeDiskArea.containsMouse ? "focus" : "common" + Layout.preferredWidth: 36 + Layout.preferredHeight: 36 + radius: Styling.radius(-2) + visible: disksRepeater.count > 1 + + Text { + anchors.centerIn: parent + text: Icons.trash + font.family: Icons.font + font.pixelSize: 14 + color: Colors.error + } + + MouseArea { + id: removeDiskArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { let newDisks = Config.system.disks.slice(); - newDisks[diskRow.index] = text.trim(); + newDisks.splice(diskRow.index, 1); Config.system.disks = newDisks; } } - Text { - anchors.verticalCenter: parent.verticalCenter - visible: !diskInput.text && !diskInput.activeFocus - text: "e.g. /, /home..." - font: diskInput.font - color: Colors.overSurfaceVariant + StyledToolTip { + visible: removeDiskArea.containsMouse + tooltipText: "Remove disk" } } } + } - // Remove button - StyledRect { - id: removeDiskButton - variant: removeDiskArea.containsMouse ? "focus" : "common" - Layout.preferredWidth: 36 - Layout.preferredHeight: 36 - radius: Styling.radius(-2) - visible: disksRepeater.count > 1 + // Add disk button + StyledRect { + id: addDiskButton + variant: addDiskArea.containsMouse ? "primaryfocus" : "primary" + Layout.preferredWidth: addDiskContent.width + 24 + Layout.preferredHeight: 36 + radius: Styling.radius(-2) + + Row { + id: addDiskContent + anchors.centerIn: parent + spacing: 6 Text { - anchors.centerIn: parent - text: Icons.trash + text: Icons.plus font.family: Icons.font font.pixelSize: 14 - color: Colors.error - } - - MouseArea { - id: removeDiskArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - let newDisks = Config.system.disks.slice(); - newDisks.splice(diskRow.index, 1); - Config.system.disks = newDisks; - } + color: addDiskButton.item + anchors.verticalCenter: parent.verticalCenter } - StyledToolTip { - visible: removeDiskArea.containsMouse - tooltipText: "Remove disk" + Text { + text: "Add Disk" + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(0) + color: addDiskButton.item + anchors.verticalCenter: parent.verticalCenter } } - } - } - - // Add disk button - StyledRect { - id: addDiskButton - variant: addDiskArea.containsMouse ? "primaryfocus" : "primary" - Layout.preferredWidth: addDiskContent.width + 24 - Layout.preferredHeight: 36 - radius: Styling.radius(-2) - - Row { - id: addDiskContent - anchors.centerIn: parent - spacing: 6 - - Text { - text: Icons.plus - font.family: Icons.font - font.pixelSize: 14 - color: addDiskButton.item - anchors.verticalCenter: parent.verticalCenter - } - Text { - text: "Add Disk" - font.family: Config.theme.font - font.pixelSize: Styling.fontSize(0) - color: addDiskButton.item - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: addDiskArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - let newDisks = Config.system.disks.slice(); - newDisks.push("/"); - Config.system.disks = newDisks; + MouseArea { + id: addDiskArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + let newDisks = Config.system.disks.slice(); + newDisks.push("/"); + Config.system.disks = newDisks; + } } } } From 00ca598541cc6ab53d30725f5a22c5b1898444d0 Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 01:05:36 +0300 Subject: [PATCH 02/13] fix(bar): set implicitWidth/Height on BarResourceMonitor for Loader sizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Loader uses implicitWidth/implicitHeight of the loaded item to determine its own size. The widget only had Layout.preferredWidth/Height which are Layout-attached hints for the parent layout, not sizing hints for Loader. Result: Loader was 0×0, widget invisible even when active. Fix: add implicitWidth: bg.implicitWidth and implicitHeight: 36 on the root Item so Loader correctly sizes itself and the widget becomes visible. Co-Authored-By: Claude Sonnet 4.6 --- modules/bar/BarResourceMonitor.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml index 37f358c8..785ee4d0 100644 --- a/modules/bar/BarResourceMonitor.qml +++ b/modules/bar/BarResourceMonitor.qml @@ -43,6 +43,8 @@ Item { } } + implicitWidth: bg.implicitWidth + implicitHeight: 36 Layout.preferredWidth: bg.implicitWidth Layout.preferredHeight: 36 Layout.alignment: Qt.AlignVCenter From 3896d2d020a6eaf23eedcc3b181c218b1d6bffde Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 14:46:10 +0300 Subject: [PATCH 03/13] fix(bar-resources): stable width, active colors, dashboard exclusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed-width numeric Text via TextMetrics ("100%"/"100°") so chip width no longer jitters as values change; values right-aligned within fixed slot - Popup contentWidth now matches panel width (bg.width) instead of 220px - All icon/text colors transition to theme's over-primary color (bg.item) when popup is open, with ColorAnimation — matches other bar buttons - Dashboard MetricsTab tab hidden and unloaded when location="bar"; tabModel is computed dynamically (excludes heartbeat icon in bar mode); redirects currentTab 2→0 when switching to bar mode Co-Authored-By: Claude Sonnet 4.6 --- modules/bar/BarResourceMonitor.qml | 62 ++++++++++++++++++++----- modules/widgets/dashboard/Dashboard.qml | 15 +++++- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml index 785ee4d0..bbe20deb 100644 --- a/modules/bar/BarResourceMonitor.qml +++ b/modules/bar/BarResourceMonitor.qml @@ -49,6 +49,24 @@ Item { Layout.preferredHeight: 36 Layout.alignment: Qt.AlignVCenter + // Fixed-width metrics so numeric values don't cause layout jitter + TextMetrics { + id: pctMetrics + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + text: "100%" + } + TextMetrics { + id: tempMetrics + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + text: "100°" + } + readonly property real pctWidth: pctMetrics.width + readonly property real tempWidth: tempMetrics.width + // Foreground color of the bg tile — switches when popup opens (primary variant) + readonly property color itemColor: bg.item + HoverHandler { onHoveredChanged: root.isHovered = hovered } @@ -93,20 +111,27 @@ Item { text: Icons.cpu font.family: Icons.font font.pixelSize: 13 - color: Colors.red + color: popup.isOpen ? root.itemColor : Colors.red + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { text: Math.round(SystemResources.cpuUsage) + "%" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-1) - color: Colors.overBackground + color: popup.isOpen ? root.itemColor : Colors.overBackground + width: root.pctWidth + horizontalAlignment: Text.AlignRight + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { visible: SystemResources.cpuTemp >= 0 text: SystemResources.cpuTemp + "°" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) - color: Colors.overSurfaceVariant + color: popup.isOpen ? root.itemColor : Colors.overSurfaceVariant + width: root.tempWidth + horizontalAlignment: Text.AlignRight + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } } } @@ -132,13 +157,17 @@ Item { text: Icons.ram font.family: Icons.font font.pixelSize: 13 - color: Colors.cyan + color: popup.isOpen ? root.itemColor : Colors.cyan + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { text: Math.round(SystemResources.ramUsage) + "%" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-1) - color: Colors.overBackground + color: popup.isOpen ? root.itemColor : Colors.overBackground + width: root.pctWidth + horizontalAlignment: Text.AlignRight + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } } } @@ -169,20 +198,27 @@ Item { text: Icons.gpu font.family: Icons.font font.pixelSize: 13 - color: root.gpuColor(SystemResources.gpuVendors[index] || "") + color: popup.isOpen ? root.itemColor : root.gpuColor(SystemResources.gpuVendors[index] || "") + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { text: Math.round(SystemResources.gpuUsages[index] || 0) + "%" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-1) - color: Colors.overBackground + color: popup.isOpen ? root.itemColor : Colors.overBackground + width: root.pctWidth + horizontalAlignment: Text.AlignRight + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { visible: (SystemResources.gpuTemps[index] ?? -1) >= 0 text: (SystemResources.gpuTemps[index] || 0) + "°" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) - color: Colors.overSurfaceVariant + color: popup.isOpen ? root.itemColor : Colors.overSurfaceVariant + width: root.tempWidth + horizontalAlignment: Text.AlignRight + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } } } @@ -215,13 +251,17 @@ Item { text: Icons.disk font.family: Icons.font font.pixelSize: 13 - color: Colors.yellow + color: popup.isOpen ? root.itemColor : Colors.yellow + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { text: Math.round(SystemResources.diskUsage[modelData] || 0) + "%" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-1) - color: Colors.overBackground + color: popup.isOpen ? root.itemColor : Colors.overBackground + width: root.pctWidth + horizontalAlignment: Text.AlignRight + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } } } @@ -246,7 +286,7 @@ Item { id: popup anchorItem: bg bar: root.bar - contentWidth: 220 + contentWidth: bg.width contentHeight: popupColumn.implicitHeight + popup.popupPadding * 2 ColumnLayout { diff --git a/modules/widgets/dashboard/Dashboard.qml b/modules/widgets/dashboard/Dashboard.qml index 0c23fc72..9761e7f7 100644 --- a/modules/widgets/dashboard/Dashboard.qml +++ b/modules/widgets/dashboard/Dashboard.qml @@ -22,8 +22,18 @@ NotchAnimationBehavior { property int currentTab: GlobalStates.dashboardCurrentTab } - readonly property var tabModel: [Icons.widgets, Icons.wallpapers, Icons.heartbeat] + readonly property bool resourcesInDashboard: !(Config.system.resources && Config.system.resources.location === "bar") + readonly property var tabModel: { + let m = [Icons.widgets, Icons.wallpapers]; + if (root.resourcesInDashboard) m.push(Icons.heartbeat); + return m; + } readonly property int tabCount: tabModel.length + + onResourcesInDashboardChanged: { + if (!resourcesInDashboard && root.state.currentTab === 2) + stack.navigateToTab(0); + } readonly property int tabSpacing: 8 readonly property int tabWidth: 48 @@ -441,9 +451,10 @@ NotchAnimationBehavior { z: visible ? 1 : 0 } - // Tab 2: Metrics + // Tab 2: Metrics (only when resources are displayed in dashboard, not bar) TabLoader { property int index: 2 + active: (root.shouldTabBeLoaded(index) || root.state.currentTab === index) && root.resourcesInDashboard sourceComponent: metricsComponent z: visible ? 1 : 0 } From f473095d9cb37252ca580ea41b3f9a0966121698 Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 14:56:46 +0300 Subject: [PATCH 04/13] feat(bar-resources): add Both mode, fix stable width, fix segment styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add "Both" location option: resource monitor shown in bar AND dashboard simultaneously; BarContent/Dashboard/SystemResources all updated - Fix bar panel width stability: use Layout.preferredWidth (not width) on numeric Text inside RowLayouts so the layout's implicitWidth is stable - Fix SegmentedSwitch: Layout.fillWidth: true on buttons → equal-width segments that fill the available space instead of shrinking to text Co-Authored-By: Claude Sonnet 4.6 --- modules/bar/BarContent.qml | 2 +- modules/bar/BarResourceMonitor.qml | 12 ++++++------ modules/components/SegmentedSwitch.qml | 2 +- modules/services/SystemResources.qml | 2 +- modules/widgets/dashboard/controls/SystemPanel.qml | 13 +++++++------ 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/modules/bar/BarContent.qml b/modules/bar/BarContent.qml index a3d560cd..320ac040 100644 --- a/modules/bar/BarContent.qml +++ b/modules/bar/BarContent.qml @@ -195,7 +195,7 @@ Item { // Resource monitor placement — top-level bindings ensure reactive updates on config change readonly property bool resourceMonitorEnabled: Config.system.resources ? Config.system.resources.enabled !== false : false - readonly property bool resourceMonitorInBar: resourceMonitorEnabled && (Config.system.resources ? Config.system.resources.location === "bar" : false) + readonly property bool resourceMonitorInBar: resourceMonitorEnabled && (Config.system.resources ? (Config.system.resources.location === "bar" || Config.system.resources.location === "both") : false) readonly property bool resourceMonitorLeft: resourceMonitorInBar && (Config.system.resources ? Config.system.resources.barSide !== "right" : true) readonly property bool resourceMonitorRight: resourceMonitorInBar && !resourceMonitorLeft diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml index bbe20deb..83314010 100644 --- a/modules/bar/BarResourceMonitor.qml +++ b/modules/bar/BarResourceMonitor.qml @@ -119,7 +119,7 @@ Item { font.family: Config.theme.font font.pixelSize: Styling.fontSize(-1) color: popup.isOpen ? root.itemColor : Colors.overBackground - width: root.pctWidth + Layout.preferredWidth: root.pctWidth horizontalAlignment: Text.AlignRight Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } @@ -129,7 +129,7 @@ Item { font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) color: popup.isOpen ? root.itemColor : Colors.overSurfaceVariant - width: root.tempWidth + Layout.preferredWidth: root.tempWidth horizontalAlignment: Text.AlignRight Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } @@ -165,7 +165,7 @@ Item { font.family: Config.theme.font font.pixelSize: Styling.fontSize(-1) color: popup.isOpen ? root.itemColor : Colors.overBackground - width: root.pctWidth + Layout.preferredWidth: root.pctWidth horizontalAlignment: Text.AlignRight Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } @@ -206,7 +206,7 @@ Item { font.family: Config.theme.font font.pixelSize: Styling.fontSize(-1) color: popup.isOpen ? root.itemColor : Colors.overBackground - width: root.pctWidth + Layout.preferredWidth: root.pctWidth horizontalAlignment: Text.AlignRight Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } @@ -216,7 +216,7 @@ Item { font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) color: popup.isOpen ? root.itemColor : Colors.overSurfaceVariant - width: root.tempWidth + Layout.preferredWidth: root.tempWidth horizontalAlignment: Text.AlignRight Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } @@ -259,7 +259,7 @@ Item { font.family: Config.theme.font font.pixelSize: Styling.fontSize(-1) color: popup.isOpen ? root.itemColor : Colors.overBackground - width: root.pctWidth + Layout.preferredWidth: root.pctWidth horizontalAlignment: Text.AlignRight Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } diff --git a/modules/components/SegmentedSwitch.qml b/modules/components/SegmentedSwitch.qml index 8c1e31b5..d8cfe9fa 100644 --- a/modules/components/SegmentedSwitch.qml +++ b/modules/components/SegmentedSwitch.qml @@ -74,8 +74,8 @@ StyledRect { required property int index Layout.fillHeight: true + Layout.fillWidth: true Layout.minimumWidth: root.buttonSize - Layout.preferredWidth: contentRow.implicitWidth + 16 // Add some padding focusPolicy: Qt.NoFocus hoverEnabled: true diff --git a/modules/services/SystemResources.qml b/modules/services/SystemResources.qml index 83a4ab82..4497a195 100644 --- a/modules/services/SystemResources.qml +++ b/modules/services/SystemResources.qml @@ -59,7 +59,7 @@ Singleton { // Optimized GPU polling avoids waking dGPUs. property Process monitorProcess: Process { id: monitorProcess - running: (Config.system.resources && Config.system.resources.enabled !== false) && ((GlobalStates.dashboardOpen && GlobalStates.dashboardCurrentTab === 2) || Config.system.resources.location === "bar") && root.validDisks.length > 0 + running: (Config.system.resources && Config.system.resources.enabled !== false) && ((GlobalStates.dashboardOpen && GlobalStates.dashboardCurrentTab === 2) || Config.system.resources.location === "bar" || Config.system.resources.location === "both") && root.validDisks.length > 0 command: { let cmd = ["python3", Quickshell.shellDir + "/scripts/system_monitor.py", root.updateInterval.toString()]; diff --git a/modules/widgets/dashboard/controls/SystemPanel.qml b/modules/widgets/dashboard/controls/SystemPanel.qml index 218a3224..2e208564 100644 --- a/modules/widgets/dashboard/controls/SystemPanel.qml +++ b/modules/widgets/dashboard/controls/SystemPanel.qml @@ -578,17 +578,18 @@ Item { Layout.fillWidth: true options: [ root.tr("system.resources.location_dashboard", "Dashboard"), - root.tr("system.resources.location_bar", "Bar") + root.tr("system.resources.location_bar", "Bar"), + root.tr("system.resources.location_both", "Both") ] - currentIndex: systemSection.resLocation === "bar" ? 1 : 0 + currentIndex: systemSection.resLocation === "bar" ? 1 : (systemSection.resLocation === "both" ? 2 : 0) onIndexChanged: index => { - systemSection.resLocation = index === 1 ? "bar" : "dashboard"; + systemSection.resLocation = index === 1 ? "bar" : (index === 2 ? "both" : "dashboard"); } } - // ── Bar Side (visible only when location = bar) ─── + // ── Bar Side (visible only when location includes bar) ─── Text { - visible: systemSection.resLocation === "bar" + visible: systemSection.resLocation === "bar" || systemSection.resLocation === "both" text: root.tr("system.resources.bar_side", "Bar Position") font.family: Config.theme.font font.pixelSize: Styling.fontSize(-1) @@ -599,7 +600,7 @@ Item { } SegmentedSwitch { - visible: systemSection.resLocation === "bar" + visible: systemSection.resLocation === "bar" || systemSection.resLocation === "both" Layout.fillWidth: true options: [ root.tr("system.resources.bar_side_left", "Left"), From 29d86f0d5956fab7f0992bb66bba18e030be288c Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 15:01:34 +0300 Subject: [PATCH 05/13] fix(settings): replace SegmentedSwitch with SelectorRow for location/side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SegmentedSwitch looked out of place. SelectorRow uses StyledRect with variant primary/focus/common — the same pattern as ShellPanel and CompositorPanel. Options fill equal width, selected item gets primary color, hover gets focus, unselected is common. DemiBold font weight on selected item for extra clarity. --- .../dashboard/controls/SystemPanel.qml | 75 ++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/modules/widgets/dashboard/controls/SystemPanel.qml b/modules/widgets/dashboard/controls/SystemPanel.qml index 2e208564..01fae992 100644 --- a/modules/widgets/dashboard/controls/SystemPanel.qml +++ b/modules/widgets/dashboard/controls/SystemPanel.qml @@ -71,6 +71,53 @@ Item { } } + // Option selector — same visual style as ShellPanel/CompositorPanel + component SelectorRow: RowLayout { + id: selectorRow + property var options: [] // [{ label: string, value: string }] + property string value: "" + signal valueSelected(string newValue) + + Layout.fillWidth: true + spacing: 4 + + Repeater { + model: selectorRow.options + + delegate: StyledRect { + id: optBtn + required property var modelData + required property int index + + readonly property bool isSelected: optBtn.modelData.value === selectorRow.value + property bool isHovered: false + + variant: isSelected ? "primary" : (isHovered ? "focus" : "common") + Layout.fillWidth: true + Layout.preferredHeight: 36 + radius: Styling.radius(0) + + Text { + anchors.centerIn: parent + text: optBtn.modelData.label + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(0) + font.weight: optBtn.isSelected ? Font.DemiBold : Font.Normal + color: optBtn.item + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: optBtn.isHovered = true + onExited: optBtn.isHovered = false + onClicked: selectorRow.valueSelected(optBtn.modelData.value) + } + } + } + } + // Main content Flickable { id: mainFlickable @@ -574,17 +621,14 @@ Item { Layout.bottomMargin: -4 } - SegmentedSwitch { - Layout.fillWidth: true + SelectorRow { options: [ - root.tr("system.resources.location_dashboard", "Dashboard"), - root.tr("system.resources.location_bar", "Bar"), - root.tr("system.resources.location_both", "Both") + { label: root.tr("system.resources.location_dashboard", "Dashboard"), value: "dashboard" }, + { label: root.tr("system.resources.location_bar", "Bar"), value: "bar" }, + { label: root.tr("system.resources.location_both", "Both"), value: "both" } ] - currentIndex: systemSection.resLocation === "bar" ? 1 : (systemSection.resLocation === "both" ? 2 : 0) - onIndexChanged: index => { - systemSection.resLocation = index === 1 ? "bar" : (index === 2 ? "both" : "dashboard"); - } + value: systemSection.resLocation + onValueSelected: newValue => { systemSection.resLocation = newValue; } } // ── Bar Side (visible only when location includes bar) ─── @@ -599,17 +643,14 @@ Item { Layout.bottomMargin: -4 } - SegmentedSwitch { + SelectorRow { visible: systemSection.resLocation === "bar" || systemSection.resLocation === "both" - Layout.fillWidth: true options: [ - root.tr("system.resources.bar_side_left", "Left"), - root.tr("system.resources.bar_side_right", "Right") + { label: root.tr("system.resources.bar_side_left", "Left"), value: "left" }, + { label: root.tr("system.resources.bar_side_right", "Right"), value: "right" } ] - currentIndex: systemSection.resBarSide === "right" ? 1 : 0 - onIndexChanged: index => { - systemSection.resBarSide = index === 1 ? "right" : "left"; - } + value: systemSection.resBarSide + onValueSelected: newValue => { systemSection.resBarSide = newValue; } } // ── Visible Items ───────────────────────────────── From 826df624d8c6b92ef7be9745b7181f49a7791949 Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 15:24:33 +0300 Subject: [PATCH 06/13] fix(settings): visible items as toggle buttons in wrapping Flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces four ToggleRow checkboxes with a Flow of StyledRect buttons. Active (shown) items get variant primary, inactive get common, hover gets focus — same pattern as SelectorRow. Width fits the label, wraps to next line if needed. --- .../dashboard/controls/SystemPanel.qml | 74 +++++++++++++------ 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/modules/widgets/dashboard/controls/SystemPanel.qml b/modules/widgets/dashboard/controls/SystemPanel.qml index 01fae992..322df941 100644 --- a/modules/widgets/dashboard/controls/SystemPanel.qml +++ b/modules/widgets/dashboard/controls/SystemPanel.qml @@ -664,32 +664,62 @@ Item { Layout.bottomMargin: -4 } - ToggleRow { + Flow { Layout.fillWidth: true - label: root.tr("system.resources.show_cpu", "CPU") - checked: systemSection.resShowCpu - onToggled: checked => { systemSection.resShowCpu = checked; } - } + spacing: 4 - ToggleRow { - Layout.fillWidth: true - label: root.tr("system.resources.show_ram", "RAM") - checked: systemSection.resShowRam - onToggled: checked => { systemSection.resShowRam = checked; } - } + Repeater { + model: [ + { key: "cpu", label: root.tr("system.resources.show_cpu", "CPU") }, + { key: "ram", label: root.tr("system.resources.show_ram", "RAM") }, + { key: "gpu", label: root.tr("system.resources.show_gpu", "GPU") }, + { key: "disk", label: root.tr("system.resources.show_disk", "Disk") } + ] - ToggleRow { - Layout.fillWidth: true - label: root.tr("system.resources.show_gpu", "GPU") - checked: systemSection.resShowGpu - onToggled: checked => { systemSection.resShowGpu = checked; } - } + delegate: StyledRect { + id: visBtn + required property var modelData + required property int index - ToggleRow { - Layout.fillWidth: true - label: root.tr("system.resources.show_disk", "Disk") - checked: systemSection.resShowDisk - onToggled: checked => { systemSection.resShowDisk = checked; } + readonly property bool isOn: { + if (modelData.key === "cpu") return systemSection.resShowCpu; + if (modelData.key === "ram") return systemSection.resShowRam; + if (modelData.key === "gpu") return systemSection.resShowGpu; + if (modelData.key === "disk") return systemSection.resShowDisk; + return false; + } + property bool isHovered: false + + variant: isOn ? "primary" : (isHovered ? "focus" : "common") + width: visBtnLabel.implicitWidth + 24 + height: 36 + radius: Styling.radius(0) + + Text { + id: visBtnLabel + anchors.centerIn: parent + text: visBtn.modelData.label + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(0) + font.weight: visBtn.isOn ? Font.DemiBold : Font.Normal + color: visBtn.item + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: visBtn.isHovered = true + onExited: visBtn.isHovered = false + onClicked: { + if (visBtn.modelData.key === "cpu") systemSection.resShowCpu = !systemSection.resShowCpu; + if (visBtn.modelData.key === "ram") systemSection.resShowRam = !systemSection.resShowRam; + if (visBtn.modelData.key === "gpu") systemSection.resShowGpu = !systemSection.resShowGpu; + if (visBtn.modelData.key === "disk") systemSection.resShowDisk = !systemSection.resShowDisk; + } + } + } + } } // ── Monitored Disks ─────────────────────────────── From 561e7eb5d6497779b3f0ed5d42358bc2475dbc0a Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 15:31:32 +0300 Subject: [PATCH 07/13] feat(system-panel): separate visible items for dashboard and bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When location is 'both', Visible Items section splits into two labeled groups — Dashboard and Bar — each controlling their own show.* and show.bar* config keys independently. --- .../dashboard/controls/SystemPanel.qml | 194 ++++++++++++++---- 1 file changed, 151 insertions(+), 43 deletions(-) diff --git a/modules/widgets/dashboard/controls/SystemPanel.qml b/modules/widgets/dashboard/controls/SystemPanel.qml index 322df941..4ee72383 100644 --- a/modules/widgets/dashboard/controls/SystemPanel.qml +++ b/modules/widgets/dashboard/controls/SystemPanel.qml @@ -519,6 +519,10 @@ Item { property bool resShowRam: true property bool resShowGpu: true property bool resShowDisk: true + property bool resShowBarCpu: true + property bool resShowBarRam: true + property bool resShowBarGpu: true + property bool resShowBarDisk: true property bool savedSuccess: false property bool hasChanges: { @@ -532,6 +536,10 @@ Item { if ((show ? show.ram !== false : true) !== resShowRam) return true; if ((show ? show.gpu !== false : true) !== resShowGpu) return true; if ((show ? show.disk !== false : true) !== resShowDisk) return true; + if ((show ? show.barCpu !== false : true) !== resShowBarCpu) return true; + if ((show ? show.barRam !== false : true) !== resShowBarRam) return true; + if ((show ? show.barGpu !== false : true) !== resShowBarGpu) return true; + if ((show ? show.barDisk !== false : true) !== resShowBarDisk) return true; return false; } @@ -546,6 +554,10 @@ Item { resShowRam = show ? show.ram !== false : true; resShowGpu = show ? show.gpu !== false : true; resShowDisk = show ? show.disk !== false : true; + resShowBarCpu = show ? show.barCpu !== false : true; + resShowBarRam = show ? show.barRam !== false : true; + resShowBarGpu = show ? show.barGpu !== false : true; + resShowBarDisk = show ? show.barDisk !== false : true; } function saveToConfig() { @@ -559,6 +571,10 @@ Item { res.show.ram = resShowRam; res.show.gpu = resShowGpu; res.show.disk = resShowDisk; + res.show.barCpu = resShowBarCpu; + res.show.barRam = resShowBarRam; + res.show.barGpu = resShowBarGpu; + res.show.barDisk = resShowBarDisk; } savedSuccess = true; savedTimer.restart(); @@ -664,58 +680,150 @@ Item { Layout.bottomMargin: -4 } - Flow { + // Dashboard items (shown when location = dashboard or both) + ColumnLayout { Layout.fillWidth: true spacing: 4 + visible: systemSection.resLocation !== "bar" - Repeater { - model: [ - { key: "cpu", label: root.tr("system.resources.show_cpu", "CPU") }, - { key: "ram", label: root.tr("system.resources.show_ram", "RAM") }, - { key: "gpu", label: root.tr("system.resources.show_gpu", "GPU") }, - { key: "disk", label: root.tr("system.resources.show_disk", "Disk") } - ] + Text { + visible: systemSection.resLocation === "both" + text: root.tr("system.resources.location_dashboard", "Dashboard") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + font.weight: Font.Medium + color: Colors.overSurfaceVariant + opacity: 0.7 + } - delegate: StyledRect { - id: visBtn - required property var modelData - required property int index + Flow { + Layout.fillWidth: true + spacing: 4 + + Repeater { + model: [ + { key: "cpu", label: root.tr("system.resources.show_cpu", "CPU") }, + { key: "ram", label: root.tr("system.resources.show_ram", "RAM") }, + { key: "gpu", label: root.tr("system.resources.show_gpu", "GPU") }, + { key: "disk", label: root.tr("system.resources.show_disk", "Disk") } + ] + + delegate: StyledRect { + id: visBtnDash + required property var modelData + required property int index + + readonly property bool isOn: { + if (modelData.key === "cpu") return systemSection.resShowCpu; + if (modelData.key === "ram") return systemSection.resShowRam; + if (modelData.key === "gpu") return systemSection.resShowGpu; + if (modelData.key === "disk") return systemSection.resShowDisk; + return false; + } + property bool isHovered: false - readonly property bool isOn: { - if (modelData.key === "cpu") return systemSection.resShowCpu; - if (modelData.key === "ram") return systemSection.resShowRam; - if (modelData.key === "gpu") return systemSection.resShowGpu; - if (modelData.key === "disk") return systemSection.resShowDisk; - return false; - } - property bool isHovered: false + variant: isOn ? "primary" : (isHovered ? "focus" : "common") + width: visBtnDashLabel.implicitWidth + 24 + height: 36 + radius: Styling.radius(0) - variant: isOn ? "primary" : (isHovered ? "focus" : "common") - width: visBtnLabel.implicitWidth + 24 - height: 36 - radius: Styling.radius(0) + Text { + id: visBtnDashLabel + anchors.centerIn: parent + text: visBtnDash.modelData.label + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(0) + font.weight: visBtnDash.isOn ? Font.DemiBold : Font.Normal + color: visBtnDash.item + } - Text { - id: visBtnLabel - anchors.centerIn: parent - text: visBtn.modelData.label - font.family: Config.theme.font - font.pixelSize: Styling.fontSize(0) - font.weight: visBtn.isOn ? Font.DemiBold : Font.Normal - color: visBtn.item + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: visBtnDash.isHovered = true + onExited: visBtnDash.isHovered = false + onClicked: { + if (visBtnDash.modelData.key === "cpu") systemSection.resShowCpu = !systemSection.resShowCpu; + if (visBtnDash.modelData.key === "ram") systemSection.resShowRam = !systemSection.resShowRam; + if (visBtnDash.modelData.key === "gpu") systemSection.resShowGpu = !systemSection.resShowGpu; + if (visBtnDash.modelData.key === "disk") systemSection.resShowDisk = !systemSection.resShowDisk; + } + } } + } + } + } - MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onEntered: visBtn.isHovered = true - onExited: visBtn.isHovered = false - onClicked: { - if (visBtn.modelData.key === "cpu") systemSection.resShowCpu = !systemSection.resShowCpu; - if (visBtn.modelData.key === "ram") systemSection.resShowRam = !systemSection.resShowRam; - if (visBtn.modelData.key === "gpu") systemSection.resShowGpu = !systemSection.resShowGpu; - if (visBtn.modelData.key === "disk") systemSection.resShowDisk = !systemSection.resShowDisk; + // Bar items (shown when location = bar or both) + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + visible: systemSection.resLocation !== "dashboard" + + Text { + visible: systemSection.resLocation === "both" + text: root.tr("system.resources.location_bar", "Bar") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + font.weight: Font.Medium + color: Colors.overSurfaceVariant + opacity: 0.7 + } + + Flow { + Layout.fillWidth: true + spacing: 4 + + Repeater { + model: [ + { key: "cpu", label: root.tr("system.resources.show_cpu", "CPU") }, + { key: "ram", label: root.tr("system.resources.show_ram", "RAM") }, + { key: "gpu", label: root.tr("system.resources.show_gpu", "GPU") }, + { key: "disk", label: root.tr("system.resources.show_disk", "Disk") } + ] + + delegate: StyledRect { + id: visBtnBar + required property var modelData + required property int index + + readonly property bool isOn: { + if (modelData.key === "cpu") return systemSection.resShowBarCpu; + if (modelData.key === "ram") return systemSection.resShowBarRam; + if (modelData.key === "gpu") return systemSection.resShowBarGpu; + if (modelData.key === "disk") return systemSection.resShowBarDisk; + return false; + } + property bool isHovered: false + + variant: isOn ? "primary" : (isHovered ? "focus" : "common") + width: visBtnBarLabel.implicitWidth + 24 + height: 36 + radius: Styling.radius(0) + + Text { + id: visBtnBarLabel + anchors.centerIn: parent + text: visBtnBar.modelData.label + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(0) + font.weight: visBtnBar.isOn ? Font.DemiBold : Font.Normal + color: visBtnBar.item + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: visBtnBar.isHovered = true + onExited: visBtnBar.isHovered = false + onClicked: { + if (visBtnBar.modelData.key === "cpu") systemSection.resShowBarCpu = !systemSection.resShowBarCpu; + if (visBtnBar.modelData.key === "ram") systemSection.resShowBarRam = !systemSection.resShowBarRam; + if (visBtnBar.modelData.key === "gpu") systemSection.resShowBarGpu = !systemSection.resShowBarGpu; + if (visBtnBar.modelData.key === "disk") systemSection.resShowBarDisk = !systemSection.resShowBarDisk; + } } } } From 4f63d5206fb1f8aeb97004bfdd9ff2a8c00a3bc0 Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 15:41:12 +0300 Subject: [PATCH 08/13] feat(resources): fix dashboard visibility, add temperature toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MetricsTab now reads show.cpu/ram/gpu/disk from config — items were always visible before, settings had no effect - Added show.dashTemp / show.barTemp config keys for temperature display - BarResourceMonitor respects show.barTemp for all temperature elements - SystemPanel adds temperature toggle buttons (per dashboard / per bar) --- config/Config.qml | 6 ++ config/defaults/system.js | 8 ++- modules/bar/BarResourceMonitor.qml | 19 +++--- .../dashboard/controls/SystemPanel.qml | 64 +++++++++++++++++++ .../widgets/dashboard/metrics/MetricsTab.qml | 29 ++++++--- 5 files changed, 107 insertions(+), 19 deletions(-) diff --git a/config/Config.qml b/config/Config.qml index a31e9fd9..92ba6e82 100644 --- a/config/Config.qml +++ b/config/Config.qml @@ -955,6 +955,12 @@ Singleton { property bool ram: true property bool gpu: true property bool disk: true + property bool barCpu: true + property bool barRam: true + property bool barGpu: true + property bool barDisk: true + property bool dashTemp: true + property bool barTemp: true } } } diff --git a/config/defaults/system.js b/config/defaults/system.js index c55d3755..27ac73c4 100644 --- a/config/defaults/system.js +++ b/config/defaults/system.js @@ -53,7 +53,13 @@ var data = { "cpu": true, "ram": true, "gpu": true, - "disk": true + "disk": true, + "barCpu": true, + "barRam": true, + "barGpu": true, + "barDisk": true, + "dashTemp": true, + "barTemp": true } } } diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml index 83314010..6ac95e08 100644 --- a/modules/bar/BarResourceMonitor.qml +++ b/modules/bar/BarResourceMonitor.qml @@ -28,11 +28,12 @@ Item { } } - // Resource visibility config with safe defaults - readonly property bool showCpu: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.cpu !== false : true - readonly property bool showRam: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.ram !== false : true - readonly property bool showGpu: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.gpu !== false : true - readonly property bool showDisk: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.disk !== false : true + // Bar-specific visibility (show.barCpu/barRam/barGpu/barDisk/barTemp) + readonly property bool showCpu: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.barCpu !== false : true + readonly property bool showRam: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.barRam !== false : true + readonly property bool showGpu: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.barGpu !== false : true + readonly property bool showDisk: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.barDisk !== false : true + readonly property bool showTemp: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.barTemp !== false : true function gpuColor(vendor) { switch ((vendor || "").toLowerCase()) { @@ -124,7 +125,7 @@ Item { Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { - visible: SystemResources.cpuTemp >= 0 + visible: root.showTemp && SystemResources.cpuTemp >= 0 text: SystemResources.cpuTemp + "°" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) @@ -211,7 +212,7 @@ Item { Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { - visible: (SystemResources.gpuTemps[index] ?? -1) >= 0 + visible: root.showTemp && (SystemResources.gpuTemps[index] ?? -1) >= 0 text: (SystemResources.gpuTemps[index] || 0) + "°" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) @@ -337,7 +338,7 @@ Item { } Text { - visible: SystemResources.cpuTemp >= 0 + visible: root.showTemp && SystemResources.cpuTemp >= 0 text: SystemResources.cpuTemp + "°C" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) @@ -484,7 +485,7 @@ Item { } Text { - visible: (SystemResources.gpuTemps[index] ?? -1) >= 0 + visible: root.showTemp && (SystemResources.gpuTemps[index] ?? -1) >= 0 text: (SystemResources.gpuTemps[index] || 0) + "°C" font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) diff --git a/modules/widgets/dashboard/controls/SystemPanel.qml b/modules/widgets/dashboard/controls/SystemPanel.qml index 4ee72383..64478c18 100644 --- a/modules/widgets/dashboard/controls/SystemPanel.qml +++ b/modules/widgets/dashboard/controls/SystemPanel.qml @@ -523,6 +523,8 @@ Item { property bool resShowBarRam: true property bool resShowBarGpu: true property bool resShowBarDisk: true + property bool resShowDashTemp: true + property bool resShowBarTemp: true property bool savedSuccess: false property bool hasChanges: { @@ -540,6 +542,8 @@ Item { if ((show ? show.barRam !== false : true) !== resShowBarRam) return true; if ((show ? show.barGpu !== false : true) !== resShowBarGpu) return true; if ((show ? show.barDisk !== false : true) !== resShowBarDisk) return true; + if ((show ? show.dashTemp !== false : true) !== resShowDashTemp) return true; + if ((show ? show.barTemp !== false : true) !== resShowBarTemp) return true; return false; } @@ -558,6 +562,8 @@ Item { resShowBarRam = show ? show.barRam !== false : true; resShowBarGpu = show ? show.barGpu !== false : true; resShowBarDisk = show ? show.barDisk !== false : true; + resShowDashTemp = show ? show.dashTemp !== false : true; + resShowBarTemp = show ? show.barTemp !== false : true; } function saveToConfig() { @@ -575,6 +581,8 @@ Item { res.show.barRam = resShowBarRam; res.show.barGpu = resShowBarGpu; res.show.barDisk = resShowBarDisk; + res.show.dashTemp = resShowDashTemp; + res.show.barTemp = resShowBarTemp; } savedSuccess = true; savedTimer.restart(); @@ -753,6 +761,34 @@ Item { } } } + + // Temperature toggle + StyledRect { + id: tempBtnDash + property bool isHovered: false + readonly property bool isOn: systemSection.resShowDashTemp + variant: isOn ? "primary" : (isHovered ? "focus" : "common") + width: tempBtnDashLabel.implicitWidth + 24 + height: 36 + radius: Styling.radius(0) + Text { + id: tempBtnDashLabel + anchors.centerIn: parent + text: root.tr("system.resources.show_temp", "Temp") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(0) + font.weight: tempBtnDash.isOn ? Font.DemiBold : Font.Normal + color: tempBtnDash.item + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: tempBtnDash.isHovered = true + onExited: tempBtnDash.isHovered = false + onClicked: systemSection.resShowDashTemp = !systemSection.resShowDashTemp + } + } } // Bar items (shown when location = bar or both) @@ -828,6 +864,34 @@ Item { } } } + + // Temperature toggle + StyledRect { + id: tempBtnBar + property bool isHovered: false + readonly property bool isOn: systemSection.resShowBarTemp + variant: isOn ? "primary" : (isHovered ? "focus" : "common") + width: tempBtnBarLabel.implicitWidth + 24 + height: 36 + radius: Styling.radius(0) + Text { + id: tempBtnBarLabel + anchors.centerIn: parent + text: root.tr("system.resources.show_temp", "Temp") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(0) + font.weight: tempBtnBar.isOn ? Font.DemiBold : Font.Normal + color: tempBtnBar.item + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: tempBtnBar.isHovered = true + onExited: tempBtnBar.isHovered = false + onClicked: systemSection.resShowBarTemp = !systemSection.resShowBarTemp + } + } } // ── Monitored Disks ─────────────────────────────── diff --git a/modules/widgets/dashboard/metrics/MetricsTab.qml b/modules/widgets/dashboard/metrics/MetricsTab.qml index 97b91b37..2d8d5bcb 100644 --- a/modules/widgets/dashboard/metrics/MetricsTab.qml +++ b/modules/widgets/dashboard/metrics/MetricsTab.qml @@ -22,6 +22,13 @@ Rectangle { property var linuxLogos: null property real chartZoom: 1.0 + // Dashboard-specific visibility from config + readonly property bool showCpu: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.cpu !== false : true + readonly property bool showRam: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.ram !== false : true + readonly property bool showGpu: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.gpu !== false : true + readonly property bool showDisk: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.disk !== false : true + readonly property bool showTemp: Config.system.resources && Config.system.resources.show ? Config.system.resources.show.dashTemp !== false : true + // Adjust history points based on zoom and repaint chart onChartZoomChanged: { // Store enough history to support zoom out @@ -368,6 +375,7 @@ Rectangle { Column { width: parent.width spacing: 4 + visible: root.showCpu ResourceItem { width: parent.width @@ -403,7 +411,7 @@ Rectangle { } Text { - visible: SystemResources.cpuTemp >= 0 + visible: root.showTemp && SystemResources.cpuTemp >= 0 text: Icons.temperature font.family: Icons.font font.pixelSize: Styling.fontSize(-2) @@ -411,7 +419,7 @@ Rectangle { } Text { - visible: SystemResources.cpuTemp >= 0 + visible: root.showTemp && SystemResources.cpuTemp >= 0 text: `${SystemResources.cpuTemp}°` font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) @@ -425,6 +433,7 @@ Rectangle { Column { width: parent.width spacing: 4 + visible: root.showRam ResourceItem { width: parent.width @@ -468,7 +477,7 @@ Rectangle { // GPUs (if detected) - show one bar per GPU Repeater { id: gpuRepeater - model: SystemResources.gpuDetected ? SystemResources.gpuCount : 0 + model: (root.showGpu && SystemResources.gpuDetected) ? SystemResources.gpuCount : 0 Column { required property int index @@ -535,7 +544,7 @@ Rectangle { } Text { - visible: (SystemResources.gpuTemps[index] ?? -1) >= 0 + visible: root.showTemp && (SystemResources.gpuTemps[index] ?? -1) >= 0 text: Icons.temperature font.family: Icons.font font.pixelSize: Styling.fontSize(-2) @@ -555,7 +564,7 @@ Rectangle { } Text { - visible: (SystemResources.gpuTemps[index] ?? -1) >= 0 + visible: root.showTemp && (SystemResources.gpuTemps[index] ?? -1) >= 0 text: `${SystemResources.gpuTemps[index]}°` font.family: Config.theme.font font.pixelSize: Styling.fontSize(-2) @@ -569,7 +578,7 @@ Rectangle { // Disks Repeater { id: diskRepeater - model: SystemResources.validDisks + model: root.showDisk ? SystemResources.validDisks : [] Column { required property string modelData @@ -781,13 +790,15 @@ Rectangle { } // Draw CPU line (red) - drawLine(SystemResources.cpuHistory, Colors.red); + if (root.showCpu) + drawLine(SystemResources.cpuHistory, Colors.red); // Draw RAM line (cyan) - drawLine(SystemResources.ramHistory, Colors.cyan); + if (root.showRam) + drawLine(SystemResources.ramHistory, Colors.cyan); // Draw GPU lines (color based on vendor) - if (SystemResources.gpuDetected && SystemResources.gpuCount > 0) { + if (root.showGpu && SystemResources.gpuDetected && SystemResources.gpuCount > 0) { for (let i = 0; i < SystemResources.gpuCount; i++) { if (SystemResources.gpuHistories[i] && SystemResources.gpuHistories[i].length > 0) { // Get vendor-specific color From 676eb1020c20178fa9d05208929d13e7ac3ef9a6 Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 16:05:57 +0300 Subject: [PATCH 09/13] fix(resources): address code review issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SystemPanel: use else if in toggle onClick chains (no wasted comparisons) - BarResourceMonitor: implement vertical bar mode — compact ColumnLayout chip, Layout.fillWidth, dynamic implicitHeight based on orientation - BarContent: add resource monitor Loaders to vertical bar layout (top position for barSide=left, bottom for barSide=right) - BarResourceMonitor: show "No metrics enabled" message in popup when all items are hidden, preventing a 0-height popup - SystemResources: document trade-off of continuous process when location includes bar --- modules/bar/BarContent.qml | 26 +++++ modules/bar/BarResourceMonitor.qml | 104 +++++++++++++++++- modules/services/SystemResources.qml | 8 +- .../dashboard/controls/SystemPanel.qml | 16 +-- 4 files changed, 140 insertions(+), 14 deletions(-) diff --git a/modules/bar/BarContent.qml b/modules/bar/BarContent.qml index 320ac040..ce6f6329 100644 --- a/modules/bar/BarContent.qml +++ b/modules/bar/BarContent.qml @@ -624,6 +624,19 @@ Item { enableShadow: root.shadowsEnabled } + // Resource Monitor — top position (barSide = left) + Loader { + active: root.resourceMonitorLeft + visible: active + Layout.fillWidth: true + sourceComponent: BarResourceMonitor { + bar: root + layerEnabled: root.shadowsEnabled + startRadius: root.outerRadius + endRadius: root.outerRadius + } + } + // Center Group Container Item { Layout.fillHeight: true @@ -759,6 +772,19 @@ Item { } } + // Resource Monitor — bottom position (barSide = right) + Loader { + active: root.resourceMonitorRight + visible: active + Layout.fillWidth: true + sourceComponent: BarResourceMonitor { + bar: root + layerEnabled: root.shadowsEnabled + startRadius: root.outerRadius + endRadius: root.outerRadius + } + } + ControlsButton { id: controlsButtonVert bar: root diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml index 6ac95e08..8eac78ce 100644 --- a/modules/bar/BarResourceMonitor.qml +++ b/modules/bar/BarResourceMonitor.qml @@ -45,9 +45,10 @@ Item { } implicitWidth: bg.implicitWidth - implicitHeight: 36 + implicitHeight: bg.implicitHeight Layout.preferredWidth: bg.implicitWidth - Layout.preferredHeight: 36 + Layout.preferredHeight: bg.implicitHeight + Layout.fillWidth: root.vertical Layout.alignment: Qt.AlignVCenter // Fixed-width metrics so numeric values don't cause layout jitter @@ -78,8 +79,8 @@ Item { anchors.fill: parent enableShadow: root.layerEnabled - implicitWidth: itemsRow.implicitWidth + 16 - implicitHeight: 36 + implicitWidth: root.vertical ? (itemsCol.implicitWidth + 16) : (itemsRow.implicitWidth + 16) + implicitHeight: root.vertical ? (itemsCol.implicitHeight + 16) : 36 topLeftRadius: root.vertical ? root.startRadius : root.startRadius topRightRadius: root.vertical ? root.startRadius : root.endRadius @@ -97,9 +98,93 @@ Item { } } + // ── Vertical chip (one row per metric) ─────────────────────── + ColumnLayout { + id: itemsCol + anchors.centerIn: parent + visible: root.vertical + spacing: 2 + + Loader { + active: root.showCpu + visible: active + sourceComponent: RowLayout { + spacing: 3 + Text { + text: Icons.cpu; font.family: Icons.font; font.pixelSize: 11 + color: popup.isOpen ? root.itemColor : Colors.red + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } + } + Text { + text: Math.round(SystemResources.cpuUsage) + "%" + font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2) + color: popup.isOpen ? root.itemColor : Colors.overBackground + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } + } + } + } + Loader { + active: root.showRam + visible: active + sourceComponent: RowLayout { + spacing: 3 + Text { + text: Icons.ram; font.family: Icons.font; font.pixelSize: 11 + color: popup.isOpen ? root.itemColor : Colors.cyan + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } + } + Text { + text: Math.round(SystemResources.ramUsage) + "%" + font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2) + color: popup.isOpen ? root.itemColor : Colors.overBackground + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } + } + } + } + Loader { + active: root.showGpu && SystemResources.gpuDetected + visible: active + sourceComponent: RowLayout { + spacing: 3 + Text { + text: Icons.gpu; font.family: Icons.font; font.pixelSize: 11 + color: popup.isOpen ? root.itemColor : root.gpuColor(SystemResources.gpuVendors[0] || "") + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } + } + Text { + text: Math.round(SystemResources.gpuUsages[0] || 0) + "%" + font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2) + color: popup.isOpen ? root.itemColor : Colors.overBackground + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } + } + } + } + Loader { + active: root.showDisk && SystemResources.validDisks.length > 0 + visible: active + sourceComponent: RowLayout { + spacing: 3 + Text { + text: Icons.disk; font.family: Icons.font; font.pixelSize: 11 + color: popup.isOpen ? root.itemColor : Colors.yellow + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } + } + Text { + readonly property string firstDisk: SystemResources.validDisks.length > 0 ? SystemResources.validDisks[0] : "/" + text: Math.round(SystemResources.diskUsage[firstDisk] || 0) + "%" + font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2) + color: popup.isOpen ? root.itemColor : Colors.overBackground + Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } + } + } + } + } + + // ── Horizontal chip (icon+value per metric in a row) ────────── RowLayout { id: itemsRow anchors.centerIn: parent + visible: !root.vertical spacing: 8 // ── CPU ────────────────────────────────────────────────── @@ -295,6 +380,17 @@ Item { anchors.fill: parent spacing: 4 + // Shown when all metrics are hidden + Text { + visible: !root.showCpu && !root.showRam && !(root.showGpu && SystemResources.gpuDetected) && !(root.showDisk && SystemResources.validDisks.length > 0) + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + text: root.tr("bar.resources.nothing", "No metrics enabled") + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-1) + color: Colors.overSurfaceVariant + } + // CPU detail Loader { active: root.showCpu diff --git a/modules/services/SystemResources.qml b/modules/services/SystemResources.qml index 4497a195..51111674 100644 --- a/modules/services/SystemResources.qml +++ b/modules/services/SystemResources.qml @@ -55,8 +55,12 @@ Singleton { property int updateInterval: 2000 // Unified monitor process. - // Resource-efficient: only runs when dashboard is open. - // Optimized GPU polling avoids waking dGPUs. + // When location = "dashboard": only runs while the metrics tab is open — fully lazy. + // When location = "bar" or "both": runs continuously for the session so the bar + // widget stays live. This is a deliberate trade-off: the python script still uses + // the is_active power-state check to avoid polling an idle dGPU, but the process + // itself stays alive and polls CPU/RAM/disk at updateInterval ms regardless of + // bar visibility. property Process monitorProcess: Process { id: monitorProcess running: (Config.system.resources && Config.system.resources.enabled !== false) && ((GlobalStates.dashboardOpen && GlobalStates.dashboardCurrentTab === 2) || Config.system.resources.location === "bar" || Config.system.resources.location === "both") && root.validDisks.length > 0 diff --git a/modules/widgets/dashboard/controls/SystemPanel.qml b/modules/widgets/dashboard/controls/SystemPanel.qml index 64478c18..1cef6850 100644 --- a/modules/widgets/dashboard/controls/SystemPanel.qml +++ b/modules/widgets/dashboard/controls/SystemPanel.qml @@ -752,10 +752,10 @@ Item { onEntered: visBtnDash.isHovered = true onExited: visBtnDash.isHovered = false onClicked: { - if (visBtnDash.modelData.key === "cpu") systemSection.resShowCpu = !systemSection.resShowCpu; - if (visBtnDash.modelData.key === "ram") systemSection.resShowRam = !systemSection.resShowRam; - if (visBtnDash.modelData.key === "gpu") systemSection.resShowGpu = !systemSection.resShowGpu; - if (visBtnDash.modelData.key === "disk") systemSection.resShowDisk = !systemSection.resShowDisk; + if (visBtnDash.modelData.key === "cpu") systemSection.resShowCpu = !systemSection.resShowCpu; + else if (visBtnDash.modelData.key === "ram") systemSection.resShowRam = !systemSection.resShowRam; + else if (visBtnDash.modelData.key === "gpu") systemSection.resShowGpu = !systemSection.resShowGpu; + else if (visBtnDash.modelData.key === "disk") systemSection.resShowDisk = !systemSection.resShowDisk; } } } @@ -855,10 +855,10 @@ Item { onEntered: visBtnBar.isHovered = true onExited: visBtnBar.isHovered = false onClicked: { - if (visBtnBar.modelData.key === "cpu") systemSection.resShowBarCpu = !systemSection.resShowBarCpu; - if (visBtnBar.modelData.key === "ram") systemSection.resShowBarRam = !systemSection.resShowBarRam; - if (visBtnBar.modelData.key === "gpu") systemSection.resShowBarGpu = !systemSection.resShowBarGpu; - if (visBtnBar.modelData.key === "disk") systemSection.resShowBarDisk = !systemSection.resShowBarDisk; + if (visBtnBar.modelData.key === "cpu") systemSection.resShowBarCpu = !systemSection.resShowBarCpu; + else if (visBtnBar.modelData.key === "ram") systemSection.resShowBarRam = !systemSection.resShowBarRam; + else if (visBtnBar.modelData.key === "gpu") systemSection.resShowBarGpu = !systemSection.resShowBarGpu; + else if (visBtnBar.modelData.key === "disk") systemSection.resShowBarDisk = !systemSection.resShowBarDisk; } } } From 378e6a02449026d2b3e1fbadc4bafd19f0dfc5c9 Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 16:08:46 +0300 Subject: [PATCH 10/13] fix(bar-resource-monitor): vertical mode chip and popup layout Chip: replace horizontal RowLayout per metric with Column (icon above value, both centered) so the widget looks correct in a vertical bar. Popup: use fixed contentWidth=240 in vertical mode instead of bg.width (~36px), which was clipping all labels to icon-only width. --- modules/bar/BarResourceMonitor.qml | 47 ++++++++++++++++++------------ 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml index 8eac78ce..25c93b9f 100644 --- a/modules/bar/BarResourceMonitor.qml +++ b/modules/bar/BarResourceMonitor.qml @@ -98,26 +98,28 @@ Item { } } - // ── Vertical chip (one row per metric) ─────────────────────── + // ── Vertical chip (icon above value, one column per metric) ── ColumnLayout { id: itemsCol anchors.centerIn: parent visible: root.vertical - spacing: 2 + spacing: 4 Loader { active: root.showCpu visible: active - sourceComponent: RowLayout { - spacing: 3 + Layout.alignment: Qt.AlignHCenter + sourceComponent: Column { + spacing: 0 + horizontalItemAlignment: Qt.AlignHCenter Text { - text: Icons.cpu; font.family: Icons.font; font.pixelSize: 11 + text: Icons.cpu; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : Colors.red Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { text: Math.round(SystemResources.cpuUsage) + "%" - font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2) + font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } @@ -126,16 +128,18 @@ Item { Loader { active: root.showRam visible: active - sourceComponent: RowLayout { - spacing: 3 + Layout.alignment: Qt.AlignHCenter + sourceComponent: Column { + spacing: 0 + horizontalItemAlignment: Qt.AlignHCenter Text { - text: Icons.ram; font.family: Icons.font; font.pixelSize: 11 + text: Icons.ram; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : Colors.cyan Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { text: Math.round(SystemResources.ramUsage) + "%" - font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2) + font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } @@ -144,16 +148,18 @@ Item { Loader { active: root.showGpu && SystemResources.gpuDetected visible: active - sourceComponent: RowLayout { - spacing: 3 + Layout.alignment: Qt.AlignHCenter + sourceComponent: Column { + spacing: 0 + horizontalItemAlignment: Qt.AlignHCenter Text { - text: Icons.gpu; font.family: Icons.font; font.pixelSize: 11 + text: Icons.gpu; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : root.gpuColor(SystemResources.gpuVendors[0] || "") Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { text: Math.round(SystemResources.gpuUsages[0] || 0) + "%" - font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2) + font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } @@ -162,17 +168,19 @@ Item { Loader { active: root.showDisk && SystemResources.validDisks.length > 0 visible: active - sourceComponent: RowLayout { - spacing: 3 + Layout.alignment: Qt.AlignHCenter + sourceComponent: Column { + spacing: 0 + horizontalItemAlignment: Qt.AlignHCenter Text { - text: Icons.disk; font.family: Icons.font; font.pixelSize: 11 + text: Icons.disk; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : Colors.yellow Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { readonly property string firstDisk: SystemResources.validDisks.length > 0 ? SystemResources.validDisks[0] : "/" text: Math.round(SystemResources.diskUsage[firstDisk] || 0) + "%" - font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2) + font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } @@ -372,7 +380,8 @@ Item { id: popup anchorItem: bg bar: root.bar - contentWidth: bg.width + // In vertical bar bg.width is the narrow bar width — use fixed width instead + contentWidth: root.vertical ? 240 : bg.width contentHeight: popupColumn.implicitHeight + popup.popupPadding * 2 ColumnLayout { From 0cb630b706e14fb8e2be8bb9cc0d6b0b69aa58e5 Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 16:10:57 +0300 Subject: [PATCH 11/13] fix(bar-resource-monitor): Column->ColumnLayout, fix startup crash --- modules/bar/BarResourceMonitor.qml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml index 25c93b9f..a7eadd4f 100644 --- a/modules/bar/BarResourceMonitor.qml +++ b/modules/bar/BarResourceMonitor.qml @@ -109,15 +109,16 @@ Item { active: root.showCpu visible: active Layout.alignment: Qt.AlignHCenter - sourceComponent: Column { + sourceComponent: ColumnLayout { spacing: 0 - horizontalItemAlignment: Qt.AlignHCenter Text { + Layout.alignment: Qt.AlignHCenter text: Icons.cpu; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : Colors.red Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { + Layout.alignment: Qt.AlignHCenter text: Math.round(SystemResources.cpuUsage) + "%" font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground @@ -129,15 +130,16 @@ Item { active: root.showRam visible: active Layout.alignment: Qt.AlignHCenter - sourceComponent: Column { + sourceComponent: ColumnLayout { spacing: 0 - horizontalItemAlignment: Qt.AlignHCenter Text { + Layout.alignment: Qt.AlignHCenter text: Icons.ram; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : Colors.cyan Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { + Layout.alignment: Qt.AlignHCenter text: Math.round(SystemResources.ramUsage) + "%" font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground @@ -149,15 +151,16 @@ Item { active: root.showGpu && SystemResources.gpuDetected visible: active Layout.alignment: Qt.AlignHCenter - sourceComponent: Column { + sourceComponent: ColumnLayout { spacing: 0 - horizontalItemAlignment: Qt.AlignHCenter Text { + Layout.alignment: Qt.AlignHCenter text: Icons.gpu; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : root.gpuColor(SystemResources.gpuVendors[0] || "") Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { + Layout.alignment: Qt.AlignHCenter text: Math.round(SystemResources.gpuUsages[0] || 0) + "%" font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground @@ -169,15 +172,16 @@ Item { active: root.showDisk && SystemResources.validDisks.length > 0 visible: active Layout.alignment: Qt.AlignHCenter - sourceComponent: Column { + sourceComponent: ColumnLayout { spacing: 0 - horizontalItemAlignment: Qt.AlignHCenter Text { + Layout.alignment: Qt.AlignHCenter text: Icons.disk; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : Colors.yellow Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { + Layout.alignment: Qt.AlignHCenter readonly property string firstDisk: SystemResources.validDisks.length > 0 ? SystemResources.validDisks[0] : "/" text: Math.round(SystemResources.diskUsage[firstDisk] || 0) + "%" font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium From 6a638313c7f1a8606a8108ba9afcd8f10028311d Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 16:12:30 +0300 Subject: [PATCH 12/13] fix(bar-resource-monitor): stable centering in vertical chip Add pctMetricsSmall TextMetrics (fontSize -2) for vertical chip items. Set Layout.preferredWidth: pctWidthSmall and horizontalAlignment: Center on both icon and value texts so width is fixed and nothing shifts when the percentage value changes digits. --- modules/bar/BarResourceMonitor.qml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml index a7eadd4f..5a47109e 100644 --- a/modules/bar/BarResourceMonitor.qml +++ b/modules/bar/BarResourceMonitor.qml @@ -66,6 +66,13 @@ Item { } readonly property real pctWidth: pctMetrics.width readonly property real tempWidth: tempMetrics.width + TextMetrics { + id: pctMetricsSmall + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(-2) + text: "100%" + } + readonly property real pctWidthSmall: pctMetricsSmall.width // Foreground color of the bg tile — switches when popup opens (primary variant) readonly property color itemColor: bg.item @@ -113,12 +120,16 @@ Item { spacing: 0 Text { Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.pctWidthSmall + horizontalAlignment: Text.AlignHCenter text: Icons.cpu; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : Colors.red Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.pctWidthSmall + horizontalAlignment: Text.AlignHCenter text: Math.round(SystemResources.cpuUsage) + "%" font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground @@ -134,12 +145,16 @@ Item { spacing: 0 Text { Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.pctWidthSmall + horizontalAlignment: Text.AlignHCenter text: Icons.ram; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : Colors.cyan Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.pctWidthSmall + horizontalAlignment: Text.AlignHCenter text: Math.round(SystemResources.ramUsage) + "%" font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground @@ -155,12 +170,16 @@ Item { spacing: 0 Text { Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.pctWidthSmall + horizontalAlignment: Text.AlignHCenter text: Icons.gpu; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : root.gpuColor(SystemResources.gpuVendors[0] || "") Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.pctWidthSmall + horizontalAlignment: Text.AlignHCenter text: Math.round(SystemResources.gpuUsages[0] || 0) + "%" font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium color: popup.isOpen ? root.itemColor : Colors.overBackground @@ -176,12 +195,16 @@ Item { spacing: 0 Text { Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.pctWidthSmall + horizontalAlignment: Text.AlignHCenter text: Icons.disk; font.family: Icons.font; font.pixelSize: 13 color: popup.isOpen ? root.itemColor : Colors.yellow Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } } } Text { Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.pctWidthSmall + horizontalAlignment: Text.AlignHCenter readonly property string firstDisk: SystemResources.validDisks.length > 0 ? SystemResources.validDisks[0] : "/" text: Math.round(SystemResources.diskUsage[firstDisk] || 0) + "%" font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium From 3445d774e41456c17e990c597f133860034f67ad Mon Sep 17 00:00:00 2001 From: flathead Date: Sat, 28 Mar 2026 16:15:29 +0300 Subject: [PATCH 13/13] fix(bar-resource-monitor): zero implicitWidth in vertical mode In vertical bar the chip was reporting implicitWidth = itemsCol.width+16 (~51px) which widened the entire bar above other elements (36px). Set implicitWidth and Layout.preferredWidth to 0 in vertical mode so the bar width is determined by other elements; Layout.fillWidth handles the actual sizing. --- modules/bar/BarResourceMonitor.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/bar/BarResourceMonitor.qml b/modules/bar/BarResourceMonitor.qml index 5a47109e..535494bb 100644 --- a/modules/bar/BarResourceMonitor.qml +++ b/modules/bar/BarResourceMonitor.qml @@ -44,9 +44,10 @@ Item { } } - implicitWidth: bg.implicitWidth + // In vertical mode don't report a wide implicitWidth — let fillWidth handle sizing + implicitWidth: root.vertical ? 0 : bg.implicitWidth implicitHeight: bg.implicitHeight - Layout.preferredWidth: bg.implicitWidth + Layout.preferredWidth: root.vertical ? 0 : bg.implicitWidth Layout.preferredHeight: bg.implicitHeight Layout.fillWidth: root.vertical Layout.alignment: Qt.AlignVCenter @@ -86,7 +87,7 @@ Item { anchors.fill: parent enableShadow: root.layerEnabled - implicitWidth: root.vertical ? (itemsCol.implicitWidth + 16) : (itemsRow.implicitWidth + 16) + implicitWidth: root.vertical ? 0 : (itemsRow.implicitWidth + 16) implicitHeight: root.vertical ? (itemsCol.implicitHeight + 16) : 36 topLeftRadius: root.vertical ? root.startRadius : root.startRadius