Krolyxon
Branch master

Branches

Refs →

aurora.configquickshellauroraPowerMenu.qml

193 lines · 7.4 KB · QML

1import QtQuick
2import Quickshell
3import Quickshell.Wayland
4import Quickshell.Hyprland
5
6// Session / power actions (SUPER+Escape, the panel's power button). The screen goes nearly black, the actions are a
7// column of Doto words: the selected one large with the live dot, the rest dim; uptime in a ring of dots on the right.
8// Up/Down/Tab or hover pick, Enter/click runs, typing filters, Esc or a click on the background closes.
9// Destructive ones need a second Enter/click within 3s (the word turns the error colour).
10PanelWindow {
11 id: win
12 property bool opened: false
13 signal closeRequested()
14
15 property real t: opened ? 1 : 0
16 Behavior on t { NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide } }
17
18 visible: t > 0
19 anchors { top: true; bottom: true; left: true; right: true }
20 exclusionMode: ExclusionMode.Ignore
21 color: "transparent"
22 WlrLayershell.layer: WlrLayer.Overlay
23 WlrLayershell.namespace: "aurora-power"
24 WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
25
26 readonly property var actions: Session.actions
27 // short words for the big type (Session's labels stay for the runner)
28 readonly property var words: ({ leave: "log out", renew: "reload", display: "screen off", shutdown: "shut down" })
29 function word(a) { return words[a.id] || a.label }
30
31 property int current: 0
32 property string armed: ""
33 Timer { id: disarm; interval: 3000; onTriggered: win.armed = "" }
34
35 onOpenedChanged: {
36 if (!opened) return
37 filter.text = ""
38 current = Math.max(0, shown.indexOf(shown.filter(a => a.id === "shutdown")[0]))
39 armed = ""
40 filter.forceActiveFocus()
41 }
42
43 readonly property var shown: {
44 var q = filter.text.toLowerCase().trim(), out = []
45 for (var i = 0; i < actions.length; i++) {
46 var sc = Math.max(Fuzzy.score(actions[i].label, q), Fuzzy.score(word(actions[i]), q))
47 if (sc > 0) out.push({ a: actions[i], sc: sc, i: i })
48 }
49 if (q !== "") out.sort(function (x, y) { return y.sc - x.sc || x.i - y.i })
50 var res = []
51 for (var j = 0; j < out.length; j++) res.push(out[j].a)
52 return res
53 }
54
55 function trigger(i) {
56 var a = shown[i]
57 if (!a) return
58 current = i
59 if (a.confirm && armed !== a.id) { armed = a.id; disarm.restart(); return }
60 armed = ""
61 closeRequested()
62 Session.run(a.id)
63 }
64
65 SystemClock { id: clock; precision: SystemClock.Minutes }
66
67 Rectangle {
68 anchors.fill: parent
69 color: Theme.fade(Theme.bg, 0.96)
70 opacity: win.t
71 MouseArea { anchors.fill: parent; onClicked: win.closeRequested() }
72 }
73
74 // typing filters; the field itself stays invisible, the filter shows as a label
75 TextInput {
76 id: filter
77 width: 0
78 height: 0
79 opacity: 0
80 onTextChanged: { win.current = 0; win.armed = "" }
81 Keys.onEscapePressed: win.closeRequested()
82 Keys.onReturnPressed: win.trigger(win.current)
83 Keys.onEnterPressed: win.trigger(win.current)
84 Keys.onPressed: event => {
85 var n = win.shown.length
86 if (!n) return
87 if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) {
88 win.current = (win.current + 1) % n; win.armed = ""; event.accepted = true
89 } else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) {
90 win.current = (win.current + n - 1) % n; win.armed = ""; event.accepted = true
91 }
92 }
93 }
94
95 // the 1920 x 1080 canvas, scaled to the screen
96 Item {
97 id: stage
98 readonly property real s: Math.min(win.width / 1920, win.height / 1080)
99 width: win.width / s
100 height: win.height / s
101 scale: s
102 transformOrigin: Item.TopLeft
103 opacity: win.t
104
105 Lb {
106 x: 240
107 y: 90
108 visible: filter.text !== ""
109 px: 14
110 color: Theme.fg
111 text: "filter · " + filter.text + (win.shown.length ? "" : " · no match")
112 }
113
114 Column {
115 x: 240 - 30 * (1 - win.t)
116 anchors.verticalCenter: parent.verticalCenter
117 spacing: 20
118 Repeater {
119 model: win.shown
120 Row {
121 id: it
122 required property var modelData
123 required property int index
124 readonly property bool on: win.current === index
125 readonly property bool isArmed: win.armed === modelData.id
126 spacing: 30
127 Rectangle {
128 anchors.verticalCenter: parent.verticalCenter
129 visible: it.on
130 width: 30
131 height: 30
132 radius: 15
133 color: it.isArmed ? Theme.err : Theme.live
134 }
135 Num {
136 text: win.word(it.modelData).toUpperCase()
137 font.pixelSize: it.on ? 128 : 56
138 color: it.isArmed ? Theme.err : it.on ? Theme.fg : Theme.mute
139 Behavior on font.pixelSize {
140 enabled: Theme.motion
141 NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide }
142 }
143 Behavior on color { enabled: Theme.motion; ColorAnimation { duration: Theme.t } }
144 }
145 Lb {
146 visible: it.isArmed
147 anchors.verticalCenter: parent.verticalCenter
148 px: 14
149 color: Theme.err
150 text: "↵ again to " + win.word(it.modelData)
151 }
152 HoverHandler { onHoveredChanged: if (hovered && win.current !== it.index) { win.current = it.index; win.armed = "" } }
153 TapHandler { onTapped: win.trigger(it.index) }
154 }
155 }
156 }
157
158 Item {
159 x: stage.width - 200 - 440
160 anchors.verticalCenter: parent.verticalCenter
161 width: 440
162 height: 440
163 Ring { anchors.fill: parent; n: 60; dot: 10; f: SysStats.uptime / (6 * 3600) }
164 Column {
165 anchors.centerIn: parent
166 Lb { anchors.horizontalCenter: parent.horizontalCenter; text: "uptime" }
167 Num {
168 anchors.horizontalCenter: parent.horizontalCenter
169 topPadding: lead + 10
170 bottomPadding: lead + 10
171 text: Math.floor(SysStats.uptime / 3600) + ":" + ("0" + Math.floor(SysStats.uptime / 60) % 60).slice(-2)
172 font.pixelSize: 104
173 }
174 Lb {
175 anchors.horizontalCenter: parent.horizontalCenter
176 readonly property int n: Hyprland.toplevels.values.length
177 text: n + (n === 1 ? " window open" : " windows open")
178 }
179 }
180 }
181
182 Row {
183 x: 240
184 anchors.bottom: parent.bottom
185 anchors.bottomMargin: 70
186 spacing: 34
187 Lb { text: "↑↓ select" }
188 Lb { text: "↵ confirm" }
189 Lb { text: "type to filter" }
190 Lb { text: "esc cancel" }
191 }
192 }
193}