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. // 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. WlrLayershell.namespace: "hyprchrome-bar" WlrLayershell.layer: WlrLayer.Overlay property int margin: 12 // The surface never resizes: it is always tall enough for the expanded // rail, and only the exclusive zone tracks the current state. Resizing a // layer surface makes Hyprland animate the change, which showed up as the // panels twitching a pixel or two the moment the collapse finished. // // The zone still follows the target height, so tiled windows reflow once // per toggle, at the start, and slide while the panels animate. readonly property real expandedContent: Math.max(hostPanel.expandedHeight, vitalsPanel.expandedHeight, trayPanel.expandedHeight) + window.margin * 2 readonly property real contentHeight: Math.max(hostPanel.targetHeight, vitalsPanel.targetHeight, trayPanel.targetHeight) + window.margin * 2 implicitHeight: Math.round(window.expandedContent) exclusionMode: ExclusionMode.Normal exclusiveZone: Math.round(window.contentHeight) // Only the panels take input. Without this the surface would keep eating // clicks across its full height while the rail is collapsed. mask: Region { item: panelRow } 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 } } } }