diff options
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins')
3 files changed, 1427 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml b/raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml new file mode 100644 index 0000000..dc2ea24 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml @@ -0,0 +1,547 @@ +import QtQuick +import Quickshell +import qs.Common +import qs.Widgets + +Item { + id: root + + property real widgetWidth: 280 + property real widgetHeight: 200 + + property string instanceId: "" + property var instanceData: null + readonly property var cfg: instanceData?.config ?? null + readonly property bool isInstance: instanceId !== "" && cfg !== null + + property string clockStyle: isInstance ? (cfg.style ?? "analog") : SettingsData.desktopClockStyle + property bool forceSquare: clockStyle === "analog" + + property real defaultWidth: { + switch (clockStyle) { + case "analog": + return 200; + case "stacked": + return 100; + default: + return 160; + } + } + property real defaultHeight: { + switch (clockStyle) { + case "analog": + return 200; + case "stacked": + return 160; + default: + return 70; + } + } + property real minWidth: { + switch (clockStyle) { + case "analog": + return 120; + case "stacked": + return 70; + default: + return 100; + } + } + property real minHeight: { + switch (clockStyle) { + case "analog": + return 120; + case "stacked": + return 100; + default: + return 45; + } + } + + property bool enabled: isInstance ? (instanceData?.enabled ?? true) : SettingsData.desktopClockEnabled + property real transparency: isInstance ? (cfg.transparency ?? 0.8) : SettingsData.desktopClockTransparency + property string colorMode: isInstance ? (cfg.colorMode ?? "primary") : SettingsData.desktopClockColorMode + property color customColor: isInstance ? (cfg.customColor ?? "#ffffff") : SettingsData.desktopClockCustomColor + property bool showDate: isInstance ? (cfg.showDate ?? true) : SettingsData.desktopClockShowDate + property bool showAnalogNumbers: isInstance ? (cfg.showAnalogNumbers ?? false) : SettingsData.desktopClockShowAnalogNumbers + + readonly property real scaleFactor: Math.min(width, height) / 200 + + readonly property color accentColor: { + if (colorMode === "primary") + return Theme.primary; + if (colorMode === "secondary") + return Theme.secondary; + if (colorMode === "custom") + return customColor; + return Theme.primary; + } + + readonly property color handColor: accentColor + readonly property color handColorDim: Theme.withAlpha(accentColor, 0.65) + readonly property color textColor: Theme.onSurface + readonly property color subtleTextColor: Theme.onSurfaceVariant + readonly property color backgroundColor: Theme.withAlpha(Theme.surface, root.transparency) + + readonly property bool showAnalogSeconds: isInstance ? (cfg.showAnalogSeconds ?? true) : SettingsData.desktopClockShowAnalogSeconds + readonly property bool showDigitalSeconds: isInstance ? (cfg.showDigitalSeconds ?? false) : false + readonly property bool needsSeconds: clockStyle === "analog" ? showAnalogSeconds : showDigitalSeconds + + SystemClock { + id: systemClock + precision: root.needsSeconds ? SystemClock.Seconds : SystemClock.Minutes + } + + Rectangle { + anchors.fill: parent + radius: Theme.cornerRadius + color: root.backgroundColor + visible: root.clockStyle !== "analog" + } + + OrganicBlobHourBulges { + anchors.fill: parent + fillColor: root.backgroundColor + visible: root.clockStyle === "analog" + lobes: 12 + rotationDeg: -90 + lobeAmount: 0.075 + hillPower: 0.92 + roundness: 0.22 + paddingFrac: 0.02 + segments: 144 + } + + Loader { + anchors.fill: parent + anchors.margins: Theme.spacingM + sourceComponent: { + if (root.clockStyle === "analog") + return analogClock; + if (root.clockStyle === "stacked") + return stackedClock; + return digitalClock; + } + } + + Component { + id: analogClock + + Item { + id: analogRoot + + property real clockSize: Math.min(width, height) + property real centerX: width / 2 + property real centerY: height / 2 + property real faceRadius: clockSize / 2 - 12 + + property int hours: systemClock.date?.getHours() % 12 ?? 0 + property int minutes: systemClock.date?.getMinutes() ?? 0 + property int seconds: systemClock.date?.getSeconds() ?? 0 + + Repeater { + model: root.showAnalogNumbers ? 12 : 0 + + StyledText { + required property int index + property real angle: (index + 1) * 30 * Math.PI / 180 + property real numRadius: analogRoot.faceRadius + 10 + + x: analogRoot.centerX + numRadius * Math.sin(angle) - width / 2 + y: analogRoot.centerY - numRadius * Math.cos(angle) - height / 2 + text: index + 1 + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: root.accentColor + } + } + + Rectangle { + id: hourHand + property real angle: (analogRoot.hours + analogRoot.minutes / 60) * 30 + property real handWidth: Math.max(8, 12 * root.scaleFactor) + property real mainLength: analogRoot.faceRadius * 0.55 + property real tailLength: handWidth * 0.5 + + x: analogRoot.centerX - width / 2 + y: analogRoot.centerY - mainLength + width: handWidth + height: mainLength + tailLength + radius: width / 2 + color: root.handColor + antialiasing: true + + transform: Rotation { + origin.x: hourHand.width / 2 + origin.y: hourHand.mainLength + angle: hourHand.angle + } + } + + Rectangle { + id: minuteHand + property real angle: (analogRoot.minutes + analogRoot.seconds / 60) * 6 + property real mainLength: analogRoot.faceRadius * 0.75 + property real tailLength: hourHand.handWidth * 0.5 + + x: analogRoot.centerX - width / 2 + y: analogRoot.centerY - mainLength + width: hourHand.handWidth + height: mainLength + tailLength + radius: width / 2 + color: root.handColorDim + antialiasing: true + + transform: Rotation { + origin.x: minuteHand.width / 2 + origin.y: minuteHand.mainLength + angle: minuteHand.angle + } + } + + Rectangle { + id: secondDot + visible: root.showAnalogSeconds + + property real angle: analogRoot.seconds * 6 * Math.PI / 180 + property real orbitRadius: analogRoot.faceRadius * 0.92 + + x: analogRoot.centerX + orbitRadius * Math.sin(angle) - width / 2 + y: analogRoot.centerY - orbitRadius * Math.cos(angle) - height / 2 + width: Math.max(10, analogRoot.clockSize * 0.07) + height: width + radius: width / 2 + color: root.accentColor + + Behavior on x { + NumberAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + Behavior on y { + NumberAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + } + + StyledText { + id: dateText + visible: root.showDate + + property real hourAngle: (analogRoot.hours + analogRoot.minutes / 60) * 30 + property real minuteAngle: analogRoot.minutes * 6 + + property string bestPosition: { + const hRad = hourAngle * Math.PI / 180; + const mRad = minuteAngle * Math.PI / 180; + + const topWeight = Math.max(0, Math.cos(hRad)) + Math.max(0, Math.cos(mRad)); + const bottomWeight = Math.max(0, -Math.cos(hRad)) + Math.max(0, -Math.cos(mRad)); + const rightWeight = Math.max(0, Math.sin(hRad)) + Math.max(0, Math.sin(mRad)); + const leftWeight = Math.max(0, -Math.sin(hRad)) + Math.max(0, -Math.sin(mRad)); + + const minWeight = Math.min(topWeight, bottomWeight, leftWeight, rightWeight); + + if (minWeight === bottomWeight) + return "bottom"; + if (minWeight === topWeight) + return "top"; + if (minWeight === rightWeight) + return "right"; + return "left"; + } + + x: { + if (bestPosition === "left") + return analogRoot.centerX - analogRoot.faceRadius * 0.5 - width / 2; + if (bestPosition === "right") + return analogRoot.centerX + analogRoot.faceRadius * 0.5 - width / 2; + return analogRoot.centerX - width / 2; + } + y: { + if (bestPosition === "top") + return analogRoot.centerY - analogRoot.faceRadius * 0.5 - height / 2; + if (bestPosition === "bottom") + return analogRoot.centerY + analogRoot.faceRadius * 0.5 - height / 2; + return analogRoot.centerY - height / 2; + } + + text: { + if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) + return systemClock.date?.toLocaleDateString(I18n.locale(), SettingsData.clockDateFormat) ?? ""; + return systemClock.date?.toLocaleDateString(I18n.locale(), "ddd, MMM d") ?? ""; + } + font.pixelSize: Theme.fontSizeSmall + color: root.accentColor + + Behavior on x { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Theme.emphasizedEasing + } + } + Behavior on y { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Theme.emphasizedEasing + } + } + } + } + } + + Component { + id: digitalClock + + Item { + id: digitalRoot + + property bool hasDate: root.showDate + property bool hasAmPm: !SettingsData.use24HourClock + property real verticalScale: hasDate && hasAmPm ? 0.55 : (hasDate || hasAmPm ? 0.65 : 0.8) + property real baseSize: Math.min(height * verticalScale, width * 0.22) + property real digitWidth: baseSize * 0.62 + property real smallSize: baseSize * 0.35 + + property string hoursStr: { + const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12); + if (SettingsData.use24HourClock || SettingsData.padHours12Hour) + return String(hours).padStart(2, '0'); + return String(hours); + } + property string minutesStr: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0') + property string secondsStr: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0') + + Column { + anchors.centerIn: parent + spacing: 0 + + StyledText { + visible: root.showDate + anchors.horizontalCenter: parent.horizontalCenter + text: { + if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) + return systemClock.date?.toLocaleDateString(I18n.locale(), SettingsData.clockDateFormat) ?? ""; + return systemClock.date?.toLocaleDateString(I18n.locale(), "ddd, MMM d") ?? ""; + } + font.pixelSize: digitalRoot.smallSize + color: Theme.withAlpha(root.accentColor, 0.7) + } + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + StyledText { + visible: digitalRoot.hoursStr.length > 1 + width: digitalRoot.digitWidth + text: digitalRoot.hoursStr.charAt(0) + font.pixelSize: digitalRoot.baseSize + font.weight: Font.Medium + color: root.accentColor + horizontalAlignment: Text.AlignHCenter + } + StyledText { + width: digitalRoot.digitWidth + text: digitalRoot.hoursStr.length > 1 ? digitalRoot.hoursStr.charAt(1) : digitalRoot.hoursStr.charAt(0) + font.pixelSize: digitalRoot.baseSize + font.weight: Font.Medium + color: root.accentColor + horizontalAlignment: Text.AlignHCenter + } + StyledText { + text: ":" + font.pixelSize: digitalRoot.baseSize + font.weight: Font.Medium + color: root.accentColor + } + StyledText { + width: digitalRoot.digitWidth + text: digitalRoot.minutesStr.charAt(0) + font.pixelSize: digitalRoot.baseSize + font.weight: Font.Medium + color: root.accentColor + horizontalAlignment: Text.AlignHCenter + } + StyledText { + width: digitalRoot.digitWidth + text: digitalRoot.minutesStr.charAt(1) + font.pixelSize: digitalRoot.baseSize + font.weight: Font.Medium + color: root.accentColor + horizontalAlignment: Text.AlignHCenter + } + StyledText { + visible: root.showDigitalSeconds + text: ":" + font.pixelSize: digitalRoot.baseSize + font.weight: Font.Medium + color: Theme.withAlpha(root.accentColor, 0.7) + } + StyledText { + visible: root.showDigitalSeconds + width: digitalRoot.digitWidth + text: digitalRoot.secondsStr.charAt(0) + font.pixelSize: digitalRoot.baseSize + font.weight: Font.Medium + color: Theme.withAlpha(root.accentColor, 0.7) + horizontalAlignment: Text.AlignHCenter + } + StyledText { + visible: root.showDigitalSeconds + width: digitalRoot.digitWidth + text: digitalRoot.secondsStr.charAt(1) + font.pixelSize: digitalRoot.baseSize + font.weight: Font.Medium + color: Theme.withAlpha(root.accentColor, 0.7) + horizontalAlignment: Text.AlignHCenter + } + } + + StyledText { + visible: !SettingsData.use24HourClock + anchors.horizontalCenter: parent.horizontalCenter + text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM" + font.pixelSize: digitalRoot.smallSize + font.weight: Font.Medium + color: Theme.withAlpha(root.accentColor, 0.7) + } + } + } + } + + Component { + id: stackedClock + + Item { + id: stackedRoot + + property bool hasSeconds: root.showDigitalSeconds + property bool hasDate: root.showDate + property bool hasAmPm: !SettingsData.use24HourClock + property real extraContent: (hasSeconds ? 0.12 : 0) + (hasDate ? 0.08 : 0) + (hasAmPm ? 0.08 : 0) + property real baseSize: height * (0.42 - extraContent * 0.5) + property real digitWidth: baseSize * 0.58 + property real smallSize: baseSize * 0.5 + property real rowSpacing: -baseSize * 0.17 + + Column { + anchors.centerIn: parent + spacing: 0 + + Column { + spacing: stackedRoot.rowSpacing + anchors.horizontalCenter: parent.horizontalCenter + + Row { + spacing: 0 + anchors.horizontalCenter: parent.horizontalCenter + + StyledText { + text: { + if (SettingsData.use24HourClock) + return String(systemClock.date?.getHours() ?? 0).padStart(2, '0').charAt(0); + const hours = systemClock.date?.getHours() ?? 0; + const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours; + return String(display).padStart(2, '0').charAt(0); + } + font.pixelSize: stackedRoot.baseSize + font.weight: Font.Medium + color: root.accentColor + width: stackedRoot.digitWidth + horizontalAlignment: Text.AlignHCenter + } + + StyledText { + text: { + if (SettingsData.use24HourClock) + return String(systemClock.date?.getHours() ?? 0).padStart(2, '0').charAt(1); + const hours = systemClock.date?.getHours() ?? 0; + const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours; + return String(display).padStart(2, '0').charAt(1); + } + font.pixelSize: stackedRoot.baseSize + font.weight: Font.Medium + color: root.accentColor + width: stackedRoot.digitWidth + horizontalAlignment: Text.AlignHCenter + } + } + + Row { + spacing: 0 + anchors.horizontalCenter: parent.horizontalCenter + + StyledText { + text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0').charAt(0) + font.pixelSize: stackedRoot.baseSize + font.weight: Font.Medium + color: root.accentColor + width: stackedRoot.digitWidth + horizontalAlignment: Text.AlignHCenter + } + + StyledText { + text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0').charAt(1) + font.pixelSize: stackedRoot.baseSize + font.weight: Font.Medium + color: root.accentColor + width: stackedRoot.digitWidth + horizontalAlignment: Text.AlignHCenter + } + } + } + + Row { + visible: stackedRoot.hasSeconds + spacing: 0 + anchors.horizontalCenter: parent.horizontalCenter + + StyledText { + text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0').charAt(0) + font.pixelSize: stackedRoot.smallSize + font.weight: Font.Medium + color: Theme.withAlpha(root.accentColor, 0.7) + width: stackedRoot.smallSize * 0.58 + horizontalAlignment: Text.AlignHCenter + } + + StyledText { + text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0').charAt(1) + font.pixelSize: stackedRoot.smallSize + font.weight: Font.Medium + color: Theme.withAlpha(root.accentColor, 0.7) + width: stackedRoot.smallSize * 0.58 + horizontalAlignment: Text.AlignHCenter + } + } + + Item { + width: 1 + height: stackedRoot.baseSize * 0.1 + visible: stackedRoot.hasDate + } + + StyledText { + visible: stackedRoot.hasDate + anchors.horizontalCenter: parent.horizontalCenter + text: systemClock.date?.toLocaleDateString(I18n.locale(), "MMM dd") ?? "" + font.pixelSize: stackedRoot.smallSize * 0.7 + color: Theme.withAlpha(root.accentColor, 0.7) + } + + StyledText { + visible: stackedRoot.hasAmPm + anchors.horizontalCenter: parent.horizontalCenter + text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM" + font.pixelSize: stackedRoot.smallSize * 0.7 + font.weight: Font.Medium + color: Theme.withAlpha(root.accentColor, 0.7) + } + } + } + } +} diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins/OrganicBlobHourBulges.qml b/raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins/OrganicBlobHourBulges.qml new file mode 100644 index 0000000..cced1a4 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins/OrganicBlobHourBulges.qml @@ -0,0 +1,137 @@ +import QtQuick +import QtQuick.Shapes + +Item { + id: root + + property color fillColor: "transparent" + + property int lobes: 12 + property real lobeAmount: 0.070 + property real roundness: 0.22 + property real hillPower: 0.78 + property real paddingFrac: 0.020 + property real inset: 0 + property int segments: 120 + property real rotationDeg: -90 + + layer.enabled: true + layer.samples: 4 + + function sgn(v) { + return v < 0 ? -1 : 1; + } + function clamp(v, a, b) { + return Math.max(a, Math.min(b, v)); + } + + function squircleMap(u, v, p) { + const ax = sgn(u) * Math.pow(Math.abs(u), p); + const ay = sgn(v) * Math.pow(Math.abs(v), p); + return { + x: ax, + y: ay + }; + } + + function buildPathD(w, h) { + const x0 = inset, y0 = inset; + const x1 = w - inset, y1 = h - inset; + const iw = Math.max(2, x1 - x0); + const ih = Math.max(2, y1 - y0); + + const cx = x0 + iw * 0.5; + const cy = y0 + ih * 0.5; + + const rx = iw * 0.5; + const ry = ih * 0.5; + const rMin = Math.min(rx, ry); + + const amp = clamp(lobeAmount, 0.0, 0.14) * rMin; + const extraPad = paddingFrac * rMin + 1.5; + const pad = amp + extraPad; + + const rxBase = Math.max(2, rx - pad); + const ryBase = Math.max(2, ry - pad); + + const blend = clamp(roundness, 0.0, 0.45); + const squirclePow = 1.0 + blend * 2.8; + + const N = Math.max(48, segments); + const rot = rotationDeg * Math.PI / 180.0; + const dt = (Math.PI * 2.0) / N; + + function hillWave(a) { + const t = (Math.cos(lobes * a) + 1.0) * 0.5; + return Math.pow(t, hillPower); + } + + function P(t) { + const a = t + rot; + const u = Math.cos(a); + const v = Math.sin(a); + + const m = 1.0 + (amp / rMin) * hillWave(a); + + const ex = u * rxBase * m; + const ey = v * ryBase * m; + + const sm = squircleMap(u, v, 1.0 / squirclePow); + const sx = sm.x * rxBase * m; + const sy = sm.y * ryBase * m; + + const x = ex * (1.0 - blend) + sx * blend; + const y = ey * (1.0 - blend) + sy * blend; + return { + x: cx + x, + y: cy + y + }; + } + + function dP(t) { + const eps = dt * 0.25; + const p1 = P(t - eps); + const p2 = P(t + eps); + return { + x: (p2.x - p1.x) / (2 * eps), + y: (p2.y - p1.y) / (2 * eps) + }; + } + + const p0 = P(0.0); + let d = `M ${p0.x.toFixed(2)} ${p0.y.toFixed(2)} `; + + for (let i = 0; i < N; i++) { + const tA = i * dt; + const tB = (i + 1) * dt; + + const A = P(tA); + const B = P(tB); + const dA = dP(tA); + const dB = dP(tB); + + const c1x = A.x + (dt / 3.0) * dA.x; + const c1y = A.y + (dt / 3.0) * dA.y; + const c2x = B.x - (dt / 3.0) * dB.x; + const c2y = B.y - (dt / 3.0) * dB.y; + + d += `C ${c1x.toFixed(2)} ${c1y.toFixed(2)}, ${c2x.toFixed(2)} ${c2y.toFixed(2)}, ${B.x.toFixed(2)} ${B.y.toFixed(2)} `; + } + + d += "Z"; + return d; + } + + Shape { + anchors.fill: parent + + ShapePath { + fillColor: root.fillColor + strokeColor: "transparent" + + PathSvg { + path: root.buildPathD(root.width, root.height) + } + } + } +} diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml b/raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml new file mode 100644 index 0000000..515e74e --- /dev/null +++ b/raveos-hyprland-theme/theme-data/dms/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml @@ -0,0 +1,743 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Common +import qs.Services +import qs.Widgets + +Item { + id: root + + property real widgetWidth: 320 + property real widgetHeight: 480 + property real defaultWidth: 320 + property real defaultHeight: 480 + property real minWidth: { + const tileCount = enabledTiles.length; + if (tileCount === 0) + return 80; + if (tileCount === 1) + return 100; + return 160; + } + property real minHeight: { + const tileCount = enabledTiles.length; + if (tileCount === 0) + return 60; + if (tileCount === 1) + return 80; + if (tileCount <= 2) + return 120; + return 180; + } + + property string instanceId: "" + property var instanceData: null + + readonly property var cfg: instanceData?.config ?? null + readonly property bool isInstance: instanceId !== "" && cfg !== null + + property bool enabled: isInstance ? (instanceData?.enabled ?? true) : SettingsData.systemMonitorEnabled + property bool showHeader: isInstance ? (cfg.showHeader ?? true) : SettingsData.systemMonitorShowHeader + property real transparency: isInstance ? (cfg.transparency ?? 0.8) : SettingsData.systemMonitorTransparency + property string colorMode: isInstance ? (cfg.colorMode ?? "primary") : SettingsData.systemMonitorColorMode + property color customColor: isInstance ? (cfg.customColor ?? "#ffffff") : SettingsData.systemMonitorCustomColor + property bool showCpu: isInstance ? (cfg.showCpu ?? true) : SettingsData.systemMonitorShowCpu + property bool showCpuGraph: isInstance ? (cfg.showCpuGraph ?? true) : SettingsData.systemMonitorShowCpuGraph + property bool showCpuTemp: isInstance ? (cfg.showCpuTemp ?? true) : SettingsData.systemMonitorShowCpuTemp + property bool showGpuTemp: isInstance ? (cfg.showGpuTemp ?? false) : SettingsData.systemMonitorShowGpuTemp + property string selectedGpuPciId: isInstance ? (cfg.gpuPciId ?? "") : SettingsData.systemMonitorGpuPciId + property bool showMemory: isInstance ? (cfg.showMemory ?? true) : SettingsData.systemMonitorShowMemory + property bool showMemoryGraph: isInstance ? (cfg.showMemoryGraph ?? true) : SettingsData.systemMonitorShowMemoryGraph + property bool showNetwork: isInstance ? (cfg.showNetwork ?? true) : SettingsData.systemMonitorShowNetwork + property bool showNetworkGraph: isInstance ? (cfg.showNetworkGraph ?? true) : SettingsData.systemMonitorShowNetworkGraph + property bool showDisk: isInstance ? (cfg.showDisk ?? true) : SettingsData.systemMonitorShowDisk + property bool showTopProcesses: isInstance ? (cfg.showTopProcesses ?? false) : SettingsData.systemMonitorShowTopProcesses + property int topProcessCount: isInstance ? (cfg.topProcessCount ?? 3) : SettingsData.systemMonitorTopProcessCount + property string topProcessSortBy: isInstance ? (cfg.topProcessSortBy ?? "cpu") : SettingsData.systemMonitorTopProcessSortBy + property string layoutMode: isInstance ? (cfg.layoutMode ?? "auto") : SettingsData.systemMonitorLayoutMode + property int graphInterval: isInstance ? (cfg.graphInterval ?? 60) : SettingsData.systemMonitorGraphInterval + + readonly property color accentColor: { + switch (colorMode) { + case "secondary": + return Theme.secondary; + case "custom": + return customColor; + default: + return Theme.primary; + } + } + + readonly property color bgColor: Theme.withAlpha(Theme.surface, root.transparency) + readonly property color tileBg: Theme.withAlpha(Theme.surfaceContainerHigh, root.transparency) + readonly property color textColor: Theme.surfaceText + readonly property color dimColor: Theme.surfaceVariantText + + property string currentGpuPciIdRef: "" + property var activeModuleRefs: [] + + property var cpuHistory: [] + property var memHistory: [] + property var netRxHistory: [] + property var netTxHistory: [] + property var diskReadHistory: [] + property var diskWriteHistory: [] + readonly property int historySize: 60 + + readonly property int sampleInterval: { + switch (graphInterval) { + case 60: + return 1000; + case 300: + return 5000; + case 600: + return 10000; + case 900: + return 15000; + case 1800: + return 30000; + default: + return 1000; + } + } + + readonly property var enabledTiles: { + var tiles = []; + if (showCpu) + tiles.push("cpu"); + if (showMemory) + tiles.push("mem"); + if (showNetwork) + tiles.push("net"); + if (showDisk) + tiles.push("disk"); + if (showGpuTemp && selectedGpuPciId) + tiles.push("gpu"); + return tiles; + } + + readonly property var sortedProcesses: { + if (!showTopProcesses || !DgopService.processes) + return []; + var procs = DgopService.processes.slice(); + if (topProcessSortBy === "memory") { + procs.sort((a, b) => (b.memoryKB || 0) - (a.memoryKB || 0)); + } else { + procs.sort((a, b) => (b.cpu || 0) - (a.cpu || 0)); + } + return procs.slice(0, topProcessCount); + } + + Component.onCompleted: { + var modules = ["system"]; + if (showCpu || showCpuTemp || showCpuGraph) + modules.push("cpu"); + if (showMemory || showMemoryGraph) + modules.push("memory"); + if (showNetwork || showNetworkGraph) + modules.push("network"); + if (showDisk) + modules.push("disk", "diskmounts"); + if (showTopProcesses) + modules.push("processes"); + activeModuleRefs = modules; + DgopService.addRef(modules); + updateGpuRef(); + } + + Component.onDestruction: { + DgopService.removeRef(activeModuleRefs); + if (currentGpuPciIdRef) + DgopService.removeGpuPciId(currentGpuPciIdRef); + } + + onShowGpuTempChanged: updateGpuRef() + onSelectedGpuPciIdChanged: updateGpuRef() + onShowTopProcessesChanged: { + if (showTopProcesses) { + activeModuleRefs = activeModuleRefs.concat(["processes"]); + DgopService.addRef(["processes"]); + } else { + DgopService.removeRef(["processes"]); + activeModuleRefs = activeModuleRefs.filter(m => m !== "processes"); + } + } + + function updateGpuRef() { + if (currentGpuPciIdRef && currentGpuPciIdRef !== selectedGpuPciId) { + DgopService.removeGpuPciId(currentGpuPciIdRef); + currentGpuPciIdRef = ""; + } + if (!showGpuTemp || !selectedGpuPciId) { + if (currentGpuPciIdRef) { + DgopService.removeGpuPciId(currentGpuPciIdRef); + currentGpuPciIdRef = ""; + } + return; + } + if (selectedGpuPciId && !currentGpuPciIdRef) { + DgopService.addGpuPciId(selectedGpuPciId); + currentGpuPciIdRef = selectedGpuPciId; + } + } + + function getGpuInfo() { + if (!selectedGpuPciId || !DgopService.availableGpus) + return null; + return DgopService.availableGpus.find(g => g.pciId === selectedGpuPciId); + } + + function formatBytes(bytes) { + if (bytes < 1024) + return bytes.toFixed(0) + "B"; + if (bytes < 1024 * 1024) + return (bytes / 1024).toFixed(0) + "K"; + if (bytes < 1024 * 1024 * 1024) + return (bytes / (1024 * 1024)).toFixed(1) + "M"; + return (bytes / (1024 * 1024 * 1024)).toFixed(1) + "G"; + } + + function formatMemKB(kb) { + if (kb < 1024) + return kb.toFixed(0) + "K"; + if (kb < 1024 * 1024) + return (kb / 1024).toFixed(0) + "M"; + return (kb / (1024 * 1024)).toFixed(1) + "G"; + } + + function addToHistory(arr, val) { + var newArr = arr.slice(); + newArr.push(val); + if (newArr.length > historySize) + newArr.shift(); + return newArr; + } + + function sampleData() { + if (showCpuGraph) + cpuHistory = addToHistory(cpuHistory, DgopService.cpuUsage); + if (showMemoryGraph) + memHistory = addToHistory(memHistory, DgopService.memoryUsage); + if (showNetworkGraph) { + netRxHistory = addToHistory(netRxHistory, DgopService.networkRxRate); + netTxHistory = addToHistory(netTxHistory, DgopService.networkTxRate); + } + if (showDisk) { + diskReadHistory = addToHistory(diskReadHistory, DgopService.diskReadRate); + diskWriteHistory = addToHistory(diskWriteHistory, DgopService.diskWriteRate); + } + } + + readonly property int sampleSeconds: sampleInterval / 1000 + + SystemClock { + id: sampleClock + precision: SystemClock.Seconds + onDateChanged: { + var sec = date.getSeconds(); + if (sec % root.sampleSeconds === 0) + root.sampleData(); + } + } + + Rectangle { + anchors.fill: parent + radius: Theme.cornerRadius + color: root.bgColor + border.width: 0 + + ColumnLayout { + anchors.fill: parent + anchors.margins: (root.enabledTiles.length === 1 && !root.showHeader) ? 0 : Theme.spacingS + spacing: Theme.spacingS + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacingS + visible: root.showHeader + + ColumnLayout { + spacing: 0 + + StyledText { + text: DgopService.cpuModel || DgopService.hostname || "System" + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: root.textColor + elide: Text.ElideRight + Layout.maximumWidth: root.width - Theme.spacingM * 2 + } + + StyledText { + visible: DgopService.shortUptime && DgopService.shortUptime.length > 0 + text: DgopService.shortUptime + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: root.dimColor + } + } + } + + GridLayout { + id: tileGrid + Layout.fillWidth: true + Layout.fillHeight: true + columns: { + if (root.layoutMode === "list") + return 1; + if (root.layoutMode === "grid") + return 2; + // auto + if (root.width < 280) + return 1; + if (root.width < 500) + return 2; + return 3; + } + rowSpacing: Theme.spacingXS + columnSpacing: Theme.spacingXS + + Repeater { + model: root.enabledTiles + + Rectangle { + id: tile + readonly property int span: Layout.columnSpan + Layout.fillWidth: true + Layout.fillHeight: true + Layout.columnSpan: { + if (root.layoutMode === "list") + return 1; + + var cols = tileGrid.columns; + if (cols <= 1) + return 1; + + var count = root.enabledTiles.length; + var idx = index; + + if (idx !== count - 1) + return 1; + + var remainder = count % cols; + if (remainder === 0) + return 1; + + if (!tile.hasGraph) + return 1; + + return cols - remainder + 1; + } + Layout.minimumHeight: 60 + radius: Theme.cornerRadius - 2 + color: root.tileBg + border.width: 0 + clip: true + + readonly property string tileType: modelData + readonly property bool hasGraph: { + switch (tileType) { + case "cpu": + return root.showCpuGraph; + case "mem": + return root.showMemoryGraph; + case "net": + return root.showNetworkGraph; + case "disk": + return true; + default: + return false; + } + } + + Canvas { + id: tileGraph + anchors.fill: parent + visible: tile.hasGraph + renderStrategy: Canvas.Cooperative + + property var hist: { + switch (tile.tileType) { + case "cpu": + return root.cpuHistory; + case "mem": + return root.memHistory; + case "net": + return root.netRxHistory; + case "disk": + return root.diskReadHistory; + default: + return []; + } + } + property var hist2: { + switch (tile.tileType) { + case "net": + return root.netTxHistory; + case "disk": + return root.diskWriteHistory; + default: + return null; + } + } + + onHistChanged: requestPaint() + onHist2Changed: requestPaint() + onWidthChanged: requestPaint() + onHeightChanged: requestPaint() + + onPaint: { + var ctx = getContext("2d"); + ctx.reset(); + ctx.clearRect(0, 0, width, height); + if (!hist || hist.length < 2) + return; + var maxVal = 100; + if (tile.tileType === "net" || tile.tileType === "disk") { + maxVal = 1; + for (var k = 0; k < hist.length; k++) + maxVal = Math.max(maxVal, hist[k]); + if (hist2) + for (var l = 0; l < hist2.length; l++) + maxVal = Math.max(maxVal, hist2[l]); + } + + var c = root.accentColor; + var grad = ctx.createLinearGradient(0, 0, 0, height); + grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.3)); + grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.05)); + + ctx.fillStyle = grad; + ctx.beginPath(); + ctx.moveTo(0, height); + for (var i = 0; i < hist.length; i++) { + var x = (width / (root.historySize - 1)) * i; + var y = height - (hist[i] / maxVal) * height * 0.85; + ctx.lineTo(x, y); + } + ctx.lineTo((width / (root.historySize - 1)) * (hist.length - 1), height); + ctx.closePath(); + ctx.fill(); + + ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.6); + ctx.lineWidth = 1.5; + ctx.beginPath(); + for (var j = 0; j < hist.length; j++) { + var px = (width / (root.historySize - 1)) * j; + var py = height - (hist[j] / maxVal) * height * 0.85; + j === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py); + } + ctx.stroke(); + + if (hist2 && hist2.length >= 2) { + ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.3); + ctx.lineWidth = 1; + ctx.beginPath(); + for (var m = 0; m < hist2.length; m++) { + var sx = (width / (root.historySize - 1)) * m; + var sy = height - (hist2[m] / maxVal) * height * 0.85; + m === 0 ? ctx.moveTo(sx, sy) : ctx.lineTo(sx, sy); + } + ctx.stroke(); + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Theme.spacingS + spacing: 2 + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacingXS + + StyledText { + text: tile.tileType.toUpperCase() + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Bold + color: root.accentColor + } + + Item { + Layout.fillWidth: true + } + + StyledText { + visible: tile.tileType === "cpu" && root.showCpuTemp && DgopService.cpuTemperature > 0 + text: DgopService.cpuTemperature.toFixed(0) + "°" + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: DgopService.cpuTemperature > 80 ? Theme.error : (DgopService.cpuTemperature > 60 ? Theme.warning : root.dimColor) + } + + StyledText { + visible: tile.tileType === "mem" + text: DgopService.formatSystemMemory(DgopService.usedMemoryKB) + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: root.dimColor + } + } + + Item { + Layout.fillHeight: true + } + + StyledText { + visible: tile.tileType === "cpu" + text: DgopService.cpuUsage.toFixed(0) + "%" + isMonospace: true + font.pixelSize: Theme.fontSizeXLarge + font.weight: Font.Medium + color: root.textColor + } + + StyledText { + visible: tile.tileType === "mem" + text: DgopService.memoryUsage.toFixed(0) + "%" + isMonospace: true + font.pixelSize: Theme.fontSizeXLarge + font.weight: Font.Medium + color: root.textColor + } + + RowLayout { + visible: tile.tileType === "net" + spacing: Theme.spacingM + ColumnLayout { + spacing: 0 + StyledText { + text: "↓" + font.pixelSize: Theme.fontSizeSmall + color: root.accentColor + } + StyledText { + text: root.formatBytes(DgopService.networkRxRate) + "/s" + isMonospace: true + font.pixelSize: Theme.fontSizeMedium + color: root.textColor + } + } + ColumnLayout { + spacing: 0 + StyledText { + text: "↑" + font.pixelSize: Theme.fontSizeSmall + color: root.dimColor + } + StyledText { + text: root.formatBytes(DgopService.networkTxRate) + "/s" + isMonospace: true + font.pixelSize: Theme.fontSizeMedium + color: root.textColor + } + } + } + + RowLayout { + visible: tile.tileType === "disk" + spacing: Theme.spacingM + ColumnLayout { + spacing: 0 + StyledText { + text: "R" + font.pixelSize: Theme.fontSizeSmall + color: root.accentColor + } + StyledText { + text: root.formatBytes(DgopService.diskReadRate) + "/s" + isMonospace: true + font.pixelSize: Theme.fontSizeMedium + color: root.textColor + } + } + ColumnLayout { + spacing: 0 + StyledText { + text: "W" + font.pixelSize: Theme.fontSizeSmall + color: root.dimColor + } + StyledText { + text: root.formatBytes(DgopService.diskWriteRate) + "/s" + isMonospace: true + font.pixelSize: Theme.fontSizeMedium + color: root.textColor + } + } + } + + ColumnLayout { + visible: tile.tileType === "gpu" + spacing: 0 + property var gpu: root.getGpuInfo() + Layout.alignment: tile.span > 1 ? Qt.AlignHCenter : Qt.AlignLeft + + StyledText { + property real temp: parent.gpu?.temperature ?? 0 + text: temp > 0 ? temp.toFixed(0) + "°C" : "--" + isMonospace: true + font.pixelSize: Theme.fontSizeXLarge + font.weight: Font.Medium + color: root.textColor + Layout.alignment: tile.span > 1 ? Qt.AlignHCenter : Qt.AlignLeft + } + StyledText { + text: parent.gpu?.displayName ?? "" + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: root.dimColor + Layout.fillWidth: true + horizontalAlignment: tile.span > 1 ? Text.AlignHCenter : Text.AlignLeft + elide: Text.ElideRight + } + } + + Rectangle { + visible: tile.tileType === "cpu" || tile.tileType === "mem" + Layout.fillWidth: true + height: 4 + radius: 2 + color: Theme.withAlpha(Theme.outline, 0.2) + + Rectangle { + property real pct: tile.tileType === "cpu" ? DgopService.cpuUsage / 100 : DgopService.memoryUsage / 100 + width: parent.width * Math.min(1, pct) + height: parent.height + radius: 2 + color: pct > 0.8 ? Theme.error : (pct > 0.6 ? Theme.warning : root.accentColor) + Behavior on width { + NumberAnimation { + duration: 150 + } + } + } + } + } + } + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Theme.spacingXS + visible: root.showTopProcesses && root.sortedProcesses.length > 0 + + Rectangle { + Layout.fillWidth: true + height: 1 + color: Theme.withAlpha(Theme.outline, 0.15) + } + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacingXS + + StyledText { + text: "TOP BY " + root.topProcessSortBy.toUpperCase() + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Bold + color: root.accentColor + } + + Item { + Layout.fillWidth: true + } + + StyledText { + text: root.topProcessSortBy === "cpu" ? "CPU" : "MEM" + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: root.dimColor + Layout.preferredWidth: 48 + horizontalAlignment: Text.AlignRight + } + } + + Repeater { + model: root.sortedProcesses + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacingXS + + StyledText { + text: modelData.command || "unknown" + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: root.textColor + Layout.fillWidth: true + elide: Text.ElideRight + } + + StyledText { + text: root.topProcessSortBy === "cpu" ? (modelData.cpu || 0).toFixed(1) + "%" : root.formatMemKB(modelData.memoryKB || 0) + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: root.dimColor + Layout.preferredWidth: 48 + horizontalAlignment: Text.AlignRight + } + } + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Theme.spacingXS + visible: root.showDisk && DgopService.diskMounts.length > 0 + + Rectangle { + Layout.fillWidth: true + height: 1 + color: Theme.withAlpha(Theme.outline, 0.15) + } + + Repeater { + model: DgopService.diskMounts.filter(m => m.mountpoint === "/" || m.mountpoint === "/home") + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacingXS + + StyledText { + text: modelData.mountpoint + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: root.dimColor + Layout.preferredWidth: 48 + } + + Rectangle { + Layout.fillWidth: true + height: 4 + radius: 2 + color: Theme.withAlpha(Theme.outline, 0.2) + + Rectangle { + property real pct: (modelData.used || 0) / Math.max(1, modelData.total || 1) + width: parent.width * pct + height: parent.height + radius: 2 + color: pct > 0.9 ? Theme.error : (pct > 0.75 ? Theme.warning : root.accentColor) + } + } + + StyledText { + text: ((modelData.used || 0) / (modelData.total || 1) * 100).toFixed(0) + "%" + isMonospace: true + font.pixelSize: Theme.fontSizeSmall + color: root.textColor + Layout.preferredWidth: 32 + horizontalAlignment: Text.AlignRight + } + } + } + } + } + } +} |