A full-screen scrim carrying the dense bar's drafting grid, shown while the rail is expanded and faded out with it. It sits on the TOP layer while the bar moves to OVERLAY. Both on one layer would stack by surface creation order, which is not something to rely on; a layer apart makes "above windows, below the bar" a guarantee. The mask is an empty Region, so the scrim takes no clicks and reserves nothing. The grid is the dense bar's, at twice the spacing and with registration crosses on every other intersection. Its 0.018 opacity was tuned against a near-black panel and is invisible over a scrim on lit windows, so grid and crosses are both properties rather than constants, and the lines take a desaturated accent derived from the palette instead of plain text colour. Cross geometry rounds with Math.floor on both the mark's offset and the bars inside it. anchors.*Center halves the box unfloored, which put an even-sized mark half a pixel off the 1px rule it registers against. crossThickness is in steps for the same reason: 1 -> 1px, 2 -> 3px, 3 -> 5px, since only an odd width straddles a rule symmetrically. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HEtXvseVb5gwAtKNhFx2PU
148 lines
4.3 KiB
QML
148 lines
4.3 KiB
QML
import QtQuick
|
|
import QtQuick.Shapes
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Hyprland
|
|
import Quickshell.Wayland
|
|
import qs.hyprchrome.widgets
|
|
import qs.hyprchrome.widgets.host
|
|
import qs.hyprchrome.widgets.vitals
|
|
import qs.hyprchrome.widgets.tray
|
|
|
|
Scope {
|
|
id: root
|
|
|
|
// 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 === root.screenName)
|
|
return screens[i];
|
|
}
|
|
return screens[0];
|
|
}
|
|
|
|
// Density is a property of the BAR: 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: true
|
|
|
|
function toggle() {
|
|
root.expanded = !root.expanded;
|
|
}
|
|
|
|
// SUPER A — see hosts/terra/home/hyprland.nix.
|
|
GlobalShortcut {
|
|
name: "chrome"
|
|
description: "Expand or collapse the hyprchrome bar"
|
|
onPressed: root.toggle()
|
|
}
|
|
|
|
// Scrim 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: root.targetScreen
|
|
active: root.expanded
|
|
}
|
|
|
|
PanelWindow {
|
|
id: window
|
|
|
|
screen: root.targetScreen
|
|
visible: root.targetScreen !== null
|
|
|
|
// One layer above the scrim, so the stacking is guaranteed rather than
|
|
// dependent on surface creation order. See ChromeBackdrop.
|
|
WlrLayershell.layer: WlrLayer.Overlay
|
|
|
|
property int margin: 12
|
|
|
|
// Where the panels will SETTLE, not where they are mid-transition. Binding
|
|
// the surface to the animated height instead resizes the layer surface —
|
|
// and, with the automatic exclusive zone, relayouts every tiled window on
|
|
// this output — on every frame of the animation.
|
|
readonly property real contentHeight: Math.max(hostPanel.targetHeight, vitalsPanel.targetHeight, trayPanel.targetHeight) + window.margin * 2
|
|
|
|
// The surface and the exclusive zone move on different clocks. The zone is
|
|
// the desktop-visible half: set it to the target immediately, so the tiled
|
|
// windows reflow ONCE, at the start, and slide while the bar animates.
|
|
// The surface itself grows before the panels do but shrinks only after they
|
|
// have finished, because a surface that shrank immediately would clip the
|
|
// panels still animating inside it.
|
|
property real barHeight: 0
|
|
|
|
exclusionMode: ExclusionMode.Normal
|
|
exclusiveZone: Math.round(window.contentHeight)
|
|
|
|
implicitHeight: window.barHeight
|
|
|
|
onContentHeightChanged: {
|
|
if (window.contentHeight > window.barHeight)
|
|
window.barHeight = window.contentHeight;
|
|
else
|
|
shrink.restart();
|
|
}
|
|
|
|
Component.onCompleted: window.barHeight = window.contentHeight
|
|
|
|
Timer {
|
|
id: shrink
|
|
|
|
// Longer than the panel's own collapse (200ms body + 110ms fade-in).
|
|
interval: 340
|
|
onTriggered: window.barHeight = window.contentHeight
|
|
}
|
|
|
|
anchors { top: true; left: true; right: true; }
|
|
|
|
color: "transparent"
|
|
|
|
RowLayout {
|
|
id: panelRow
|
|
x: window.margin
|
|
y: window.margin
|
|
width: window.width - window.margin * 2
|
|
spacing: 8
|
|
|
|
HostPanel {
|
|
id: hostPanel
|
|
|
|
expanded: root.expanded
|
|
toggleOnClick: false
|
|
Layout.preferredWidth: 350
|
|
Layout.fillHeight: true
|
|
}
|
|
|
|
VitalsPanel {
|
|
id: vitalsPanel
|
|
|
|
expanded: root.expanded
|
|
toggleOnClick: false
|
|
Layout.preferredWidth: 500
|
|
Layout.fillHeight: true
|
|
}
|
|
|
|
// Slack lives between the left group and the tray, so the tray sits
|
|
// flush right whatever the other panels measure.
|
|
Item { Layout.fillWidth: true }
|
|
|
|
TrayPanel {
|
|
id: trayPanel
|
|
|
|
expanded: root.expanded
|
|
toggleOnClick: false
|
|
Layout.fillHeight: true
|
|
}
|
|
}
|
|
}
|
|
}
|