import QtQuick import Quickshell import Quickshell.Hyprland import "root:/config" import "root:/components" // Workspace indicator: leaning ticks that stretch open when focused. Occupied // workspaces fill in, urgent ones snap to the alarm colour. Item { id: root required property var screenRef readonly property HyprlandMonitor monitor: Hyprland.monitorFor(screenRef) readonly property int focusedId: monitor?.activeWorkspace?.id ?? -1 // Live workspaces on this monitor, keyed by id, ignoring special workspaces. readonly property var occupancy: { const map = {}; for (const w of (Hyprland.workspaces?.values ?? [])) { if (w.id < 1) continue; if (root.monitor && w.monitor && w.monitor.id !== root.monitor.id) continue; map[w.id] = { count: w.toplevels?.values?.length ?? 0, urgent: w.urgent, fullscreen: w.hasFullscreen }; } return map; } // Always show 1..5, plus anything beyond that which is actually in use. readonly property var slots: { const ids = [1, 2, 3, 4, 5]; for (const key in root.occupancy) { const id = parseInt(key); if (ids.indexOf(id) === -1) ids.push(id); } return ids.sort((a, b) => a - b); } implicitWidth: row.implicitWidth implicitHeight: 24 Row { id: row anchors.verticalCenter: parent.verticalCenter spacing: 6 Repeater { model: root.slots Item { id: pip required property var modelData required property int index readonly property int wsId: modelData readonly property var info: root.occupancy[wsId] readonly property bool occupied: info !== undefined && info.count > 0 readonly property bool urgent: info !== undefined && info.urgent readonly property bool focused: root.focusedId === wsId readonly property bool hovered: pipHover.hovered // Every tick is the same height and the same shear. Only the // width changes, and only to say which workspace is focused. // // Kept well short of the bar's 20px divider rules. At 18px an // empty outline tick was the same height, lean and weight as a // divider, and the workspace strip read as a row of rules. width: focused ? 30 : (occupied ? 16 : 8) height: 14 Behavior on width { NumberAnimation { duration: Theme.durSlow easing.type: Easing.OutCubic } } Skew { id: tick anchors.fill: parent // Focused is a solid accent block. Urgent is a solid alarm // block — it used to be the accent too, which made an // urgent workspace indistinguishable from the focused one. // Occupied is a subtext block. Empty is a flat recessed // slot, filled rather than outlined so it cannot pass for a // divider. color: { if (pip.focused) return Theme.accent; if (pip.urgent) return Theme.alarm; if (pip.hovered) return Theme.text; if (pip.occupied) return Theme.subtext; return Theme.surfaceAlt; } Behavior on color { ColorAnimation { duration: Theme.durBase } } // Urgent is the one pip that has to be seen peripherally. SequentialAnimation on opacity { running: pip.urgent && !pip.focused loops: Animation.Infinite alwaysRunToEnd: true NumberAnimation { to: 0.4; duration: 520; easing.type: Easing.InOutQuad } NumberAnimation { to: 1.0; duration: 520; easing.type: Easing.InOutQuad } } } // The focused workspace wears its number. Text { anchors.centerIn: parent opacity: pip.focused ? 1 : 0 visible: opacity > 0.01 text: pip.wsId color: Theme.ink font.family: Theme.fontDisplay font.pixelSize: Theme.fsSmall font.weight: Theme.weightDisplay renderType: Text.NativeRendering Behavior on opacity { NumberAnimation { duration: Theme.durBase } } } // Fullscreen marker. Rectangle { visible: pip.info !== undefined && pip.info.fullscreen && !pip.focused width: 4; height: 4 color: Theme.accent anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.bottom anchors.topMargin: 1 } HoverHandler { id: pipHover cursorShape: Qt.PointingHandCursor } // Hyprland evaluates IPC `dispatch` payloads as Lua now that the // config is Lua (hyprland.lua rather than hyprland.conf), so the // old `dispatch workspace 3` string is a Lua syntax error and the // click silently does nothing. Dispatchers have to be called the // same way lua/binds/workspaces.lua calls them. TapHandler { onTapped: Hyprland.dispatch("hl.dsp.focus({workspace = " + pip.wsId + "})") } } } } }