diff options
Diffstat (limited to 'desktop/modules/appearance/StepsWindow.qml')
| -rw-r--r-- | desktop/modules/appearance/StepsWindow.qml | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/desktop/modules/appearance/StepsWindow.qml b/desktop/modules/appearance/StepsWindow.qml new file mode 100644 index 0000000..7349fcd --- /dev/null +++ b/desktop/modules/appearance/StepsWindow.qml @@ -0,0 +1,198 @@ +// Copyright (C) 2026 Danilo M. <danix@danix.xyz> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +import Quickshell +import Quickshell.Wayland +import QtQuick +import "../.." + +// One tab per consumer with manual steps pending. Centred on the vertical +// screen and kept open on purpose: the commands are pasted into a root shell +// elsewhere, so the window takes keyboard focus only when clicked and stays +// until closed. Done deletes a tab's file; the producer also clears it once +// the consumer is up to date. +Scope { + id: root + + property string monitor: "DP-3" + property bool open: false + property string tab: "" + + readonly property var screenObj: + Quickshell.screens.find(s => s.name === root.monitor) ?? Quickshell.screens[0] + readonly property var current: Steps.tabs.find(t => t.name === root.tab) ?? Steps.tabs[0] ?? null + + // A poke from udt-steps: reload, and open when something is pending. + // Also what a keybind calls to bring the window back after closing it. + function poke() { + root.opening = true; + Steps.refresh(); + } + property bool opening: false + + Connections { + target: Steps + function onLoaded() { + if (Steps.tabs.length === 0) root.open = false; + else if (root.opening) root.open = true; + root.opening = false; + } + } + + LazyLoader { + active: root.open + + PanelWindow { + id: win + screen: root.screenObj + // No anchors: a layer surface anchored nowhere is centred. + implicitWidth: 960 + implicitHeight: body.implicitHeight + 40 + color: "transparent" + exclusionMode: ExclusionMode.Ignore + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.namespace: "quickshell-steps" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + + Rectangle { + anchors.fill: parent + radius: 14 + color: Qt.alpha(Theme.base, 0.92) + border.width: 1 + border.color: Qt.alpha(Theme.text, 0.12) + + Item { + anchors.fill: parent + focus: true + Keys.onEscapePressed: root.open = false + } + + Column { + id: body + anchors { top: parent.top; left: parent.left; right: parent.right; margins: 20 } + spacing: 16 + + Item { + width: parent.width + height: tabBar.implicitHeight + + Row { + id: tabBar + spacing: 8 + Repeater { + model: Steps.tabs + Tab { + required property var modelData + text: modelData.label + selected: root.current !== null && root.current.name === modelData.name + onClicked: root.tab = modelData.name + } + } + } + + Tab { + anchors.right: parent.right + text: "Close" + onClicked: root.open = false + } + } + + Flickable { + width: parent.width + // Sized from the screen, never from the window: the + // window's height comes from this, and a binding back + // to it collapsed the whole thing to its margins. + height: Math.min(content.implicitHeight, + win.screen.height - 240 - tabBar.implicitHeight - actions.implicitHeight - 32) + contentHeight: content.implicitHeight + clip: true + + Column { + id: content + width: parent.width + spacing: 12 + + Repeater { + model: root.current ? root.current.blocks : [] + + Loader { + required property var modelData + width: content.width + sourceComponent: modelData.cmd ? cmdBlock : prose + property var block: modelData + } + } + } + } + + Row { + id: actions + spacing: 8 + anchors.right: parent.right + Tab { + text: "Copy all commands" + enabled: root.current !== null && Steps.commands(root.current) !== "" + onClicked: Steps.copy(Steps.commands(root.current)) + } + Tab { + text: "Done" + enabled: root.current !== null + onClicked: Steps.dismiss(root.current.name) + } + } + } + } + + Component { + id: prose + Text { + text: block.text + wrapMode: Text.Wrap + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1 } + color: Theme.text + } + } + + Component { + id: cmdBlock + Rectangle { + implicitHeight: Math.max(code.implicitHeight, copyBtn.implicitHeight) + 24 + radius: 8 + color: Qt.alpha(Theme.crust, 0.8) + border.width: 1 + border.color: Qt.alpha(Theme.accent, 0.3) + + // Selectable as well as copyable: sometimes one line of a + // block is what is wanted. + TextEdit { + id: code + anchors { left: parent.left; right: copyBtn.left; verticalCenter: parent.verticalCenter; margins: 12 } + text: block.text + readOnly: true + selectByMouse: true + wrapMode: TextEdit.WrapAnywhere + font { family: "Inconsolata Nerd Font Mono"; pixelSize: Theme.fontSize - 1 } + color: Theme.text + selectionColor: Qt.alpha(Theme.accent, 0.4) + selectedTextColor: Theme.text + } + + Tab { + id: copyBtn + anchors { right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter } + text: "Copy" + onClicked: Steps.copy(block.text) + } + } + } + } + } +} |
