A second shell chrome under dotfiles/quickshell/hyprchrome, built around a BarPanel that carries TWO renderings of its data: the default children are the expanded detail view, `summary` the terse one shown while collapsed. Both stay bound to the same sources, so the densities cannot disagree, and the panel cross-fades between them while its height animates. Panels: HostPanel (hostname, user, timezone, clock), VitalsPanel (CPU load and temperature, memory, GPU load and temperature, all metered), TrayPanel (system tray, self-sizing). HyprChromeBar pins them to DP-2 and owns `expanded` for the whole rail — SUPER A, via GlobalShortcut "chrome". GPU busy comes off sysfs rather than the node_exporter scrape VitalsData already does: the hwmon collector carries the card's temps, power and clocks but not its utilisation. The bar's height binds to each panel's `targetHeight` — where it will settle, not where the animation currently is — because the exclusive zone is window-sized by default, and binding to the animated height relayouts every tiled window on the output twelve times per toggle. The zone follows the target immediately so the desktop reflows once, at the start; the surface itself shrinks only after the panels finish, or it would clip them mid-animation. DebugWindow stages a widget in the middle of the secondary monitor (SUPER CTRL D), masked so only the staged widget takes pointer input. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HEtXvseVb5gwAtKNhFx2PU
135 lines
3.8 KiB
QML
135 lines
3.8 KiB
QML
import QtQuick
|
|
import QtQuick.Shapes
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Hyprland
|
|
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()
|
|
}
|
|
|
|
PanelWindow {
|
|
id: window
|
|
|
|
screen: root.targetScreen
|
|
visible: root.targetScreen !== null
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|