diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 22c82ed..d834457 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -833,10 +833,9 @@ in 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 + // Decorative only: a pixel inchworm loops around the screen edge + // and gets shoved about by the panels sliding out of the bar. + // Nothing here is load-bearing — delete the file, its qmldir line // and the one instantiation at the end of Bar.qml. Item { id: weird @@ -845,7 +844,7 @@ in // 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: 9 + readonly property int segments: 11 // ── Inchworm gait ─────────────────────────────────────── // A looper does not glide: it plants its tail, arches the @@ -859,6 +858,12 @@ in readonly property real spanMax: 66 readonly property real crawl: 2.2 // px per tick, whichever end is moving readonly property real archMax: 26 // hoop height at full compression + // The hoop is only his middle. The face carries on ahead of + // it and the tail trails behind, both flat on the rail, so + // what you read is a body pumping between two planted ends + // rather than the whole animal rippling. + readonly property real headLead: 16 + readonly property real tailDrag: 16 // ── The rail ──────────────────────────────────────────── // The worm rides the chrome cutout: the exact rounded rect @@ -923,14 +928,24 @@ in // ── Worm ──────────────────────────────────────────────── QtObject { id: worm - property real tailD: Math.random() * 4000 // rail distance - property real headD: tailD + weird.spanMin - property bool reaching: true // head moving, tail planted + property real backD: Math.random() * 4000 // rail distance + property real frontD: backD + weird.spanMin + property real step: weird.crawl // sign = direction of travel + property bool reaching: true // front moving, back planted property bool loose: false // shoved off, ballistic property real vx: 0 property real vy: 0 } + // Turn around: the two ends swap roles, so the face leads in + // the new direction instead of the worm moonwalking. + function reverse() { + let f = worm.frontD; + worm.frontD = worm.backD; + worm.backD = f; + worm.step = -worm.step; + } + // Every segment's position, rebuilt each tick. One array // instead of per-segment bindings: the gait and the ballistic // flight are different enough that computing them in the tick @@ -939,18 +954,26 @@ in property var body: [] // Body laid along the rail, hooped by however much slack the - // current compression leaves. index 0 is the head. + // current compression leaves. index 0 is the head. `span` is + // signed, so everything below works crawling either way. function railBody() { - let span = worm.headD - worm.tailD; - let squash = (spanMax - span) / (spanMax - spanMin); + let span = worm.frontD - worm.backD; + let dir = span >= 0 ? 1 : -1; + let squash = (spanMax - Math.abs(span)) / (spanMax - spanMin); + // He reaches past the hoop at both ends. + let from = worm.backD - dir * tailDrag; + let to = worm.frontD + dir * headLead; let out = []; for (let i = 0; i < segments; i++) { let u = 1 - i / (segments - 1); // 1 at head, 0 at tail - let d = worm.tailD + u * span; + let d = from + u * (to - from); let base = railPoint(d); - // sin() pins both ends to the surface and lifts the - // middle: the feet stay planted, the back arches. - let lift = archMax * squash * Math.sin(Math.PI * u); + // Lift only across the arching middle: sin() pins the + // hoop's own two ends down, and beyond them the neck + // and tail lie flat on the rail. + let v = (d - worm.backD) / span; + let lift = v > 0 && v < 1 + ? archMax * squash * Math.sin(Math.PI * v) : 0; let n = railNormal(d); out.push(Qt.point(base.x + n.x * lift, base.y + n.y * lift)); } @@ -976,8 +999,8 @@ in if (worm.vy > 3) { worm.vy = -worm.vy * 0.45; worm.vx *= 0.7; } else { worm.loose = false; - worm.tailD = railAtBottom(hx); - worm.headD = worm.tailD + spanMin; + worm.backD = railAtBottom(hx); + worm.frontD = worm.backD + Math.sign(worm.step) * spanMin; worm.reaching = true; } } @@ -991,14 +1014,14 @@ in body = railBody(); } } else { - // Take turns: throw the head out to full stretch, - // then drag the tail up to it. + // Take turns: throw the front out to full stretch, + // then drag the back up to it. if (worm.reaching) { - worm.headD += crawl; - if (worm.headD - worm.tailD >= spanMax) worm.reaching = false; + worm.frontD += worm.step; + if (Math.abs(worm.frontD - worm.backD) >= spanMax) worm.reaching = false; } else { - worm.tailD += crawl; - if (worm.headD - worm.tailD <= spanMin) worm.reaching = true; + worm.backD += worm.step; + if (Math.abs(worm.frontD - worm.backD) <= spanMin) worm.reaching = true; } body = railBody(); checkShove(); @@ -1044,12 +1067,16 @@ in property var pushers: [] property var prevRects: [] + function inRect(p, r) { + return Math.abs(p.x - r.x) <= r.z + 6 && Math.abs(p.y - r.y) <= r.w + 6; + } + function checkShove() { - let hx = body[0].x, hy = body[0].y; + let head = body[0], tail = body[segments - 1]; for (let i = 0; i < pushers.length; i++) { let r = pushers[i], q = prevRects[i]; if (!r || r.z < 0.5) continue; - if (Math.abs(hx - r.x) > r.z + 6 || Math.abs(hy - r.y) > r.w + 6) continue; + if (!inRect(head, r)) continue; if (!q || q.z < 0.5) { q = r; } // appeared already overlapping him // Only an edge that is travelling outward carries him, // and it carries him the way it is going: the dropdown @@ -1057,7 +1084,18 @@ in // up out of the floor. A panel closing just lets go. let dx = Math.max(0, (r.x + r.z) - (q.x + q.z)) + Math.min(0, (r.x - r.z) - (q.x - q.z)); let dy = Math.max(0, (r.y + r.w) - (q.y + q.w)) + Math.min(0, (r.y - r.w) - (q.y - q.w)); - if (Math.abs(dx) < 1 && Math.abs(dy) < 1) continue; + // Sitting still, it is simply solid: he walks into the + // panel and turns around rather than crawling through + // it, which is what made an open dropdown feel like it + // was not there at all. Only turn him when he is + // ENTERING — head in, tail still out. Whole body + // inside (he landed there) means walking on is the way + // out, and reversing on that would just buzz him in + // place against both edges. + if (Math.abs(dx) < 1 && Math.abs(dy) < 1) { + if (!inRect(tail, r)) reverse(); + return; + } worm.loose = true; // Clamped: the panel's first frame of OutExpo covers // most of its width, which would fire him off screen. @@ -1066,87 +1104,6 @@ in return; } } - - // ── 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; - } } ''; }; @@ -1586,8 +1543,6 @@ 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 @@ -6795,14 +6750,11 @@ 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 - // The same rects the frame shader draws, so the worm is - // swept by the panel outline itself as it grows. - pushers: [chromeSdf.panel, chromeSdf.launcher] - } + // Last child, so he crawls over the panels rather than under + // them. Purely decorative, see Weird.qml. The pushers are the + // same rects the frame shader draws, so what shoves him is the + // panel outline itself as it grows. + Weird { pushers: [chromeSdf.panel, chromeSdf.launcher] } } ''; };