feat(quickshell): add HyprChromeShell, workspaces panel, persistent backdrop
HyprChromeShell owns what the rail's surfaces have to agree on: the target screen, the density and the shortcut that toggles it, and the bar/backdrop layer PAIR. That pair is why the wrapper exists — the backdrop has to sit exactly one layer below the bar in both densities, and split across two files the two assignments drifted apart and put the scrim over the bar. HyprChromeBar is now just its own surface; ChromeBackdrop takes its layer. The backdrop renders in both densities instead of fading out: full-screen while expanded, scoped to the band the rail occupies while collapsed, with a proportional tail fading off the bottom of that band. Both gradients end on their solid color when there is no fade — with a zero-length ramp the start and end stops coincide, Qt sorts stops unstably, and the transparent one winning turned "no fade" into a ramp across the whole scrim. WorkspacesPanel, between host and vitals, shows the active workspace per monitor: one accent box per output collapsed, the full strip with the shown one filled expanded, lined up in a column behind a fixed-width name cell. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U19X5LGTxtq4pb4jdNVivv
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import qs.hyprchrome.widgets
|
||||
|
||||
// The hyprchrome shell: owns everything the rail's surfaces have to agree on,
|
||||
// and instantiates them.
|
||||
//
|
||||
// State lives here rather than in any one surface because more than one of them
|
||||
// reads it, and a second reader is what turns a local property into shared
|
||||
// state. Three things qualify so far:
|
||||
//
|
||||
// * which monitor the shell lives on — every surface has to pick the same one
|
||||
// * the density — the whole rail expands and collapses as one, so the toggle
|
||||
// and the shortcut that drives it belong to the shell, not to the bar
|
||||
// * the layer PAIR — the backdrop must sit exactly one layer below the bar in
|
||||
// both densities. Two surfaces on the same layer stack by creation order,
|
||||
// which is not something to rely on; one layer apart is a guarantee. Split
|
||||
// across two files those two assignments drifted apart and the scrim ended
|
||||
// up over the bar, so they are derived together here and passed down.
|
||||
//
|
||||
// A future widget joins by taking `targetScreen` and `expanded` the same way.
|
||||
Scope {
|
||||
id: shell
|
||||
|
||||
// Monitor the rail lives on. Falls back to the FIRST connected screen when
|
||||
// the name matches nothing, so the bar still appears on a single-monitor
|
||||
// session or after a cable swap (DebugWindow falls back to the last one
|
||||
// instead — it wants the secondary).
|
||||
property string screenName: "DP-2"
|
||||
|
||||
// Quickshell.screens is a QML list, not a JS array — no .find() on it.
|
||||
readonly property var targetScreen: {
|
||||
const screens = Quickshell.screens;
|
||||
if (screens.length === 0)
|
||||
return null;
|
||||
for (let i = 0; i < screens.length; i++) {
|
||||
if (screens[i].name === shell.screenName)
|
||||
return screens[i];
|
||||
}
|
||||
return screens[0];
|
||||
}
|
||||
|
||||
// Density is a property of the SHELL: every panel follows it, so the whole
|
||||
// rail expands and collapses as one. Panels keep their own animation; only
|
||||
// the decision is centralised here.
|
||||
property bool expanded: false
|
||||
|
||||
function toggle() {
|
||||
shell.expanded = !shell.expanded;
|
||||
}
|
||||
|
||||
// Expanded the rail is over everything; collapsed it drops below ordinary
|
||||
// windows. BOTTOM rather than BACKGROUND for the collapsed bar: it is the
|
||||
// lowest level that still leaves a layer underneath for the backdrop, and
|
||||
// it keeps the rail off the wallpaper's own level.
|
||||
readonly property int barLayer: shell.expanded ? WlrLayer.Overlay : WlrLayer.Bottom
|
||||
readonly property int backdropLayer: shell.expanded ? WlrLayer.Top : WlrLayer.Background
|
||||
|
||||
// SUPER A — see hosts/terra/home/hyprland.nix.
|
||||
GlobalShortcut {
|
||||
name: "chrome"
|
||||
description: "Expand or collapse the hyprchrome bar"
|
||||
onPressed: shell.toggle()
|
||||
}
|
||||
|
||||
// Backdrop first: it is a layer below the bar, so stacking does not depend
|
||||
// on creation order, but keeping the declaration order the same as the
|
||||
// visual order costs nothing.
|
||||
ChromeBackdrop {
|
||||
screen: shell.targetScreen
|
||||
active: shell.expanded
|
||||
wlrLayer: shell.backdropLayer
|
||||
|
||||
// Collapsed, the scrim only backs the rail, so it needs the band the
|
||||
// rail occupies. contentHeight is the SETTLED height for the current
|
||||
// state — it jumps once per toggle rather than tracking the panels
|
||||
// frame by frame, so the backdrop animates the change itself instead of
|
||||
// chasing a value that is already being animated.
|
||||
barHeight: bar.contentHeight
|
||||
}
|
||||
|
||||
HyprChromeBar {
|
||||
id: bar
|
||||
|
||||
screen: shell.targetScreen
|
||||
// Set here, not from the window's own `screen`: reading that inside
|
||||
// `visible` is circular — a hidden window has no screen to report.
|
||||
visible: shell.targetScreen !== null
|
||||
expanded: shell.expanded
|
||||
wlrLayer: shell.barLayer
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user