aboutsummaryrefslogtreecommitdiffstats
path: root/appearance/StepsWindow.qml
blob: fef1d8966b7c767611a0ee1edcfbef45077a2b95 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// 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

// 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.name
                                    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)
                    }
                }
            }
        }
    }
}