// The dock — a replacement for Dash-to-Dock configured as: bottom edge, 48px // icons, intellihide against all windows, dot running-indicators, show-apps // button at the leading edge. // // Intellihide means the dock reserves no space at all (exclusiveZone 0) and // floats over windows, appearing when the pointer reaches the bottom edge or // when nothing is in the way. import Quickshell import Quickshell.Wayland import Quickshell.Hyprland import QtQuick import qs.config import qs.services PanelWindow { id: root // Set by Variants when instantiated per-screen. Plain rather than // `required` so the dock can also be created standalone while testing; // Variants injects into the existing property either way. property var modelData: null screen: root.modelData anchors { bottom: true left: true right: true } color: "transparent" // A dock that reserved space would not be intellihiding. exclusiveZone: 0 exclusionMode: ExclusionMode.Ignore // Matched by the `qs-dock` layer rule in hypr/rules.lua — do not rename. WlrLayershell.namespace: "qs-dock" WlrLayershell.layer: WlrLayer.Top WlrLayershell.keyboardFocus: WlrKeyboardFocus.None // ── Geometry ──────────────────────────────────────────────────────────── // Height = room for the tooltip above the bar + the bar + the gap under it. readonly property int revealStripHeight: 3 readonly property int bottomMargin: Theme.barGap readonly property int tooltipSpace: 34 implicitHeight: tooltipSpace + body.implicitHeight + bottomMargin // ── Intellihide ───────────────────────────────────────────────────────── // Hyprland does not expose live toplevel geometry, so exact overlap cannot // be computed. "Is anything on this workspace at all" is the robust proxy, // and it is what Dash-to-Dock's all-windows intellihide felt like in // practice: an empty workspace keeps the dock out. readonly property bool workspaceOccupied: { const ws = Hyprland.focusedWorkspace; return !!ws && ws.toplevels.values.length > 0; } readonly property bool wantRevealed: !Settings.dockAutohide || !workspaceOccupied || pointer.hovered property bool revealed: true onWantRevealedChanged: { if (wantRevealed) { hideTimer.stop(); revealTimer.restart(); } else { revealTimer.stop(); hideTimer.restart(); } } Timer { id: revealTimer interval: Settings.dockRevealDelayMs onTriggered: root.revealed = true } Timer { id: hideTimer interval: Settings.dockHideDelayMs onTriggered: root.revealed = false } // Other modules (the bar, the capture overlay) read this. onRevealedChanged: ShellState.dockRevealed = revealed // wantRevealed's first evaluation emits no change signal when it lands on // false (the default), so the initial state has to be taken explicitly — // otherwise a shell started on a busy workspace would leave the dock up. Component.onCompleted: { revealed = wantRevealed; ShellState.dockRevealed = revealed; } // ── Input region ──────────────────────────────────────────────────────── // Revealed: the bar plus everything below it, so crossing the gap under the // dock does not count as leaving. Hidden: a sliver along the screen edge, // which is the only thing that can still trigger the dock — every other // click passes straight through to the window underneath. mask: Region { item: maskItem } Item { id: surface anchors.fill: parent // Reports the pointer anywhere inside the input region. A HoverHandler // rather than a MouseArea because it keeps reporting while the icons' // own MouseAreas are hovered. HoverHandler { id: pointer } Item { id: maskItem x: root.revealed ? body.x : 0 y: root.revealed ? body.y : surface.height - root.revealStripHeight width: root.revealed ? body.width : surface.width height: root.revealed ? surface.height - body.y : root.revealStripHeight } DockBody { id: body anchors.horizontalCenter: parent.horizontalCenter // Slides off the bottom edge when hidden. y: root.revealed ? root.tooltipSpace : surface.height opacity: root.revealed ? 1 : 0 // Asymmetric on purpose. Revealing is a response to something the // user just did, so it has to feel immediate — any delay there // reads as lag. Hiding is not a response to anything, so it can // take its time and stay calm in peripheral vision. Behavior on y { NumberAnimation { duration: root.revealed ? Theme.durDockReveal : Theme.durNormal easing.type: root.revealed ? Easing.OutQuint : Easing.InCubic } } Behavior on opacity { NumberAnimation { duration: root.revealed ? Theme.durDockReveal : Theme.durNormal easing.type: Easing.OutCubic } } } } }