cpu panel: sparklines, throttle highlight, animated header, P/E-core grouping

This commit is contained in:
Damocles 2026-04-14 01:04:52 +02:00
parent db9e38d267
commit 75875b54a0

View file

@ -8,11 +8,28 @@ M.BarSection {
tooltip: ""
property int usage: 0
Behavior on usage {
NumberAnimation {
duration: 400
easing.type: Easing.OutCubic
}
}
property real freqGhz: 0
Behavior on freqGhz {
NumberAnimation {
duration: 400
easing.type: Easing.OutCubic
}
}
property var _prev: null
property var _corePrev: []
property var _coreUsage: []
property var _coreFreq: []
property var _coreHistory: [] // array of arrays, last 16 samples per core
property var _coreMaxFreq: [] // max freq in GHz per core, from cpufreq
property var _coreTypes: [] // "Performance" or "Efficiency" per core
FileView {
id: stat
@ -57,6 +74,17 @@ M.BarSection {
}
root._coreUsage = newUsage;
root._corePrev = newPrev;
// Update sparkline history
const histLen = 16;
const oldH = root._coreHistory;
const newH = [];
for (let i = 0; i < newUsage.length; i++) {
const prev = i < oldH.length ? oldH[i] : [];
const next = prev.concat([newUsage[i]]);
newH.push(next.length > histLen ? next.slice(next.length - histLen) : next);
}
root._coreHistory = newH;
}
}
@ -73,6 +101,28 @@ M.BarSection {
}
}
// Read per-core max freq once at init
Process {
running: true
command: ["sh", "-c", "for f in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/cpuinfo_max_freq; do [ -f \"$f\" ] && cat \"$f\" || echo 0; done 2>/dev/null"]
stdout: StdioCollector {
onStreamFinished: {
root._coreMaxFreq = text.trim().split("\n").filter(l => l).map(l => parseInt(l) / 1e6);
}
}
}
// Read P/E-core topology once at init
Process {
running: true
command: ["sh", "-c", "for d in /sys/devices/system/cpu/cpu[0-9]*/topology/core_type; do [ -f \"$d\" ] && cat \"$d\" || echo Performance; done 2>/dev/null"]
stdout: StdioCollector {
onStreamFinished: {
root._coreTypes = text.trim().split("\n").filter(l => l).map(l => l.trim());
}
}
}
Timer {
interval: M.Modules.cpu.interval || 1000
running: true
@ -135,7 +185,7 @@ M.BarSection {
anchorItem: root
accentColor: root.accentColor
panelNamespace: "nova-cpu"
contentWidth: 220
contentWidth: 260
// Header
Item {
@ -171,62 +221,126 @@ M.BarSection {
delegate: Item {
required property int index
width: hoverPanel.contentWidth
height: 20
readonly property int _u: root._coreUsage[index] ?? 0
readonly property real _f: root._coreFreq[index] ?? 0
readonly property color _barColor: root._loadColor(_u)
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: 14
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";
}
Item {
id: coreBar
anchors.left: coreLabel.right
anchors.leftMargin: 6
anchors.right: freqLabel.left
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter
height: 4
height: _isFirstECore ? 28 : 20
Rectangle {
anchors.fill: parent
color: M.Theme.base02
radius: 2
// 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
}
Rectangle {
width: parent.width * (parent.parent._u / 100)
height: parent.height
color: parent.parent._barColor
radius: 2
Behavior on width {
NumberAnimation {
duration: 150
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 {
NumberAnimation {
duration: 150
}
}
}
}
}
Text {
id: freqLabel
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: parent._f.toFixed(2)
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
width: 32
horizontalAlignment: Text.AlignRight
// Sparkline
Canvas {
id: sparkline
anchors.right: freqLabel.left
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter
width: 32
height: 10
property var _hist: root._coreHistory[parent.parent.index] || []
property color _col: parent.parent._barColor
on_HistChanged: requestPaint()
on_ColChanged: 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
}
}
}
}
@ -274,7 +388,6 @@ M.BarSection {
}
}
// Bottom padding
Item {
width: 1
height: 4