quickshell: drop the worm; only feed the spectrum ring while the popup is open
This commit is contained in:
parent
d831fc82f2
commit
d7ea0978ec
1 changed files with 12 additions and 307 deletions
|
|
@ -347,8 +347,9 @@ in
|
||||||
|
|
||||||
# cava in raw mode: one newline-terminated frame per tick, bars as
|
# cava in raw mode: one newline-terminated frame per tick, bars as
|
||||||
# semicolon-separated 0-100 ints — trivially parsed by SplitParser.
|
# semicolon-separated 0-100 ints — trivially parsed by SplitParser.
|
||||||
# Drives the media card's spectrum ring. Only spawned while the
|
# Drives the media card's spectrum ring. Spawned while something is
|
||||||
# calendar popup is open, so nothing captures audio at idle.
|
# playing, so nothing captures audio at idle; the frames are only fed
|
||||||
|
# to the ring while the calendar popup is actually open.
|
||||||
cavaConfig = pkgs.writeText "cava-quickshell.conf" ''
|
cavaConfig = pkgs.writeText "cava-quickshell.conf" ''
|
||||||
[general]
|
[general]
|
||||||
framerate = 30
|
framerate = 30
|
||||||
|
|
@ -374,7 +375,6 @@ in
|
||||||
Bar 1.0 Bar.qml
|
Bar 1.0 Bar.qml
|
||||||
Lock 1.0 Lock.qml
|
Lock 1.0 Lock.qml
|
||||||
Cheatsheet 1.0 Cheatsheet.qml
|
Cheatsheet 1.0 Cheatsheet.qml
|
||||||
Weird 1.0 Weird.qml
|
|
||||||
Archie 1.0 Archie.qml
|
Archie 1.0 Archie.qml
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
@ -842,296 +842,6 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
"quickshell/Weird.qml" = {
|
|
||||||
onChange = qsRestart;
|
|
||||||
text = ''
|
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
// 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
|
|
||||||
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: 11
|
|
||||||
|
|
||||||
// ── Inchworm gait ───────────────────────────────────────
|
|
||||||
// A looper does not glide: it plants its tail, arches the
|
|
||||||
// middle into a hoop, throws the head forward, then drags
|
|
||||||
// the tail up to meet it. So the body is the rail interval
|
|
||||||
// [tailD, headD] and the two ends take turns moving — the
|
|
||||||
// span oscillating between spanMin and spanMax IS the walk
|
|
||||||
// cycle, and the arch is just how tall the hoop has to be
|
|
||||||
// to absorb the slack.
|
|
||||||
readonly property real spanMin: 26
|
|
||||||
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
|
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inward unit normal at d — which way "up off the surface"
|
|
||||||
// points. Taken from the tangent rather than cased per edge,
|
|
||||||
// so it swings round the corners for free: the path runs
|
|
||||||
// clockwise, so rotating the tangent by +90° in screen
|
|
||||||
// coordinates (y down) always aims into the screen.
|
|
||||||
function railNormal(d) {
|
|
||||||
let a = railPoint(d - 2), b = railPoint(d + 2);
|
|
||||||
let tx = b.x - a.x, ty = b.y - a.y;
|
|
||||||
let len = Math.hypot(tx, ty) || 1;
|
|
||||||
return Qt.point(-ty / len, tx / len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Worm ────────────────────────────────────────────────
|
|
||||||
QtObject {
|
|
||||||
id: worm
|
|
||||||
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
|
|
||||||
property real spin: 0 // rad/tick while airborne
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// and handing over plain points is far less machinery than
|
|
||||||
// making both reactive.
|
|
||||||
property var body: []
|
|
||||||
|
|
||||||
// Body laid along the rail, hooped by however much slack the
|
|
||||||
// current compression leaves. index 0 is the head. `span` is
|
|
||||||
// signed, so everything below works crawling either way.
|
|
||||||
function railBody() {
|
|
||||||
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 = from + u * (to - from);
|
|
||||||
let base = railPoint(d);
|
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
interval: 33; running: true; repeat: true
|
|
||||||
onTriggered: {
|
|
||||||
if (worm.loose) {
|
|
||||||
worm.vy += 0.6; // gravity, px/tick²
|
|
||||||
let hx = body.length ? body[0].x : 0;
|
|
||||||
let hy = body.length ? body[0].y : 0;
|
|
||||||
hx += worm.vx; hy += worm.vy;
|
|
||||||
// The rail doubles as the walls — he cannot leave
|
|
||||||
// the screen he was shoved off.
|
|
||||||
if (hx < railL) { hx = railL; worm.vx = -worm.vx * 0.5; }
|
|
||||||
if (hx > railR) { hx = railR; worm.vx = -worm.vx * 0.5; }
|
|
||||||
if (hy >= railB) {
|
|
||||||
hy = 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.backD = railAtBottom(hx);
|
|
||||||
worm.frontD = worm.backD + Math.sign(worm.step) * spanMin;
|
|
||||||
worm.reaching = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (worm.loose) {
|
|
||||||
// Airborne he keeps the shape he was caught
|
|
||||||
// in and tumbles about his own head: map()
|
|
||||||
// reads the old array throughout, so body[0]
|
|
||||||
// stays the pivot and the head lands exactly
|
|
||||||
// on the new position.
|
|
||||||
let c = Math.cos(worm.spin), s = Math.sin(worm.spin);
|
|
||||||
let px = body[0].x, py = body[0].y;
|
|
||||||
body = body.map(p => {
|
|
||||||
let ox = p.x - px, oy = p.y - py;
|
|
||||||
return Qt.point(hx + ox * c - oy * s, hy + ox * s + oy * c);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
body = railBody();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Take turns: throw the front out to full stretch,
|
|
||||||
// then drag the back up to it.
|
|
||||||
if (worm.reaching) {
|
|
||||||
worm.frontD += worm.step;
|
|
||||||
if (Math.abs(worm.frontD - worm.backD) >= spanMax) worm.reaching = false;
|
|
||||||
} else {
|
|
||||||
worm.backD += worm.step;
|
|
||||||
if (Math.abs(worm.frontD - worm.backD) <= spanMin) worm.reaching = true;
|
|
||||||
}
|
|
||||||
body = railBody();
|
|
||||||
checkShove();
|
|
||||||
}
|
|
||||||
// Snapshot last, so a rect is always compared against
|
|
||||||
// where it was exactly one tick ago. vector4d is a
|
|
||||||
// value type, so this copies numbers, not handles.
|
|
||||||
prevRects = pushers.map(p => p.rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: weird.segments
|
|
||||||
Rectangle {
|
|
||||||
id: seg
|
|
||||||
required property int index
|
|
||||||
readonly property var p: weird.body[index]
|
|
||||||
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 } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Shoved by the panels ────────────────────────────────
|
|
||||||
// Each pusher is { rect, push }: `rect` is the very vector4d
|
|
||||||
// the frame shader melts into the chrome (cx, cy, hw, hh;
|
|
||||||
// hw <= 0 means "not on screen"), so what sweeps him aside is
|
|
||||||
// the panel outline you can actually see. `push` is the unit
|
|
||||||
// direction that panel slides out in — a constant of the
|
|
||||||
// design, and not something worth deriving: the morph is
|
|
||||||
// 280ms of OutExpo, nearly all of it landing inside a single
|
|
||||||
// 33ms tick, so there is rarely a partly-grown rect to read a
|
|
||||||
// direction off of.
|
|
||||||
property var pushers: []
|
|
||||||
property var prevRects: []
|
|
||||||
readonly property real shoveSpeed: 26
|
|
||||||
// Ticks before a panel may launch him again. Without it he can
|
|
||||||
// land back inside the same rect with his head over the edge
|
|
||||||
// and get booted forever.
|
|
||||||
property int shoveCool: 0
|
|
||||||
|
|
||||||
function launch(p) {
|
|
||||||
worm.loose = true;
|
|
||||||
worm.vx = p.push.x * shoveSpeed;
|
|
||||||
worm.vy = p.push.y * shoveSpeed - 8; // -8: sent up, not slid along
|
|
||||||
worm.spin = (0.04 + Math.random() * 0.05) * (p.push.x >= 0 ? 1 : -1);
|
|
||||||
shoveCool = 45;
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
if (shoveCool > 0) { shoveCool--; return; }
|
|
||||||
let tail = body[segments - 1];
|
|
||||||
for (let i = 0; i < pushers.length; i++) {
|
|
||||||
let r = pushers[i].rect, q = prevRects[i];
|
|
||||||
if (!r || r.z < 0.5) continue;
|
|
||||||
// Any part of him in the way is enough to be caught.
|
|
||||||
let touched = false;
|
|
||||||
for (let s = 0; s < body.length && !touched; s++)
|
|
||||||
touched = inRect(body[s], r);
|
|
||||||
if (!touched) continue;
|
|
||||||
// A panel popping out on top of him and him crawling
|
|
||||||
// into one already open both end the same way now:
|
|
||||||
// launched. Only the first is the moment you asked
|
|
||||||
// for, but a dropdown sits open for minutes while he
|
|
||||||
// crawls at well under a pixel a tick, so contact is
|
|
||||||
// the event you actually see — and having that merely
|
|
||||||
// turn him round is what read as never being knocked.
|
|
||||||
//
|
|
||||||
// The exception is his whole body being inside, which
|
|
||||||
// means he landed in there: walking out is the way
|
|
||||||
// out, and kicking him again would only pin him.
|
|
||||||
if (!inRect(tail, r)) launch(pushers[i]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
"quickshell/Archie.qml" = {
|
"quickshell/Archie.qml" = {
|
||||||
onChange = qsRestart;
|
onChange = qsRestart;
|
||||||
text = ''
|
text = ''
|
||||||
|
|
@ -6247,7 +5957,15 @@ in
|
||||||
if (parts[i] === "") continue;
|
if (parts[i] === "") continue;
|
||||||
out.push(parseInt(parts[i]) || 0);
|
out.push(parseInt(parts[i]) || 0);
|
||||||
}
|
}
|
||||||
if (out.length > 0) calPopup.levels = out;
|
// Only publish while the popup is on screen.
|
||||||
|
// The bar's surface is the whole screen with a
|
||||||
|
// full-screen SDF shader filling it, so every
|
||||||
|
// levels write repaints all of it — 30 times a
|
||||||
|
// second, for a ring nobody can see, whenever
|
||||||
|
// anything plays audio. cava itself keeps
|
||||||
|
// running so the ring is still mid-song when
|
||||||
|
// the popup opens.
|
||||||
|
if (out.length > 0 && calPopup.open) calPopup.levels = out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7318,20 +7036,7 @@ in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, paired with the way each
|
|
||||||
// one slides out: dropdowns and toasts rightward out of the
|
|
||||||
// bar column, the launcher up out of the bottom frame band.
|
|
||||||
${lib.optionalString (!isMacbook) ''
|
${lib.optionalString (!isMacbook) ''
|
||||||
Weird {
|
|
||||||
pushers: [
|
|
||||||
{ rect: chromeSdf.panel, push: Qt.point(1, 0) },
|
|
||||||
{ rect: chromeSdf.toast, push: Qt.point(1, 0) },
|
|
||||||
{ rect: chromeSdf.launcher, push: Qt.point(0, -1) }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Archie. One dog, primary screen only.
|
// Archie. One dog, primary screen only.
|
||||||
Archie { id: archieDog; primary: bar.screen === Quickshell.screens[0] }
|
Archie { id: archieDog; primary: bar.screen === Quickshell.screens[0] }
|
||||||
''}
|
''}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue