153 lines
5.6 KiB
QML
153 lines
5.6 KiB
QML
// 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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|