Skip to content

Commit fb2f05b

Browse files
authored
feat: add power menu (#24)
1 parent b696a79 commit fb2f05b

File tree

5 files changed

+199
-20
lines changed

5 files changed

+199
-20
lines changed

ags/lib/settings.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ export let config = {
2626
},
2727
},
2828
lockCommand: opt<string | null>(null),
29+
powerMenuEntries: opt<Array<{ label: string; command: string; confirmation?: string }>>([
30+
{
31+
label: "Restart",
32+
command: "reboot",
33+
confirmation: "Are you sure you want to restart the computer?",
34+
},
35+
{
36+
label: "Power Off",
37+
command: "shutdown now",
38+
confirmation: "Are you sure you want to power off the computer?",
39+
},
40+
]),
2941
minWorkspaces: opt<number>(3),
3042
popups: {
3143
volumePopup: {

ags/quicksettings/power-menu.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type Gtk from "gi://Gtk?version=3.0";
2+
import { config } from "lib/settings";
3+
import type { Binding } from "types/service.js";
4+
import { PopupWindow, showModal } from "window";
5+
6+
/**
7+
* Power menu that lists multiple power options.
8+
*
9+
* @param reveal - Variable that stores the reveal state of the power menu.
10+
*/
11+
export const PowerMenu = (params: {
12+
reveal?: Binding<any, any, boolean>;
13+
}) => {
14+
const entries = config.powerMenuEntries.map(({ label, command, confirmation }) =>
15+
Widget.Button({
16+
className: "entry",
17+
hexpand: true,
18+
child: Widget.Box({
19+
children: [
20+
Widget.Label({
21+
label: `${label}...`,
22+
}),
23+
],
24+
}),
25+
async onClicked() {
26+
App.closeWindow("quicksettings");
27+
28+
const confirmed = confirmation
29+
? await showModal({
30+
title: label,
31+
description: confirmation,
32+
noOption: "Cancel",
33+
yesOption: label,
34+
emphasize: "no",
35+
})
36+
: true;
37+
38+
if (confirmed) {
39+
await Utils.execAsync(command);
40+
}
41+
},
42+
}),
43+
);
44+
45+
return Widget.Revealer({
46+
hexpand: false,
47+
transition: "slide_down",
48+
transitionDuration: 150,
49+
revealChild: params.reveal,
50+
child: Widget.Box({
51+
className: "power-menu",
52+
hexpand: true,
53+
vexpand: false,
54+
vertical: true,
55+
children: [
56+
Widget.Box({
57+
className: "title",
58+
children: [
59+
Widget.Icon({
60+
className: "icon",
61+
icon: "system-shutdown-symbolic",
62+
size: 20,
63+
}),
64+
Widget.Label({
65+
label: "Power Off",
66+
}),
67+
],
68+
}),
69+
Widget.Box({
70+
className: "entries",
71+
vertical: true,
72+
children: entries,
73+
}),
74+
],
75+
}),
76+
});
77+
};

ags/quicksettings/quicksettings.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { config } from "lib/settings";
22
import type { Icon } from "lib/types";
33
import type { Binding } from "types/service";
44
import { PopupWindow, showModal } from "window";
5+
import { PowerMenu } from "./power-menu";
56
import { Sliders } from "./sliders";
67
import { Toggles } from "./toggles";
78

@@ -40,6 +41,7 @@ const Button = (props: {
4041
export const Quicksettings = () => {
4142
// Used to take a screenshot without including the quicksettings menu.
4243
const opacity = Variable(1.0);
44+
const revealPowerMenu = Variable(false);
4345

4446
const top_button_battery = Button({
4547
icon: battery.bind("icon_name"),
@@ -96,24 +98,12 @@ export const Quicksettings = () => {
9698
Button({
9799
icon: "system-shutdown-symbolic",
98100
async onClick() {
99-
App.closeWindow("quicksettings");
100-
101-
const shutdown = await showModal({
102-
title: "Power Off",
103-
description: "Are you sure you want to power off the computer?",
104-
noOption: "Cancel",
105-
yesOption: "Power Off",
106-
emphasize: "no",
107-
});
108-
109-
if (shutdown) {
110-
await Utils.execAsync("shutdown now");
111-
}
101+
revealPowerMenu.value = !revealPowerMenu.value;
112102
},
113103
}),
114104
];
115105

116-
return PopupWindow({
106+
const window = PopupWindow({
117107
name: "quicksettings",
118108
location: "top-right",
119109
child: Widget.Box({
@@ -138,10 +128,22 @@ export const Quicksettings = () => {
138128
}),
139129
}),
140130

131+
PowerMenu({ reveal: revealPowerMenu.bind() }),
132+
141133
Sliders(),
142134

143135
Toggles(),
144136
],
145137
}),
146138
});
139+
140+
window.hook(
141+
App,
142+
() => {
143+
revealPowerMenu.value = false;
144+
},
145+
"window-toggled",
146+
);
147+
148+
return window;
147149
};

ags/style.scss

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,6 @@ window.darkened {
200200
margin-left: 0.5em;
201201
margin-bottom: 0.5em;
202202

203-
label {
204-
font-weight: bold;
205-
font-size: 14px;
206-
}
207-
208203
.button-row {
209204
@include space-between-x(3em);
210205

@@ -232,6 +227,48 @@ window.darkened {
232227
}
233228
}
234229

230+
.power-menu {
231+
@include space-between-y(0.625em);
232+
233+
margin-top: 1.25em;
234+
padding: 0.625em;
235+
border-radius: 25px;
236+
background-color: hover-color($background1);
237+
238+
.title {
239+
.icon {
240+
@include rounded-full;
241+
padding: 0.625em;
242+
background-color: hover-color($surface0);
243+
margin-right: 0.625em;
244+
}
245+
246+
label {
247+
font-size: 1.25em;
248+
font-weight: bold;
249+
}
250+
}
251+
252+
.entries {
253+
.entry {
254+
@include rounded-full;
255+
padding: 0.625em;
256+
257+
&:hover {
258+
background-color: hover-color($surface0);
259+
}
260+
261+
&:active {
262+
background-color: $primary;
263+
264+
* {
265+
color: $background1;
266+
}
267+
}
268+
}
269+
}
270+
}
271+
235272
.sliders {
236273
@include space-between-y(1.5em);
237274
}
@@ -271,6 +308,11 @@ window.darkened {
271308
}
272309

273310
.toggle-buttons {
311+
label {
312+
font-weight: bold;
313+
font-size: 14px;
314+
}
315+
274316
.toggle-button {
275317
border-top-left-radius: 9999px;
276318
border-bottom-left-radius: 9999px;
@@ -385,4 +427,4 @@ window.darkened {
385427

386428
}
387429
}
388-
}
430+
}

modules/mithril-shell.nix

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,52 @@ in
118118
'';
119119
};
120120

121+
powerMenuEntries = mkOption {
122+
type = types.listOf (
123+
types.submodule {
124+
options = {
125+
label = mkOption {
126+
type = types.str;
127+
example = "Power Off";
128+
description = ''
129+
The name of the power menu entry.
130+
'';
131+
};
132+
command = mkOption {
133+
type = types.str;
134+
example = "shutdown now";
135+
description = ''
136+
The command to run when selecting the option.
137+
'';
138+
};
139+
confirmation = mkOption {
140+
type = types.nullOr types.str;
141+
default = null;
142+
example = "Are you sure you want to power off the computer?";
143+
description = ''
144+
An optional confirmation prompt to show before running the command.
145+
'';
146+
};
147+
};
148+
}
149+
);
150+
description = ''
151+
List of entries to show in the power menu.
152+
'';
153+
default = [
154+
{
155+
label = "Restart";
156+
command = "reboot";
157+
confirmation = "Are you sure you want to restart the computer?";
158+
}
159+
{
160+
label = "Power Off";
161+
command = "shutdown now";
162+
confirmation = "Are you sure you want to power off the computer?";
163+
}
164+
];
165+
};
166+
121167
minWorkspaces = mkOption {
122168
type = types.int;
123169
default = 3;

0 commit comments

Comments
 (0)