import QtQuick import "." as M M.BarSection { id: root spacing: Math.max(1, M.Theme.moduleSpacing - 2) tooltip: "" property int usage: M.SystemStats.cpuUsage Behavior on usage { enabled: root._showPanel NumberAnimation { duration: 400 easing.type: Easing.OutCubic } } property real freqGhz: M.SystemStats.cpuFreqGhz Behavior on freqGhz { enabled: root._showPanel NumberAnimation { duration: 400 easing.type: Easing.OutCubic } } readonly property var _cores: M.SystemStats.cpuCores readonly property var _coreMaxFreq: M.SystemStats.cpuCoreMaxFreq readonly property var _coreTypes: M.SystemStats.cpuCoreTypes property bool _pinned: false readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered readonly property bool _showPanel: _anyHover || _pinned property bool _coreConsumerActive: false on_ShowPanelChanged: { if (_showPanel && !_coreConsumerActive) { _coreConsumerActive = true; M.SystemStats.coreConsumers++; } else if (!_showPanel && _coreConsumerActive) { _coreConsumerActive = false; M.SystemStats.coreConsumers--; } } property M.ProcessList _procs: M.ProcessList { sortBy: "cpu" active: root._showPanel onProcessesChanged: hoverPanel.keepOpen(300) } 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); } M.BarIcon { icon: "\uF2DB" anchors.verticalCenter: parent.verticalCenter TapHandler { cursorShape: Qt.PointingHandCursor onTapped: root._pinned = !root._pinned } } M.BarLabel { label: M.SystemStats.cpuUsage.toString().padStart(2) + "%@" + M.SystemStats.cpuFreqGhz.toFixed(2) minText: "99%@9.99" 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-cpu" panelTitle: "CPU" contentWidth: 260 // Header — freq + usage (title comes from panelTitle) Item { width: parent.width height: 28 Text { id: _headerFreq anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: root.freqGhz.toFixed(2) + " GHz" color: root.accentColor font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily font.bold: true width: _headerFreqSizer.implicitWidth horizontalAlignment: Text.AlignRight Text { id: _headerFreqSizer visible: false text: "9.99 GHz" font: parent.font } } Text { anchors.right: _headerFreq.left anchors.rightMargin: 6 anchors.verticalCenter: parent.verticalCenter text: root.usage + "%" color: root.accentColor font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily font.bold: true width: _headerUsageSizer.implicitWidth horizontalAlignment: Text.AlignRight Text { id: _headerUsageSizer visible: false text: "100%" font: parent.font } } } // Per-core rows Repeater { model: root._cores.length delegate: Item { required property int index width: hoverPanel.contentWidth readonly property int _u: root._cores[index]?.usage ?? 0 readonly property real _f: root._cores[index]?.freq_ghz ?? 0 readonly property color _barColor: root._loadColor(_u) readonly property bool _throttled: { const maxF = root._coreMaxFreq[index] ?? 0; return maxF > 0 && _f < maxF * 0.85 && _u >= 60; } readonly property bool _isFirstECore: { const types = root._coreTypes; if (!types.length || index >= types.length) return false; if (types[index] !== "Efficiency") return false; return index === 0 || types[index - 1] !== "Efficiency"; } height: _isFirstECore ? 28 : 20 // P/E-core divider Rectangle { visible: parent._isFirstECore anchors.top: parent.top anchors.topMargin: 3 anchors.horizontalCenter: parent.horizontalCenter width: parent.width - 16 height: 1 color: M.Theme.base03 } // Row content pinned to bottom of delegate Item { anchors.bottom: parent.bottom width: parent.width height: 20 Text { id: coreLabel anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: index color: M.Theme.base04 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily width: 16 } Item { id: coreBar anchors.left: coreLabel.right anchors.leftMargin: 6 anchors.right: sparkline.left anchors.rightMargin: 6 anchors.verticalCenter: parent.verticalCenter height: 4 Rectangle { anchors.fill: parent color: M.Theme.base02 radius: 2 } Rectangle { width: parent.width * (parent.parent.parent._u / 100) height: parent.height color: parent.parent.parent._barColor radius: 2 Behavior on width { enabled: root._showPanel NumberAnimation { duration: 150 } } } } // Sparkline Canvas { id: sparkline anchors.right: freqLabel.left anchors.rightMargin: 6 anchors.verticalCenter: parent.verticalCenter width: 32 height: 10 property var _hist: root._cores[parent.parent.index]?.history ?? [] property color _col: parent.parent._barColor on_HistChanged: if (root._showPanel) requestPaint() on_ColChanged: 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 bw = width / d.length; ctx.fillStyle = _col.toString(); for (let i = 0; i < d.length; i++) { const h = Math.max(1, height * d[i] / 100); ctx.fillRect(i * bw, height - h, Math.max(1, bw - 0.5), h); } } } Text { id: freqLabel anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: parent.parent._f.toFixed(2) color: parent.parent._throttled ? M.Theme.base08 : M.Theme.base04 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily width: 34 horizontalAlignment: Text.AlignRight } } } } // Process list separator Rectangle { width: parent.width - 16 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: M.Theme.base03 } Item { width: hoverPanel.contentWidth height: 18 Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: "PROCESS" 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: "CPU" color: M.Theme.base03 font.pixelSize: M.Theme.fontSize - 3 font.family: M.Theme.fontFamily font.letterSpacing: 1 } } // Top processes by CPU Repeater { model: root._procs.processes delegate: Item { required property var modelData width: hoverPanel.contentWidth height: 20 Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: modelData.cmd color: M.Theme.base05 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily elide: Text.ElideRight width: parent.width - 80 } Text { anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: modelData.cpu.toFixed(1) + "%" color: root._loadColor(modelData.cpu) font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily width: 36 horizontalAlignment: Text.AlignRight } } } Item { width: 1 height: 4 } } }