quickshell: add a pixel worm and middle-click-to-knock-cards-off
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e39be67887
commit
4626e72219
1 changed files with 165 additions and 0 deletions
|
|
@ -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 }
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue