// The first thing a new machine shows. // // Panama has fifteen settings categories and thirty-odd pages inside them, // which is the opposite of the usual problem: somebody arriving from GNOME, // macOS or Windows cannot tell which four things matter. This is those four // things, once, on the first start. // // Deliberately not a tour. Nobody reads a tour, and a multi-step wizard on a // desktop somebody just installed is another thing standing between them and // using it. One screen, the handful of keys that unlock everything else, and a // way out. // // The chords are read from the live keymap rather than written here, so a // machine whose owner has already rebound something shows what they actually // have -- and so this cannot drift the way a hand-written list would. // // Shown once, tracked by the `welcomeSeen` preference. It stays reachable // afterwards from the launcher and from Settings > System > About, because // the moment somebody wants it again is exactly the moment a one-shot has // thrown it away. import Quickshell import Quickshell.Wayland import QtQuick import qs.config import qs.services import qs.widgets import qs.modules.settings PanelWindow { id: root readonly property bool open: ShellState.welcomeOpen // What to teach, in the order it becomes useful. Each names a bind by the // description hypr/keybinds.lua gives it, so the chord shown is whatever // that bind currently answers to. readonly property var lessons: [ { find: "Launcher", fallback: "Super + Space", title: "Find anything", detail: "Applications, files, clipboard history, emoji, a calculator. Start typing." }, { find: "Terminal", fallback: "Super + T", title: "Open a terminal", detail: "Windows tile themselves as they open. Nothing needs arranging." }, { find: "Overview", fallback: "Super + `", title: "See every window", detail: "All your workspaces at once, and search across them." }, { find: "Settings", fallback: "Super + I", title: "Change anything", detail: "Displays, sound, network, appearance, and everything else." }, { find: "Keyboard shortcuts", fallback: "Super + /", title: "The rest of the keys", detail: "Every shortcut this desktop has, whenever you want it." } ] function chordFor(description: string, fallback: string): string { for (const bind of Keybinds.binds) { if (bind.description === description) return bind.chord; } return fallback; } anchors { top: true; bottom: true; left: true; right: true } color: "transparent" exclusiveZone: 0 exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "qs-popover-welcome" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: root.open ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None property bool mapped: false visible: root.mapped onOpenChanged: { if (root.open) { unmapTimer.stop(); root.mapped = true; Keybinds.refresh(); } else { unmapTimer.restart(); } } Timer { id: unmapTimer interval: Theme.durNormal onTriggered: root.mapped = false } // Dismissing is what marks it seen. Closing by any route counts: somebody // who pressed Escape has seen it, and showing it again next login would be // the desktop failing to take no for an answer. function dismiss(): void { DesktopPreferences.set("welcomeSeen", true); ShellState.close(); } // First start on a fresh install. Waits for the keymap so the chords are // real rather than fallbacks, and gives the rest of the shell a moment to // finish coming up -- appearing over a half-drawn desktop reads as a crash. Timer { id: firstStart interval: 2500 running: !DesktopPreferences.get("welcomeSeen") onTriggered: { if (!DesktopPreferences.get("welcomeSeen") && !ShellState.anyOverlayOpen) ShellState.open("welcome"); } } 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 } } // No click-outside-to-close. Everything else on this desktop dismisses // that way, but this is the one surface where a stray click during the // first thirty seconds would throw away the only explanation on offer. MouseArea { anchors.fill: parent } } Rectangle { id: card anchors.centerIn: parent width: Math.min(root.width - 120, 640) height: Math.min(root.height - 120, content.implicitHeight + 64) 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 } Column { id: content anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.margins: 32 spacing: 22 Column { width: parent.width spacing: 6 Text { text: "Welcome to Panama" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeTitle font.weight: Font.DemiBold } Text { width: parent.width text: "Five keys, and you have the whole desktop." color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSize wrapMode: Text.WordWrap } } Column { width: parent.width spacing: 14 Repeater { model: root.lessons Item { required property var modelData width: parent.width height: Math.max(chordPill.height, lessonText.implicitHeight) Rectangle { id: chordPill width: 150 height: chordLabel.implicitHeight + 12 radius: Theme.pillRadius color: Theme.alpha(Theme.fg, 0.07) Text { id: chordLabel anchors.centerIn: parent text: root.chordFor(modelData.find, modelData.fallback) color: Theme.accent font.family: Theme.fontMono font.pixelSize: Theme.fontSizeSmall font.features: Theme.tabularFigures } } Column { id: lessonText anchors.left: chordPill.right anchors.leftMargin: 16 anchors.right: parent.right anchors.verticalCenter: chordPill.verticalCenter spacing: 2 Text { width: parent.width text: modelData.title color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSize font.weight: Font.Medium } Text { width: parent.width text: modelData.detail color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall wrapMode: Text.WordWrap } } } } } Row { anchors.right: parent.right spacing: 8 SettingsButton { text: "Show every shortcut" onClicked: { DesktopPreferences.set("welcomeSeen", true); ShellState.open("cheatsheet"); } } SettingsButton { text: "Start using it" tone: "accent" onClicked: root.dismiss() } } } } Item { anchors.fill: parent focus: true Keys.onEscapePressed: root.dismiss() } }