Make Shell a category, the bar legible, and the dock a real dock
Desktop & Dock becomes Shell — Bar, Dock, Control Center, Tiling, Workspaces — the home for everything Quickshell draws. The settings- management cluster moves to System as Sync & Backup, Appearance's Shell tab dissolves, and 24-hour time finally lives on Date & Time, which always owned it. The bar gets what it never had: a way to survive the wallpaper. A second neutral text family (follow theme, or forced light or dark), a one-layer shadow under every glyph, and a gradient scrim for wallpapers nothing else survives — all off by default, pixel-identical until asked. Widgets earn toggles (weather, media, clipboard, calendar countdown), the vitals cluster stops leaving a dead pill behind, and Control Center's sections learn to step aside. The dock graduates from MVP: a context menu with window rows, pin, unpin, quit and new-window; scroll an icon to cycle its windows; drag to reorder on the dock itself; hover previews with one-shot captures; and "Add App to Dock" in the launcher. Three real bugs died en route — menus that slid away with the autohide, a readonly-property crash on every menu open, and a drag that drifted half a slot per icon on side docks. The pinned-apps editor in Settings becomes a drag strip. 166 contracts; the full suite is green except two live display and switcher tests that cannot run behind a locked session — re-verified on unlock. Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
// "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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user