quickshell: worm rides the frame rail, falling cards knock him off it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-08-04 10:01:49 +01:00
parent 535fe56b76
commit 3eb1d242fa

View file

@ -848,29 +848,106 @@ in
readonly property int segments: 10
readonly property int lag: 3 // ticks of trail per segment
// The rail
// The worm rides the chrome cutout: the exact rounded rect
// the frame shader carves out of the shell, so he crawls the
// real screen edge, corners and all. Keep these in step with
// the ShaderEffect's `cutout` in Bar.qml.
readonly property real railL: Theme.barWidth
readonly property real railT: Theme.frameWidth
readonly property real railR: weird.width - Theme.frameWidth
readonly property real railB: weird.height - Theme.frameWidth
readonly property real rad: Math.min(Theme.radius, (railR - railL) / 2, (railB - railT) / 2)
readonly property real runX: railR - railL - 2 * rad // straight top/bottom run
readonly property real runY: railB - railT - 2 * rad // straight left/right run
readonly property real arc: Math.PI * rad / 2
readonly property real perim: 2 * (runX + runY) + 4 * arc
// Point at distance d clockwise along that outline, starting
// where the top-left corner lets go of the top edge: run,
// quarter arc, run, quarter arc, Each leg hands its end
// point to the next, so the path closes exactly.
function railPoint(d) {
d = ((d % perim) + perim) % perim;
let r = rad;
if (d < runX) return Qt.point(railL + r + d, railT);
d -= runX;
if (d < arc) { let a = -Math.PI / 2 + d / r; return Qt.point(railR - r + Math.cos(a) * r, railT + r + Math.sin(a) * r); }
d -= arc;
if (d < runY) return Qt.point(railR, railT + r + d);
d -= runY;
if (d < arc) { let a = d / r; return Qt.point(railR - r + Math.cos(a) * r, railB - r + Math.sin(a) * r); }
d -= arc;
if (d < runX) return Qt.point(railR - r - d, railB);
d -= runX;
if (d < arc) { let a = Math.PI / 2 + d / r; return Qt.point(railL + r + Math.cos(a) * r, railB - r + Math.sin(a) * r); }
d -= arc;
if (d < runY) return Qt.point(railL, railB - r - d);
d -= runY;
let a = Math.PI + d / r;
return Qt.point(railL + r + Math.cos(a) * r, railT + r + Math.sin(a) * r);
}
// Where a landing at x sits on the bottom run he always
// comes down onto the floor, so that is the only leg that
// ever needs solving backwards.
function railAtBottom(x) {
return runX + arc + runY + arc
+ Math.max(0, Math.min(runX, railR - rad - x));
}
// 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 real d: Math.random() * 4000 // distance along the rail
property real hx: 0
property real hy: 0
property bool loose: false // knocked off, ballistic
property real vx: 0
property real vy: 0
property var trail: []
}
// A card in flight punts him off the rail. Head only a
// per-segment hit test buys nothing you can see.
function checkHit() {
for (let c of pile) {
if (!c.inFlight) continue; // a landed card is scenery
if (worm.hx < c.x - 6 || worm.hx > c.x + c.width + 6) continue;
if (worm.hy < c.y - 6 || worm.hy > c.y + c.height + 6) continue;
worm.loose = true;
worm.vx = (worm.hx < c.x + c.width / 2 ? -1 : 1) * (2 + Math.random() * 2);
worm.vy = -3; // popped up first, then gravity
return;
}
}
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;
if (worm.loose) {
worm.vy += 0.6; // gravity, px/tick²
worm.hx += worm.vx;
worm.hy += worm.vy;
// The rail doubles as the walls he cannot leave
// the screen he was knocked off.
if (worm.hx < weird.railL) { worm.hx = weird.railL; worm.vx = -worm.vx * 0.5; }
if (worm.hx > weird.railR) { worm.hx = weird.railR; worm.vx = -worm.vx * 0.5; }
if (worm.hy >= weird.railB) {
worm.hy = weird.railB;
// Still moving: bounce. Spent: he is back on
// the floor, which is just the bottom run.
if (worm.vy > 3) { worm.vy = -worm.vy * 0.45; worm.vx *= 0.7; }
else { worm.loose = false; worm.d = weird.railAtBottom(worm.hx); }
}
} else {
worm.d += 2.6;
let p = weird.railPoint(worm.d);
worm.hx = p.x; worm.hy = p.y;
weird.checkHit();
}
let t = worm.trail;
t.unshift(Qt.point(nx, ny));
t.unshift(Qt.point(worm.hx, worm.hy));
if (t.length > weird.segments * weird.lag) t.pop();
// slice(), not `t`: a var property assigned the array
// it already holds emits no change signal, so the
@ -943,6 +1020,9 @@ in
textureSize: Qt.size(width / 3, height / 3)
smooth: false
transformOrigin: Item.Center
// Only a card still in the air can knock the worm off
// his rail; one sitting in the pile is scenery.
readonly property bool inFlight: fall.running
function drop() {
fallX.to = Math.max(Theme.barWidth, Math.min(weird.width - width, x + (Math.random() - 0.5) * 240));