Krolyxon
Commit ff7bd95

Branches

Refs →

aurora.configquickshellauroraPowerMenu.qml

185 lines · 6.9 KB · QML

1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Wayland
5import Quickshell.Hyprland
6
7// Session / power actions (was the fuzzel `sysact` script). Type to fuzzy filter, Up/Down/Tab pick,
8// Enter runs, Esc closes. Destructive ones need a second Enter/click ("sure?").
9PanelWindow {
10 id: win
11 property bool opened: false
12 signal closeRequested()
13
14 visible: opened
15 implicitWidth: 300
16 // fixed to the full list so the centred window doesn't jump while filtering
17 implicitHeight: 24 + 36 + actions.length * 32 + 22
18 exclusiveZone: 0
19 color: "transparent"
20 WlrLayershell.layer: WlrLayer.Overlay
21 WlrLayershell.namespace: "aurora-power"
22 WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
23
24 HyprlandFocusGrab {
25 windows: [win]
26 active: win.opened
27 onCleared: win.closeRequested()
28 }
29
30 readonly property var actions: Session.actions
31
32 property int current: 0
33 property string armed: ""
34 Timer { id: disarm; interval: 3000; onTriggered: win.armed = "" }
35
36 onOpenedChanged: {
37 if (!opened) return
38 filter.text = ""
39 current = 0
40 armed = ""
41 filter.forceActiveFocus()
42 }
43
44 readonly property var shown: {
45 var q = filter.text.toLowerCase().trim(), out = []
46 for (var i = 0; i < actions.length; i++) {
47 var sc = Fuzzy.score(actions[i].label, q)
48 if (sc > 0) out.push({ a: actions[i], sc: sc, i: i })
49 }
50 out.sort(function (x, y) { return y.sc - x.sc || x.i - y.i })
51 var res = []
52 for (var j = 0; j < out.length; j++) res.push(out[j].a)
53 return res
54 }
55
56 function trigger(i) {
57 var a = shown[i]
58 if (!a) return
59 current = i
60 if (a.confirm && armed !== a.id) { armed = a.id; disarm.restart(); return }
61 armed = ""
62 closeRequested()
63 Session.run(a.id)
64 }
65
66 Rectangle {
67 anchors.fill: parent
68 color: Theme.bgA
69 border.width: 2
70 border.color: Theme.accent
71
72 ColumnLayout {
73 id: col
74 anchors.fill: parent
75 anchors.margins: 12
76 spacing: 2
77
78 Rectangle {
79 Layout.fillWidth: true
80 Layout.bottomMargin: 6
81 implicitHeight: 30
82 color: Theme.surface
83 border.width: 1
84 border.color: filter.activeFocus ? Theme.accent : Theme.line
85 Text {
86 anchors.left: parent.left
87 anchors.leftMargin: 10
88 anchors.verticalCenter: parent.verticalCenter
89 text: Theme.iPower
90 color: Theme.dim
91 font.family: Theme.font
92 font.pixelSize: Theme.fontSize
93 }
94 TextInput {
95 id: filter
96 anchors.fill: parent
97 anchors.leftMargin: 32
98 anchors.rightMargin: 10
99 verticalAlignment: TextInput.AlignVCenter
100 color: Theme.fg
101 selectionColor: Theme.accent
102 selectedTextColor: Theme.bg
103 font.family: Theme.font
104 font.pixelSize: Theme.fontSize
105 clip: true
106 onTextChanged: { win.current = 0; win.armed = "" }
107 Keys.onEscapePressed: win.closeRequested()
108 Keys.onReturnPressed: win.trigger(win.current)
109 Keys.onEnterPressed: win.trigger(win.current)
110 Keys.onPressed: event => {
111 var n = win.shown.length
112 if (!n) return
113 if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) {
114 win.current = (win.current + 1) % n; win.armed = ""; event.accepted = true
115 } else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) {
116 win.current = (win.current + n - 1) % n; win.armed = ""; event.accepted = true
117 }
118 }
119 }
120 }
121
122 Repeater {
123 model: win.shown
124 delegate: Rectangle {
125 id: row
126 required property var modelData
127 required property int index
128 readonly property bool selected: win.current === index
129 readonly property bool isArmed: win.armed === modelData.id
130 Layout.fillWidth: true
131 implicitHeight: 30
132 color: isArmed ? Theme.err : (selected ? Theme.surfaceHi : (rm.containsMouse ? Theme.surface : "transparent"))
133 border.width: selected ? 1 : 0
134 border.color: isArmed ? Theme.err : Theme.accent
135
136 Rectangle { // selection marker, like ClipWindow
137 visible: row.selected
138 width: 2
139 height: parent.height
140 color: row.isArmed ? Theme.bg : Theme.accent
141 }
142 Text {
143 id: ico
144 anchors.left: parent.left
145 anchors.leftMargin: 12
146 anchors.verticalCenter: parent.verticalCenter
147 width: 18
148 text: row.modelData.icon
149 color: row.isArmed ? Theme.bg : Theme.accent
150 font.family: Theme.font
151 font.pixelSize: Theme.fontSize + 2
152 }
153 Text {
154 anchors.left: ico.right
155 anchors.leftMargin: 10
156 anchors.verticalCenter: parent.verticalCenter
157 text: row.isArmed ? row.modelData.label + " sure?" : row.modelData.label
158 color: row.isArmed ? Theme.bg : Theme.fg
159 font.family: Theme.font
160 font.pixelSize: Theme.fontSize
161 }
162 MouseArea {
163 id: rm
164 anchors.fill: parent
165 hoverEnabled: true
166 cursorShape: Qt.PointingHandCursor
167 onEntered: if (win.current !== row.index) { win.current = row.index; win.armed = "" }
168 onClicked: win.trigger(row.index)
169 }
170 }
171 }
172
173 Item { Layout.fillHeight: true }
174
175 Text {
176 Layout.fillWidth: true
177 Layout.topMargin: 6
178 text: win.shown.length ? "type filter enter run esc close" : "no match"
179 color: win.shown.length ? Theme.dim : Theme.err
180 font.family: Theme.font
181 font.pixelSize: Theme.fontSize - 2
182 }
183 }
184 }
185}