feat(quickshell): give the prompt its own panel, ESC handling and placement
PolkitPanel replaces BarPanel as the dialog's chrome. Deliberately not a
subclass or a fork: most of BarPanel is density machinery — summary slot,
animated height, state pair, transitions — that a modal never uses, and
inheriting it would tie the dialog's look to a component whose real job is the
rail, so every restyle here would have to be justified against the panels up
there. It keeps the shell's silhouette (cut corners with detached accent caps,
the accent rules, the header strip) and drops the rail's tick decoration.
ESC closes the rail. The bar had no keyboard focus at all, so this adds it,
gated by the shell rather than left to the compositor to arbitrate between two
exclusive surfaces — that resolves by stacking and would invert silently the
day the layers change:
grabsKeyboard: shell.expanded && !polkit.prompting
so ESC dismisses the prompt while one is open and closes the rail afterwards.
Verified by instrumenting the handoff: expanded -> true, prompt open -> false,
prompt dismissed -> true, collapsed -> false. Note the rail now takes EXCLUSIVE
keyboard focus while expanded, which is the cost of answering a keypress the
user has not aimed at anything.
The dialog sits a third of the way down rather than centred, panel centre on
the third so it grows symmetrically as the message wraps, floored at a margin
so a tall prompt on a short output cannot be pushed off the top.
Also carries the backdrop tuning: dim 0.75 -> 0.65, gridOpacity 0.15 -> 0.10,
crossOpacity 0.45 -> 0.15, now that the scrim is used by the prompt as well as
the rail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GAq2kKCLazZmrKvkd3akud
This commit is contained in:
@@ -28,6 +28,50 @@ PanelWindow {
|
||||
// HyprChromeShell.
|
||||
property int wlrLayer: WlrLayer.Overlay
|
||||
|
||||
// Whether the rail should hold the keyboard, so ESC can close it. Driven by
|
||||
// the shell rather than derived from `expanded`, because the rail is not the
|
||||
// only thing that wants the keyboard: while a polkit prompt is up the shell
|
||||
// withholds this, so ESC reaches the DIALOG and dismisses that instead.
|
||||
// Once the prompt is gone the rail gets the keyboard back and a second ESC
|
||||
// closes the rail — one key, one thing at a time, innermost first.
|
||||
property bool grabsKeyboard: false
|
||||
|
||||
// EXCLUSIVE rather than OnDemand: OnDemand only offers focus to a surface
|
||||
// the user clicks, and the whole point here is to answer a keypress the
|
||||
// user has not aimed at anything. Taking the keyboard is defensible because
|
||||
// an expanded rail is already a modal-ish state — it dims the desktop
|
||||
// behind itself with the same scrim the prompt uses.
|
||||
WlrLayershell.keyboardFocus: window.grabsKeyboard
|
||||
? WlrKeyboardFocus.Exclusive
|
||||
: WlrKeyboardFocus.None
|
||||
|
||||
signal dismissed
|
||||
|
||||
// A layer surface only delivers keys to an item that has active focus, and
|
||||
// nothing in the rail wants focus for its own sake — the panels are
|
||||
// readouts. So one focus sink covers the whole surface. It re-takes focus
|
||||
// whenever the grab is handed back, since losing the surface's focus drops
|
||||
// the item's too.
|
||||
Item {
|
||||
id: keySink
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
window.dismissed();
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: window
|
||||
function onGrabsKeyboardChanged() {
|
||||
if (window.grabsKeyboard)
|
||||
keySink.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Own namespace so a layerrule can exempt the rail from Hyprland's layer
|
||||
// animation without also catching the launchers, which share the default
|
||||
// "quickshell" namespace and do want their fade.
|
||||
|
||||
Reference in New Issue
Block a user