From 4626e72219ffd194cce896cdc6e0f2d7b124ab86 Mon Sep 17 00:00:00 2001 From: rope Date: Tue, 4 Aug 2026 08:30:06 +0100 Subject: [PATCH] quickshell: add a pixel worm and middle-click-to-knock-cards-off Co-Authored-By: Claude Opus 5 --- settings/quickshell.nix | 165 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 6fe7555..3ca5521 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -374,6 +374,7 @@ in Bar 1.0 Bar.qml Lock 1.0 Lock.qml Cheatsheet 1.0 Cheatsheet.qml + Weird 1.0 Weird.qml ''; }; @@ -827,6 +828,164 @@ in ''; }; + "quickshell/Weird.qml" = { + onChange = qsRestart; + text = '' + import QtQuick + + // Decorative only. A pixel worm wanders the screen, and + // middle-clicking any Card tips it out of its panel to land in a + // pile at the bottom. Nothing here is load-bearing: delete the + // file, its qmldir line, the objectName on Bar's Card component + // and the one instantiation at the end of Bar.qml. + Item { + id: weird + anchors.fill: parent + + // Pixel grid. Every worm segment snaps to it, which IS the + // pixel-art look — there are no sprites involved. + readonly property int cell: 6 + readonly property int segments: 10 + readonly property int lag: 3 // ticks of trail per segment + + // ── Worm ──────────────────────────────────────────────── + QtObject { + id: worm + property real hx: weird.width / 2 + property real hy: weird.height / 2 + property real dir: Math.random() * 2 * Math.PI + property var trail: [] + } + + Timer { + interval: 33; running: true; repeat: true + onTriggered: { + worm.dir += (Math.random() - 0.5) * 0.45; + let m = weird.cell * 3; + let nx = worm.hx + Math.cos(worm.dir) * 2.4; + let ny = worm.hy + Math.sin(worm.dir) * 2.4; + // Reflect the heading at the edges. Clamping alone + // leaves it grinding along the border forever. + if (nx < m || nx > weird.width - m) { worm.dir = Math.PI - worm.dir; nx = worm.hx; } + if (ny < m || ny > weird.height - m) { worm.dir = -worm.dir; ny = worm.hy; } + worm.hx = nx; worm.hy = ny; + let t = worm.trail; + t.unshift(Qt.point(nx, ny)); + if (t.length > weird.segments * weird.lag) t.pop(); + worm.trail = t; // reassign: arrays don't notify on mutation + } + } + + Repeater { + model: weird.segments + Rectangle { + id: seg + required property int index + readonly property var p: worm.trail[index * weird.lag] + readonly property int size: weird.cell * (index < weird.segments - 3 ? 2 : 1) + visible: p !== undefined + width: size; height: size + // Snap to the grid — sub-pixel motion would sand the + // pixels back off. + x: p ? Math.round((p.x - size / 2) / weird.cell) * weird.cell : 0 + y: p ? Math.round((p.y - size / 2) / weird.cell) * weird.cell : 0 + color: Theme.base0D + opacity: 1 - index * 0.05 + antialiasing: false + + Row { // eyes, head segment only + visible: seg.index === 0 + anchors.centerIn: parent + spacing: 4 + Repeater { model: 2; Rectangle { width: 2; height: 2; color: Theme.base00 } } + } + } + } + + // ── Knocked-off cards ─────────────────────────────────── + // hideSource pulls the panel's own copy out of the render + // pass without touching a single one of its bindings, so + // destroying the chunk puts the card straight back. + property var pile: [] + property var resetOn: null // bind to whatever sweeps the pile + onResetOnChanged: sweep() + + function knock(card) { + for (let d of pile) if (d.card === card) return; + let p = card.mapToItem(weird, 0, 0); + let c = chunk.createObject(weird, { card: card, x: p.x, y: p.y }); + pile.push(c); + c.drop(); + } + + function sweep() { + for (let d of pile) d.destroy(); + pile = []; + } + + Component { + id: chunk + ShaderEffectSource { + id: bit + property Item card: null + sourceItem: card + hideSource: true + width: card ? card.width : 0 + height: card ? card.height : 0 + // Deliberately under-resolved so the card lands as + // chunky pixels like the worm. Raise the divisor for + // a cleaner-looking chunk. + textureSize: Qt.size(width / 3, height / 3) + smooth: false + transformOrigin: Item.Center + + function drop() { + fallX.to = Math.max(Theme.barWidth, Math.min(weird.width - width, x + (Math.random() - 0.5) * 240)); + fallY.to = weird.height - height - Theme.frameWidth; + spin.to = (Math.random() - 0.5) * 60; + fall.start(); + } + + ParallelAnimation { + id: fall + NumberAnimation { id: fallY; target: bit; property: "y"; duration: 1100; easing.type: Easing.OutBounce } + NumberAnimation { id: fallX; target: bit; property: "x"; duration: 1100; easing.type: Easing.OutQuad } + NumberAnimation { id: spin; target: bit; property: "rotation"; duration: 1100; easing.type: Easing.OutQuad } + } + } + } + + // Sits above every panel, so it sees the press first. A press + // that is not on a card is handed straight back — the volume + // widget's own middle-click still mutes. + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.MiddleButton + onPressed: (mouse) => { + let c = weird.cardAt(weird.parent, mouse.x, mouse.y); + if (c) weird.knock(c); else mouse.accepted = false; + } + } + + // Deepest item tagged objectName "card" under the point. + // childAt() is no use: this overlay is the topmost child of + // the window, so it would only ever find itself. + function cardAt(item, x, y) { + for (let i = item.children.length - 1; i >= 0; i--) { + let c = item.children[i]; + if (c === weird || !c.visible) continue; + let p = item.mapToItem(c, x, y); + if (p.x < 0 || p.y < 0 || p.x > c.width || p.y > c.height) continue; + let hit = cardAt(c, p.x, p.y); + if (hit) return hit; + if (c.objectName === "card") return c; + } + return null; + } + } + ''; + }; + "quickshell/Commands.qml" = { onChange = qsRestart; text = '' @@ -1262,6 +1421,8 @@ in component Card: Rectangle { default property alias cardData: _cardCol.data property alias cardSpacing: _cardCol.spacing + // Weird.qml hit-tests for this tag on middle-click. + objectName: "card" radius: Theme.radiusCard color: Theme.cardBg implicitHeight: _cardCol.height + 2 * Theme.cardPad @@ -6467,6 +6628,10 @@ in } } } + + // Last child = on top of every panel, which is what the + // card hit-test needs. Purely decorative, see Weird.qml. + Weird { resetOn: bar.activeDropdown } } ''; };