From f690a42c52fe5f3e1a95803ad06bb6a1e643c731 Mon Sep 17 00:00:00 2001 From: Damocles Date: Sun, 12 Apr 2026 23:28:11 +0200 Subject: [PATCH] overview backdrop: cyberpunk neon clock, scan lines, hex grid, system stats --- modules/OverviewBackdrop.qml | 395 ++++++++++++++++++++++++++++++++--- 1 file changed, 364 insertions(+), 31 deletions(-) diff --git a/modules/OverviewBackdrop.qml b/modules/OverviewBackdrop.qml index fdb3c0f..51c84ac 100644 --- a/modules/OverviewBackdrop.qml +++ b/modules/OverviewBackdrop.qml @@ -1,10 +1,10 @@ import QtQuick +import QtQuick.Effects import Quickshell +import Quickshell.Io import Quickshell.Wayland import "." as M -// Rendered in niri's overview backdrop via place-within-backdrop layer rule. -// Visible between workspace rows during overview. PanelWindow { id: root @@ -22,57 +22,390 @@ PanelWindow { anchors.right: true anchors.bottom: true - // Test pattern — diagonal gradient with centered label + SystemClock { + id: clock + precision: SystemClock.Seconds + } + + // Slow drifting gradient background Rectangle { anchors.fill: parent + property real _phase: 0 + NumberAnimation on _phase { + from: 0 + to: 360 + duration: 30000 + loops: Animation.Infinite + } gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0 - color: Qt.rgba(M.Theme.base0C.r, M.Theme.base0C.g, M.Theme.base0C.b, 0.15) + color: Qt.rgba(M.Theme.base0C.r, M.Theme.base0C.g, M.Theme.base0C.b, 0.06) } GradientStop { position: 0.5 - color: Qt.rgba(M.Theme.base0E.r, M.Theme.base0E.g, M.Theme.base0E.b, 0.1) + color: Qt.rgba(M.Theme.base0E.r, M.Theme.base0E.g, M.Theme.base0E.b, 0.04) } GradientStop { position: 1 - color: Qt.rgba(M.Theme.base09.r, M.Theme.base09.g, M.Theme.base09.b, 0.15) + color: Qt.rgba(M.Theme.base09.r, M.Theme.base09.g, M.Theme.base09.b, 0.06) } } } - // Grid of dots as a test pattern - Grid { - anchors.fill: parent - columns: Math.floor(width / 80) - rows: Math.floor(height / 80) + // Scanning line — slow horizontal sweep + Rectangle { + id: scanLine + width: 2 + height: parent.height + opacity: 0.08 + color: M.Theme.base0D - Repeater { - model: parent.columns * parent.rows - Rectangle { - width: 80 - height: 80 - color: "transparent" - Rectangle { - anchors.centerIn: parent - width: 4 - height: 4 - radius: 2 - color: M.Theme.base0D - opacity: 0.2 + NumberAnimation on x { + from: 0 + to: root.width + duration: 8000 + loops: Animation.Infinite + easing.type: Easing.InOutSine + } + + // Glow trail + Rectangle { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.left + width: 60 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { + position: 0 + color: "transparent" + } + GradientStop { + position: 1 + color: Qt.rgba(M.Theme.base0D.r, M.Theme.base0D.g, M.Theme.base0D.b, 0.04) } } } } - Text { + // Vertical scan line — slower + Rectangle { + id: vScanLine + width: parent.width + height: 1 + opacity: 0.06 + color: M.Theme.base0E + + NumberAnimation on y { + from: 0 + to: root.height + duration: 12000 + loops: Animation.Infinite + easing.type: Easing.InOutSine + } + } + + // Floating hex grid + Canvas { + anchors.fill: parent + opacity: 0.04 + + onPaint: { + const ctx = getContext("2d"); + const w = width, h = height; + ctx.clearRect(0, 0, w, h); + ctx.strokeStyle = M.Theme.base05.toString(); + ctx.lineWidth = 0.5; + + const size = 40; + const dx = size * 1.5; + const dy = size * Math.sqrt(3); + + for (let col = -1; col < w / dx + 1; col++) { + for (let row = -1; row < h / dy + 1; row++) { + const cx = col * dx; + const cy = row * dy + (col % 2 ? dy / 2 : 0); + ctx.beginPath(); + for (let i = 0; i < 6; i++) { + const angle = Math.PI / 3 * i - Math.PI / 6; + const px = cx + size * 0.5 * Math.cos(angle); + const py = cy + size * 0.5 * Math.sin(angle); + if (i === 0) + ctx.moveTo(px, py); + else + ctx.lineTo(px, py); + } + ctx.closePath(); + ctx.stroke(); + } + } + } + } + + // Center content + Column { anchors.centerIn: parent - text: "nova-shell overview backdrop" - color: M.Theme.base05 - opacity: 0.3 - font.pixelSize: 24 - font.family: M.Theme.fontFamily - font.letterSpacing: 6 + spacing: 20 + + // Large neon clock + Item { + anchors.horizontalCenter: parent.horizontalCenter + width: clockRow.width + height: clockRow.height + + Row { + id: glowSrc + visible: false + anchors.centerIn: parent + Text { + text: Qt.formatDateTime(clock.date, "HH") + color: M.Theme.base0D + font.pixelSize: 96 + font.family: M.Theme.fontFamily + font.bold: true + anchors.verticalCenter: parent.verticalCenter + } + Text { + text: ":" + color: _colon._colors[_colon._colorIdx % _colon._colors.length] + font.pixelSize: 96 + font.family: M.Theme.fontFamily + font.bold: true + opacity: _colon.opacity + anchors.verticalCenter: parent.verticalCenter + Behavior on color { + ColorAnimation { + duration: 800 + } + } + } + Text { + text: Qt.formatDateTime(clock.date, "mm") + color: M.Theme.base0E + font.pixelSize: 96 + font.family: M.Theme.fontFamily + font.bold: true + anchors.verticalCenter: parent.verticalCenter + } + } + + MultiEffect { + source: glowSrc + anchors.fill: glowSrc + shadowEnabled: true + shadowColor: M.Theme.base0D + shadowBlur: 1.0 + shadowVerticalOffset: 0 + shadowHorizontalOffset: 0 + } + + Row { + id: clockRow + anchors.centerIn: parent + Text { + text: Qt.formatDateTime(clock.date, "HH") + color: M.Theme.base0D + opacity: 0.9 + font.pixelSize: 96 + font.family: M.Theme.fontFamily + font.bold: true + anchors.verticalCenter: parent.verticalCenter + } + Text { + id: _colon + text: ":" + font.pixelSize: 96 + font.family: M.Theme.fontFamily + font.bold: true + anchors.verticalCenter: parent.verticalCenter + property int _colorIdx: 0 + readonly property var _colors: [M.Theme.base08, M.Theme.base09, M.Theme.base0A, M.Theme.base0B, M.Theme.base0C, M.Theme.base0D, M.Theme.base0E, M.Theme.base05] + color: _colors[_colorIdx % _colors.length] + Behavior on color { + ColorAnimation { + duration: 800 + } + } + SequentialAnimation { + loops: Animation.Infinite + running: true + NumberAnimation { + target: _colon + property: "opacity" + to: 0.1 + duration: 1000 + easing.type: Easing.InOutSine + } + ScriptAction { + script: _colon._colorIdx++ + } + NumberAnimation { + target: _colon + property: "opacity" + to: 0.9 + duration: 1000 + easing.type: Easing.InOutSine + } + } + } + Text { + text: Qt.formatDateTime(clock.date, "mm") + color: M.Theme.base0E + opacity: 0.9 + font.pixelSize: 96 + font.family: M.Theme.fontFamily + font.bold: true + anchors.verticalCenter: parent.verticalCenter + } + } + } + + // Seconds bar + Item { + anchors.horizontalCenter: parent.horizontalCenter + width: clockRow.width + height: 3 + Rectangle { + anchors.fill: parent + color: M.Theme.base02 + radius: 1 + opacity: 0.2 + } + Rectangle { + width: parent.width * (clock.date.getSeconds() / 59) + height: parent.height + radius: 1 + opacity: 0.6 + color: _colon._colors[_colon._colorIdx % _colon._colors.length] + Behavior on color { + ColorAnimation { + duration: 800 + } + } + Behavior on width { + NumberAnimation { + duration: 300 + } + } + } + } + + // Date + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: Qt.formatDateTime(clock.date, "dddd, dd MMMM yyyy") + color: M.Theme.base05 + opacity: 0.4 + font.pixelSize: 20 + font.family: M.Theme.fontFamily + font.letterSpacing: 6 + layer.enabled: true + layer.effect: MultiEffect { + shadowEnabled: true + shadowColor: M.Theme.base0D + shadowBlur: 0.3 + shadowVerticalOffset: 0 + shadowHorizontalOffset: 0 + } + } + + // System stats row + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 30 + + Repeater { + model: [ + { + label: "CPU", + file: "/proc/stat" + }, + { + label: "MEM", + file: "/proc/meminfo" + }, + { + label: "TMP", + file: "/sys/class/thermal/thermal_zone0/temp" + } + ] + + delegate: Column { + required property var modelData + required property int index + spacing: 4 + property int _val: 0 + + readonly property var _accent: [M.Theme.base08, M.Theme.base0B, M.Theme.base09][index] + + Timer { + interval: 2000 + running: true + repeat: true + triggeredOnStart: true + onTriggered: _reader.running = true + } + + Process { + id: _reader + command: ["sh", "-c", { + "CPU": "awk '/^cpu /{u=$2+$4; t=$2+$3+$4+$5+$6+$7+$8; print int(u*100/t)}' /proc/stat", + "MEM": "awk '/MemTotal/{t=$2}/MemAvailable/{a=$2}END{print int((t-a)*100/t)}' /proc/meminfo", + "TMP": "awk '{print int($1/1000)}' /sys/class/thermal/thermal_zone0/temp" + }[modelData.label]] + stdout: StdioCollector { + onStreamFinished: parent.parent._val = parseInt(text) || 0 + } + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: parent._val + (parent.modelData.label === "TMP" ? "°" : "%") + color: parent._accent + opacity: 0.7 + font.pixelSize: 18 + font.family: M.Theme.fontFamily + font.bold: true + } + + // Mini bar + Item { + anchors.horizontalCenter: parent.horizontalCenter + width: 50 + height: 3 + Rectangle { + anchors.fill: parent + color: M.Theme.base02 + radius: 1 + opacity: 0.15 + } + Rectangle { + width: parent.width * Math.min(1, parent.parent._val / 100) + height: parent.height + radius: 1 + opacity: 0.5 + color: parent.parent._accent + Behavior on width { + NumberAnimation { + duration: 500 + } + } + } + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: parent.modelData.label + color: M.Theme.base04 + opacity: 0.4 + font.pixelSize: 10 + font.family: M.Theme.fontFamily + font.letterSpacing: 3 + } + } + } + } } }