diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-24 12:10:35 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-24 12:10:35 +0200 |
| commit | b75031c1320615ef636dbfeb42e2e73b2926382a (patch) | |
| tree | beb044b60b8328e3c6663dc5048a6a4b0e24c44a /desktop/companion | |
| parent | 9ad2ff6410d1de7fa241718a75c81a2cf08746a4 (diff) | |
| download | quickshell-b75031c1320615ef636dbfeb42e2e73b2926382a.tar.gz quickshell-b75031c1320615ef636dbfeb42e2e73b2926382a.zip | |
companion: sit on the assistant's chat card
While the canvas shows its tall card she hops onto its top edge, through
the monitor hop: the exit plays, the window's margins move, the entrance
plays. The tall card's top is fixed, so nothing follows a moving edge.
Seated she ignores focus changes and balloons, and shows Mood.seatFrame
of whatever she would show standing.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/companion')
| -rw-r--r-- | desktop/companion/Companion.qml | 50 |
1 files changed, 37 insertions, 13 deletions
diff --git a/desktop/companion/Companion.qml b/desktop/companion/Companion.qml index e2d91a9..21b1a38 100644 --- a/desktop/companion/Companion.qml +++ b/desktop/companion/Companion.qml @@ -63,24 +63,42 @@ PanelWindow { } } - // The monitor she is on trails the focused one: a change plays her exit - // here first, and only then moves the window (settled, below). + // Where she goes: the focused monitor's corner, or seated on the + // assistant's tall card. here and sitting are where the window is now + // and trail the goal: a change plays her exit first, and only then + // moves the window (settled, below). readonly property string focused: Hyprland.focusedMonitor?.name ?? "" + readonly property bool seated: root.asst.open && root.asst.tall + readonly property string goal: root.seated ? root.asst.monitor : root.focused property string here: "" + property bool sitting: false property bool hopping: false // No focused monitor (outputs gone over a suspend) is not a destination. // Already out of view, there is no exit to wait for: settled would never // fire, hopping would stick and hide her for good, so move at once. - onFocusedChanged: { - if (root.focused === "" || root.focused === root.here) return; - if (root.here === "" || root.away) root.here = root.focused; - else root.hopping = true; + function move() { + if (root.goal === "" || (root.goal === root.here && root.seated === root.sitting)) return; + if (root.here === "" || root.away) { + root.here = root.goal; + root.sitting = root.seated; + } else { + root.hopping = true; + } } - Component.onCompleted: root.here = root.focused + onGoalChanged: root.move() + onSeatedChanged: root.move() + Component.onCompleted: root.move() screen: Quickshell.screens.find(s => s.name === root.here) ?? Quickshell.screens[0] anchors { right: true; bottom: true } - margins { right: 24; bottom: 16 } + // Seated, her right edge is 90 px right of the screen's centre, which + // puts her in the gap in the card's header, and her feet 35 px below the + // card's top. The right edge rather than her centre, because frames + // differ in width and the window would shift with every frame. + margins { + right: root.sitting ? Math.round(root.asst.screenObj.width / 2 - 90) : 24 + bottom: root.sitting ? root.asst.screenObj.height - root.asst.cardTop - 35 : 16 + } implicitWidth: 760 implicitHeight: 260 color: "transparent" @@ -268,7 +286,7 @@ PanelWindow { interval: 100 onTriggered: { const t = Date.now(); - if (root.away || root.here !== root.focused || !Mood.admireAllowed(root.admireAt, t)) return; + if (root.away || root.sitting || root.here !== root.focused || !Mood.admireAllowed(root.admireAt, t)) return; if ((Hyprland.focusedWorkspace?.toplevels.values.length ?? 1) !== 0) return; if (!Admired.listed(Admired.on(root.here))) return; root.admireAt = t; @@ -291,8 +309,10 @@ PanelWindow { Timer { id: remap; interval: 400; onTriggered: root.remapping = false } // Balloons stack bottom-right of their screen. When that is her corner, - // any balloon on screen shoves her left past the stack. + // any balloon on screen shoves her left past the stack. Seated on the + // assistant's card she is nowhere near them. readonly property bool shoved: { + if (root.sitting) return false; const place = Notify.config.balloons; if (place.screen !== root.here || place.corner !== "bottom-right") return false; return (Notify.queue || []).some(n => NC.shown(Notify.config, n, Notify.dnd, @@ -452,8 +472,10 @@ PanelWindow { running: root.cycle !== null && !root.away onTriggered: root.cycleIndex = (root.cycleIndex + 1) % root.cycle.frames.length } - readonly property string frame: root.leaving ? root.waveCycle[root.waveIndex] + readonly property string standing: root.leaving ? root.waveCycle[root.waveIndex] : root.cycle ? root.cycle.frames[root.cycleIndex % root.cycle.frames.length] : root.mood.frame + // Seated, the sitting version of it (Mood.seatFrame). + readonly property string frame: root.sitting ? Mood.seatFrame(root.standing) : root.standing // qs -p <desktop> ipc call companion toggle -> SUPER+\ IpcHandler { @@ -463,10 +485,12 @@ PanelWindow { function hide() { Prefs.setCompanion(false); } } - // The exit has played on the old monitor: move, and the entrance plays. + // The exit has played: move, and the entrance plays. The goal may have + // changed during the exit; the latest one wins. function settled(out) { if (out && root.hopping) { - root.here = root.focused; + root.here = root.goal; + root.sitting = root.seated; root.hopping = false; } } |
