diff --git a/ags/lib/settings.ts b/ags/lib/settings.ts index 6ac2def..6372606 100644 --- a/ags/lib/settings.ts +++ b/ags/lib/settings.ts @@ -26,6 +26,18 @@ export let config = { }, }, lockCommand: opt(null), + powerMenuEntries: opt>([ + { + label: "Restart", + command: "reboot", + confirmation: "Are you sure you want to restart the computer?", + }, + { + label: "Power Off", + command: "shutdown now", + confirmation: "Are you sure you want to power off the computer?", + }, + ]), minWorkspaces: opt(3), popups: { volumePopup: { diff --git a/ags/quicksettings/power-menu.ts b/ags/quicksettings/power-menu.ts new file mode 100644 index 0000000..9c2cc99 --- /dev/null +++ b/ags/quicksettings/power-menu.ts @@ -0,0 +1,77 @@ +import type Gtk from "gi://Gtk?version=3.0"; +import { config } from "lib/settings"; +import type { Binding } from "types/service.js"; +import { PopupWindow, showModal } from "window"; + +/** + * Power menu that lists multiple power options. + * + * @param reveal - Variable that stores the reveal state of the power menu. + */ +export const PowerMenu = (params: { + reveal?: Binding; +}) => { + const entries = config.powerMenuEntries.map(({ label, command, confirmation }) => + Widget.Button({ + className: "entry", + hexpand: true, + child: Widget.Box({ + children: [ + Widget.Label({ + label: `${label}...`, + }), + ], + }), + async onClicked() { + App.closeWindow("quicksettings"); + + const confirmed = confirmation + ? await showModal({ + title: label, + description: confirmation, + noOption: "Cancel", + yesOption: label, + emphasize: "no", + }) + : true; + + if (confirmed) { + await Utils.execAsync(command); + } + }, + }), + ); + + return Widget.Revealer({ + hexpand: false, + transition: "slide_down", + transitionDuration: 150, + revealChild: params.reveal, + child: Widget.Box({ + className: "power-menu", + hexpand: true, + vexpand: false, + vertical: true, + children: [ + Widget.Box({ + className: "title", + children: [ + Widget.Icon({ + className: "icon", + icon: "system-shutdown-symbolic", + size: 20, + }), + Widget.Label({ + label: "Power Off", + }), + ], + }), + Widget.Box({ + className: "entries", + vertical: true, + children: entries, + }), + ], + }), + }); +}; diff --git a/ags/quicksettings/quicksettings.ts b/ags/quicksettings/quicksettings.ts index fca8135..5e46918 100644 --- a/ags/quicksettings/quicksettings.ts +++ b/ags/quicksettings/quicksettings.ts @@ -2,6 +2,7 @@ import { config } from "lib/settings"; import type { Icon } from "lib/types"; import type { Binding } from "types/service"; import { PopupWindow, showModal } from "window"; +import { PowerMenu } from "./power-menu"; import { Sliders } from "./sliders"; import { Toggles } from "./toggles"; @@ -40,6 +41,7 @@ const Button = (props: { export const Quicksettings = () => { // Used to take a screenshot without including the quicksettings menu. const opacity = Variable(1.0); + const revealPowerMenu = Variable(false); const top_button_battery = Button({ icon: battery.bind("icon_name"), @@ -96,24 +98,12 @@ export const Quicksettings = () => { Button({ icon: "system-shutdown-symbolic", async onClick() { - App.closeWindow("quicksettings"); - - const shutdown = await showModal({ - title: "Power Off", - description: "Are you sure you want to power off the computer?", - noOption: "Cancel", - yesOption: "Power Off", - emphasize: "no", - }); - - if (shutdown) { - await Utils.execAsync("shutdown now"); - } + revealPowerMenu.value = !revealPowerMenu.value; }, }), ]; - return PopupWindow({ + const window = PopupWindow({ name: "quicksettings", location: "top-right", child: Widget.Box({ @@ -138,10 +128,22 @@ export const Quicksettings = () => { }), }), + PowerMenu({ reveal: revealPowerMenu.bind() }), + Sliders(), Toggles(), ], }), }); + + window.hook( + App, + () => { + revealPowerMenu.value = false; + }, + "window-toggled", + ); + + return window; }; diff --git a/ags/style.scss b/ags/style.scss index 54343c3..6ca6358 100644 --- a/ags/style.scss +++ b/ags/style.scss @@ -200,11 +200,6 @@ window.darkened { margin-left: 0.5em; margin-bottom: 0.5em; - label { - font-weight: bold; - font-size: 14px; - } - .button-row { @include space-between-x(3em); @@ -232,6 +227,48 @@ window.darkened { } } + .power-menu { + @include space-between-y(0.625em); + + margin-top: 1.25em; + padding: 0.625em; + border-radius: 25px; + background-color: hover-color($background1); + + .title { + .icon { + @include rounded-full; + padding: 0.625em; + background-color: hover-color($surface0); + margin-right: 0.625em; + } + + label { + font-size: 1.25em; + font-weight: bold; + } + } + + .entries { + .entry { + @include rounded-full; + padding: 0.625em; + + &:hover { + background-color: hover-color($surface0); + } + + &:active { + background-color: $primary; + + * { + color: $background1; + } + } + } + } + } + .sliders { @include space-between-y(1.5em); } @@ -271,6 +308,11 @@ window.darkened { } .toggle-buttons { + label { + font-weight: bold; + font-size: 14px; + } + .toggle-button { border-top-left-radius: 9999px; border-bottom-left-radius: 9999px; @@ -385,4 +427,4 @@ window.darkened { } } -} \ No newline at end of file +} diff --git a/modules/mithril-shell.nix b/modules/mithril-shell.nix index ae40e8c..98be01a 100644 --- a/modules/mithril-shell.nix +++ b/modules/mithril-shell.nix @@ -110,6 +110,52 @@ in ''; }; + powerMenuEntries = mkOption { + type = types.listOf ( + types.submodule { + options = { + label = mkOption { + type = types.str; + example = "Power Off"; + description = '' + The name of the power menu entry. + ''; + }; + command = mkOption { + type = types.str; + example = "shutdown now"; + description = '' + The command to run when selecting the option. + ''; + }; + confirmation = mkOption { + type = types.nullOr types.str; + default = null; + example = "Are you sure you want to power off the computer?"; + description = '' + An optional confirmation prompt to show before running the command. + ''; + }; + }; + } + ); + description = '' + List of entries to show in the power menu. + ''; + default = [ + { + label = "Restart"; + command = "reboot"; + confirmation = "Are you sure you want to restart the computer?"; + } + { + label = "Power Off"; + command = "shutdown now"; + confirmation = "Are you sure you want to power off the computer?"; + } + ]; + }; + minWorkspaces = mkOption { type = types.int; default = 3;