import QtQuick import Quickshell import "." as M M.BarSection { id: root spacing: Math.max(1, M.Theme.moduleSpacing - 2) tooltip: "" visible: M.Modules.gpu.enable && M.SystemStats.gpuAvailable property bool _pinned: false readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered readonly property bool _showPanel: _anyHover || _pinned on_AnyHoverChanged: { if (_anyHover) _unpinTimer.stop(); else if (_pinned) _unpinTimer.start(); } Timer { id: _unpinTimer interval: 500 onTriggered: root._pinned = false } function _loadColor(pct) { const t = Math.max(0, Math.min(100, pct)) / 100; const a = t < 0.5 ? M.Theme.base0B : M.Theme.base0A; const b = t < 0.5 ? M.Theme.base0A : M.Theme.base08; const u = t < 0.5 ? t * 2 : (t - 0.5) * 2; return Qt.rgba(a.r + (b.r - a.r) * u, a.g + (b.g - a.g) * u, a.b + (b.b - a.b) * u, 1); } function _fmt(gb) { return gb >= 10 ? gb.toFixed(1) + "G" : gb.toFixed(2) + "G"; } M.BarIcon { icon: "\uDB84\uDCB0" color: root._loadColor(M.SystemStats.gpuUsage) anchors.verticalCenter: parent.verticalCenter TapHandler { cursorShape: Qt.PointingHandCursor onTapped: root._pinned = !root._pinned } } M.BarLabel { label: M.SystemStats.gpuUsage + "%" minText: "100%" color: root._loadColor(M.SystemStats.gpuUsage) anchors.verticalCenter: parent.verticalCenter TapHandler { cursorShape: Qt.PointingHandCursor onTapped: root._pinned = !root._pinned } } M.HoverPanel { id: hoverPanel showPanel: root._showPanel screen: QsWindow.window?.screen ?? null anchorItem: root accentColor: root.accentColor panelNamespace: "nova-gpu" panelTitle: "GPU" contentWidth: 240 // Header — vendor + usage% Item { width: parent.width height: 28 Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: M.SystemStats.gpuVendor.toUpperCase() color: M.Theme.base03 font.pixelSize: M.Theme.fontSize - 3 font.family: M.Theme.fontFamily font.letterSpacing: 1 } Text { anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: M.SystemStats.gpuUsage + "%" color: root._loadColor(M.SystemStats.gpuUsage) font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily font.bold: true } } // Usage bar Item { width: parent.width height: 14 Item { anchors.left: parent.left anchors.leftMargin: 12 anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter height: 6 Rectangle { anchors.fill: parent color: M.Theme.base02 radius: 3 } Rectangle { width: parent.width * Math.min(1, M.SystemStats.gpuUsage / 100) height: parent.height color: root._loadColor(M.SystemStats.gpuUsage) radius: 3 Behavior on width { enabled: root._showPanel NumberAnimation { duration: 200 easing.type: Easing.OutCubic } } } } } // Usage history sparkline Canvas { id: _sparkline anchors.left: parent.left anchors.leftMargin: 12 anchors.right: parent.right anchors.rightMargin: 12 height: 36 property var _hist: M.SystemStats.gpuHistory on_HistChanged: if (root._showPanel) requestPaint() Connections { target: root function on_ShowPanelChanged() { if (root._showPanel) _sparkline.requestPaint(); } } onPaint: { const ctx = getContext("2d"); if (!ctx) return; ctx.clearRect(0, 0, width, height); const d = _hist; if (!d.length) return; const maxSamples = 60; const bw = width / maxSamples; const offset = maxSamples - d.length; for (let i = 0; i < d.length; i++) { const barH = Math.max(1, height * d[i] / 100); const col = root._loadColor(d[i]); ctx.fillStyle = col.toString(); ctx.fillRect((offset + i) * bw, height - barH, Math.max(1, bw - 0.5), barH); } } } // VRAM section Rectangle { width: parent.width - 16 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: M.Theme.base03 } Item { width: parent.width height: 22 Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: "VRAM" color: M.Theme.base03 font.pixelSize: M.Theme.fontSize - 3 font.family: M.Theme.fontFamily font.letterSpacing: 1 } Text { anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: root._fmt(M.SystemStats.gpuVramUsedGb) + " / " + root._fmt(M.SystemStats.gpuVramTotalGb) color: root.accentColor font.pixelSize: M.Theme.fontSize - 1 font.family: M.Theme.fontFamily font.bold: true } } Item { width: parent.width height: 12 Item { anchors.left: parent.left anchors.leftMargin: 12 anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter height: 5 Rectangle { anchors.fill: parent color: M.Theme.base02 radius: 2 } Rectangle { width: M.SystemStats.gpuVramTotalGb > 0 ? parent.width * Math.min(1, M.SystemStats.gpuVramUsedGb / M.SystemStats.gpuVramTotalGb) : 0 height: parent.height color: root.accentColor radius: 2 Behavior on width { enabled: root._showPanel NumberAnimation { duration: 200 easing.type: Easing.OutCubic } } } } } // Temperature row Item { width: parent.width height: 22 visible: M.SystemStats.gpuTempC > 0 Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: "Temp" color: M.Theme.base04 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily } Text { anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: M.SystemStats.gpuTempC + "\u00B0C" color: M.SystemStats.gpuTempC > 85 ? M.Theme.base08 : M.SystemStats.gpuTempC > 70 ? M.Theme.base0A : M.Theme.base05 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily } } Item { width: 1 height: 4 } } }