import QtQuick import QtQuick.Layouts import Quickshell.Io import "../../config" as Cfg Rectangle { id: root property real panelRadius: Cfg.Config.radius / 2 property int borderWidth: Cfg.Config.panelBorderWidth property int barHeight: Cfg.Config.barHeight property int barMargin: Cfg.Config.margin readonly property var t: Cfg.Config.theme width: statsLayout.implicitWidth + 20 height: Cfg.Config.moduleHeight color: t.statsBg radius: panelRadius border.color: t.statsBorder border.width: borderWidth Behavior on width { NumberAnimation { duration: 250; easing.type: Easing.OutCubic } } property bool expanded: Cfg.Config.statsStartExpanded property bool hovered: false HoverHandler { onHoveredChanged: root.hovered = hovered } TapHandler { onTapped: { root.expanded = !root.expanded if (!root.expanded) root.hovered = false } } property real cpuVal: 0 property real memVal: 0 property real diskVal: 0 Timer { interval: 2000 running: true repeat: true triggeredOnStart: true onTriggered: { statsProc.running = true } } Process { id: statsProc command: ["sh", "-c", "top -bn1 | awk '/^%Cpu/ {print 100-$8}'; free | awk '/Mem:/ {print ($2-$7)/$2 * 100}'; df / --output=pcent | tail -1 | tr -dc '0-9'"] property string rawOutput: "" stdout: SplitParser { onRead: data => { statsProc.rawOutput += data + "\n" } } onRunningChanged: { if (!statsProc.running && statsProc.rawOutput) { const lines = statsProc.rawOutput.trim().split("\n") if (lines.length >= 3) { let v = parseFloat(lines[0]); root.cpuVal = isNaN(v) ? 0 : v v = parseFloat(lines[1]); root.memVal = isNaN(v) ? 0 : v v = parseFloat(lines[2]); root.diskVal = isNaN(v) ? 0 : v } statsProc.rawOutput = "" } } } component StatRing: RowLayout { id: ring spacing: 5 property real value: 0.0 property string icon: "" property real iconXOff: 0 property color ringColor: Cfg.Config.theme.accent property color trackColor: Cfg.Config.theme.statsTrackColor Behavior on value { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } readonly property real fraction: Math.max(0, Math.min(value, 100)) / 100 Text { id: leftPct text: Math.round(ring.value) + "%" color: ring.ringColor font.pixelSize: Cfg.Config.statsRingFontSize font.bold: true font.family: "monospace" Layout.alignment: Qt.AlignVCenter clip: true readonly property bool active: Cfg.Config.statsLabelPosition === "left" && (root.expanded || root.hovered) opacity: active ? 1 : 0 Layout.preferredWidth: active ? implicitWidth : 0 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } } Item { id: ringCore Layout.preferredWidth: Cfg.Config.statsRingSize Layout.preferredHeight: Cfg.Config.statsRingSize onVisibleChanged: if (visible) arc.requestPaint() Canvas { id: arc anchors.fill: parent antialiasing: true Connections { target: ring function onFractionChanged() { arc.requestPaint() } function onRingColorChanged() { arc.requestPaint() } } onPaint: { var ctx = getContext("2d") ctx.reset() var cx = width / 2 var cy = height / 2 var lw = 2.8 var r = Math.min(cx, cy) - lw / 2 - 0.5 ctx.beginPath() ctx.arc(cx, cy, r, 0, Math.PI * 2) ctx.strokeStyle = ring.trackColor ctx.lineWidth = lw ctx.stroke() if (ring.fraction > 0) { var start = -Math.PI / 2 var end = start + (ring.fraction * Math.PI * 2) ctx.beginPath() ctx.arc(cx, cy, r, start, end) ctx.strokeStyle = ring.ringColor ctx.lineWidth = lw ctx.lineCap = "round" ctx.stroke() } } } Text { anchors.centerIn: parent anchors.horizontalCenterOffset: ring.iconXOff text: ring.icon color: ring.ringColor font.pixelSize: Cfg.Config.statsIconSize } } Text { id: rightPct text: Math.round(ring.value) + "%" color: ring.ringColor font.pixelSize: Cfg.Config.statsRingFontSize font.bold: true font.family: "monospace" Layout.alignment: Qt.AlignVCenter clip: true readonly property bool active: Cfg.Config.statsLabelPosition === "right" && (root.expanded || root.hovered) opacity: active ? 1 : 0 Layout.preferredWidth: active ? implicitWidth : 0 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } } } RowLayout { id: statsLayout anchors.centerIn: parent spacing: 8 StatRing { value: root.cpuVal icon: "󰍛" iconXOff: 0.3 ringColor: root.t.statsCpuColor trackColor: root.t.statsTrackColor } StatRing { value: root.memVal icon: "󰑭" ringColor: root.t.statsMemColor trackColor: root.t.statsTrackColor } StatRing { value: root.diskVal icon: "󰒋" ringColor: root.t.statsDiskColor trackColor: root.t.statsTrackColor } } }