nova-shell/modules/Cpu.qml

317 lines
11 KiB
QML

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
}
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"
contentWidth: 260
// Header
Item {
width: parent.width
height: 28
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "CPU"
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 1
font.family: M.Theme.fontFamily
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root.usage + "% " + root.freqGhz.toFixed(2) + " GHz"
color: root.accentColor
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily
font.bold: true
}
}
// 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
}
// 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
}
}
}