// "Add app to dock", reachable without opening Settings. // // The Settings page is the place to curate the whole dock -- reorder it, unpin // things, change how it hides. Adding one application is a single decision made // while looking at the dock, and routing it through a settings window means // finding the page, then the card, then the box. This is that box, on its own. // // It embeds the same DockAppPicker the Dock page uses rather than growing a // second search: the exclusion of already-pinned applications, the icon lookup // and the "nothing until you type" behaviour are all already there, and two // copies of them would drift. import Quickshell import Quickshell.Wayland import QtQuick import qs.config import qs.services import qs.modules.settings import qs.widgets PanelWindow { id: root readonly property bool open: ShellState.dockPickerOpen // The one place a pin is added from outside the Settings page. Returns // false for an id nothing installs, so the IPC caller hears about a typo // instead of the dock quietly gaining a hole -- DockBody drops a pin it // cannot resolve, so a bad id is invisible at runtime. function pin(id: string): bool { const wanted = String(id ?? "").trim(); if (!wanted || !DesktopEntries.byId(wanted)) return false; const stored = Settings.dockPinned; if (stored.indexOf(wanted) >= 0) return true; DesktopPreferences.set("dockPinned", stored.concat([wanted])); return true; } anchors { top: true; bottom: true; left: true; right: true } color: "transparent" exclusiveZone: 0 exclusionMode: ExclusionMode.Ignore // Blurred by the `^qs-popover` rule in hypr/rules.lua; the scrim is painted // here rather than added to that rule, the same way the cheatsheet does it. WlrLayershell.namespace: "qs-popover-dock-picker" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: root.open ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None // Stays mapped for the length of the close animation, or it vanishes // instantly and only the opening is ever seen. property bool mapped: false visible: root.mapped onOpenChanged: { if (root.open) { unmapTimer.stop(); root.mapped = true; picker.grab(); } else { unmapTimer.restart(); } } Timer { id: unmapTimer interval: Theme.durNormal onTriggered: root.mapped = false } Rectangle { anchors.fill: parent color: Theme.alpha(Theme.bgDark, Theme.overlayAlpha) opacity: root.open ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Theme.durNormal; easing.type: Easing.OutCubic } } MouseArea { anchors.fill: parent onClicked: ShellState.close() } } Rectangle { id: card anchors.horizontalCenter: parent.horizontalCenter // High rather than centred: the list grows downwards as you type, and a // centred card walks up the screen while you are reading it. y: Math.round(parent.height * 0.18) width: Math.min(root.width - 120, 520) height: header.height + picker.implicitHeight + 56 radius: Theme.popoverRadius color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha) border.width: 1 border.color: Theme.alpha(Theme.fg, 0.1) opacity: root.open ? 1 : 0 scale: root.open ? 1 : 0.98 Behavior on opacity { NumberAnimation { duration: Theme.durNormal; easing.type: Easing.OutCubic } } Behavior on scale { NumberAnimation { duration: Theme.durNormal; easing.type: Easing.OutCubic } } PrismEdge { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right inset: Theme.popoverRadius } // Clicks on the card itself must not fall through to the scrim. MouseArea { anchors.fill: parent } Item { id: header anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.margins: 22 height: title.implicitHeight Text { id: title text: "Add app to dock" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge font.weight: Font.DemiBold } Text { anchors.right: parent.right anchors.verticalCenter: title.verticalCenter text: "Esc to close" color: Theme.fgMuted font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall } } DockAppPicker { id: picker anchors.top: header.bottom anchors.topMargin: 16 anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: 22 anchors.rightMargin: 22 pinned: Settings.dockPinned onPicked: id => { root.pin(id); ShellState.close(); } } } // A Shortcut rather than Keys.onEscapePressed on an item: the search box // owns Escape while it has focus (it clears the query first), and a key // handler on an ancestor would never see the second press. Shortcut { sequence: "Escape" enabled: root.open onActivated: ShellState.close() } }