add ProcessList singleton, memory hover panel with breakdown + top processes

This commit is contained in:
Damocles 2026-04-14 01:00:08 +02:00
parent edcc78483c
commit 7e0021853f
3 changed files with 309 additions and 7 deletions

View file

@ -5,9 +5,14 @@ import "." as M
M.BarSection {
id: root
spacing: Math.max(1, M.Theme.moduleSpacing - 2)
tooltip: "Memory: " + root.percent + "% used"
tooltip: ""
property int percent: 0
property real usedGb: 0
property real totalGb: 0
property real availGb: 0
property real cachedGb: 0
property real buffersGb: 0
FileView {
id: meminfo
@ -19,12 +24,22 @@ M.BarSection {
if (v)
m[k.trim()] = parseInt(v.trim());
});
const total = m.MemTotal;
const avail = m.MemAvailable;
if (total > 0)
root.percent = Math.round(((total - avail) / total) * 100);
const total = m.MemTotal || 0;
const avail = m.MemAvailable || 0;
const buffers = m.Buffers || 0;
const cached = (m.Cached || 0) + (m.SReclaimable || 0);
const used = total - avail;
if (total > 0) {
root.percent = Math.round(used / total * 100);
root.usedGb = used / 1048576;
root.totalGb = total / 1048576;
root.availGb = avail / 1048576;
root.cachedGb = cached / 1048576;
root.buffersGb = buffers / 1048576;
}
}
}
Timer {
interval: M.Modules.memory.interval || 2000
running: true
@ -32,15 +47,253 @@ M.BarSection {
onTriggered: meminfo.reload()
}
function _fmt(gb) {
return gb >= 10 ? gb.toFixed(1) + "G" : gb.toFixed(2) + "G";
}
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
}
M.BarIcon {
icon: "\uEFC5"
anchors.verticalCenter: parent.verticalCenter
TapHandler {
cursorShape: Qt.PointingHandCursor
onTapped: root._pinned = !root._pinned
}
}
M.BarLabel {
label: root.percent + "%"
minText: "100%"
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-memory"
contentWidth: 240
// Header
Item {
width: parent.width
height: 28
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Memory"
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._fmt(root.usedGb) + " / " + root._fmt(root.totalGb)
color: root.accentColor
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily
font.bold: true
}
}
// Usage bar
Item {
width: parent.width
height: 14
Item {
id: memBar
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
}
// Cached (base0D, behind used)
Rectangle {
width: parent.width * Math.min(1, (root.usedGb + root.cachedGb) / Math.max(root.totalGb, 0.001))
height: parent.height
color: M.Theme.base0D
opacity: 0.4
radius: 3
Behavior on width {
NumberAnimation {
duration: 200
}
}
}
// Used (accentColor, on top)
Rectangle {
width: parent.width * Math.min(1, root.usedGb / Math.max(root.totalGb, 0.001))
height: parent.height
color: root.accentColor
radius: 3
Behavior on width {
NumberAnimation {
duration: 200
}
}
}
}
}
// Breakdown rows
Item {
width: parent.width
height: 18
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Used"
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: root._fmt(root.usedGb)
color: M.Theme.base05
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
}
}
Item {
width: parent.width
height: 18
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Cached"
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: root._fmt(root.cachedGb)
color: M.Theme.base05
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
}
}
Item {
width: parent.width
height: 18
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Available"
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: root._fmt(root.availGb)
color: M.Theme.base05
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
}
}
// Process list separator
Rectangle {
width: parent.width - 16
height: 1
anchors.horizontalCenter: parent.horizontalCenter
color: M.Theme.base03
}
// Top processes by memory
Repeater {
model: M.ProcessList.byMem
delegate: Item {
required property var modelData
width: hoverPanel.contentWidth
height: 20
Text {
id: procCmd
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.mem.toFixed(1) + "%"
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
width: 36
horizontalAlignment: Text.AlignRight
}
}
}
Item {
width: 1
height: 4
}
}
}