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:
2026-08-30 22:41:30 +02:00
co-authored by Claude Opus 5
parent 15e081e52d
commit 3aecaf9de5
6 changed files with 507 additions and 117 deletions
@@ -82,7 +82,7 @@ Item {
// Where the summary sits when collapsed: just past the slug chip, and
// centred in the strip the panel collapses to, which is sized to the
// summary itself (or the height floor, whichever is taller).
readonly property real briefLeft: panel.headerContentWidth
readonly property real briefLeft: panel.headerContentWidth + 6
readonly property real collapsedHeight: Math.max(panel.minimumHeight, brief.height + panel.headerPadding * 2)
readonly property real briefTop: Math.round((panel.collapsedHeight - brief.height) / 2)
@@ -154,24 +154,6 @@ Item {
PathLine { x: 0; y: panel.offsetY }
}
// Connecting edges — drawn only on a side whose chamfer is off, which is
// exactly where a neighbour butts against this panel.
ShapePath {
fillColor: "transparent"
strokeColor: Theme.accent
strokeWidth: panel.rightChamfer ? 0 : panel.connectorWidth
startX: panelShape.width; startY: panel.offsetY
PathLine { x: panelShape.width; y: panelShape.height }
}
ShapePath {
fillColor: "transparent"
strokeColor: Theme.accent
strokeWidth: panel.leftChamfer ? 0 : panel.connectorWidth
startX: 0; startY: panel.offsetY
PathLine { x: 0; y: panelShape.height }
}
// Upper left accent line
ShapePath {
@@ -206,7 +188,7 @@ Item {
y: Math.round((panel.headerHeight - height) / 2)
// Puts the title where the accent line ends: chip + headerPadding on
// both sides of it.
spacing: panel.headerPadding
spacing: panel.headerPadding + 6
// Header slug — the one piece that survives a collapse, so the strip
// still says which panel it is.
@@ -223,7 +205,7 @@ Item {
text: panel.panelId
color: Theme.surface
font.family: Theme.microFont
font.pixelSize: 9
font.pixelSize: 11
font.bold: true
}
}