Reworked toggle buttons and added power menu

This commit is contained in:
2025-02-07 22:09:12 -08:00
parent 0fea2069f1
commit ffc8737756
16 changed files with 281 additions and 109 deletions

View File

@@ -9,6 +9,7 @@ import client from "./client"
import player from "./player"
import bluetooth from "./bluetooth"
import notifs from "./notif"
import power from "./power"
const time = Variable("").poll(1000, "date +'%a %b %d · %H:%M'")
@@ -21,11 +22,13 @@ export default function Bar(monitor: Hyprland.Monitor) {
monitor={monitor.id}
exclusivity={Astal.Exclusivity.IGNORE}
anchor={TOP | LEFT | RIGHT}
heightRequest={36}
layer={Astal.Layer.TOP}
application={App}>
<centerbox>
<eventbox
onHover={self => self.css="box-shadow: 0 0 16px 4px black;"}
><centerbox heightRequest={36}>
<box>
{power()}
{battery()}
{volume()}
{brightness()}
@@ -43,6 +46,6 @@ export default function Bar(monitor: Hyprland.Monitor) {
</box></button>
{notifs()}
</box>
</centerbox>
</centerbox></eventbox>
</window>
}

View File

@@ -19,17 +19,19 @@ function profileButton(label: string, profile: string) {
return <button className="toggleButton" onClicked={(self) => {
powerProfiles.activeProfile = profile
}}>
<overlay overlay={new Widget.Box({
halign: Gtk.Align.END,
valign: Gtk.Align.CENTER,
className: bind(powerProfiles, "activeProfile").as((p) =>
<box halign={Gtk.Align.FILL}>
<label halign={Gtk.Align.START} hexpand>{label}</label>
<box
halign={Gtk.Align.END}
valign={Gtk.Align.CENTER}
className={bind(powerProfiles, "activeProfile").as((p) =>
(p === profile)
? "toggleIndicator active"
: "toggleIndicator inactive"
)
})}>
<label halign={Gtk.Align.START}>{label}</label>
</overlay>
}
/>
</box>
</button>
}
@@ -63,47 +65,40 @@ function BattWindow() {
/>
</overlay>
<box vertical={true} spacing={10}>
<overlay overlay={new Widget.Box({
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
vertical: true,
children: [
new Widget.Label({
className: "bigText",
label: bind(batt, "energyRate").as((w) => `${w}W`),
}),
new Widget.Label({
className: "smallText",
label: bind(batt, "charging").as(
(c) => c ? "Charging" : "Discharging"
),
}),
]
})}>
<box className="info" />
</overlay>
<overlay overlay={new Widget.Box({
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
vertical: true,
children: [
new Widget.Label({
className: "status",
label: mergeBindings([charging, toFull, toEmpty]).as(
<box className="info" vertical>
<label
css="font-size: 16px;"
vexpand
valign={Gtk.Align.END}>
{bind(batt, "energyRate").as(w => `${w}W`)}
</label>
<label
vexpand
valign={Gtk.Align.START}>
{bind(batt, "charging").as(c =>
c ? "Charging" : "Discharging"
)}
</label>
</box>
<box className="info" vertical>
<label
css="font-size: 16px;"
vexpand
valign={Gtk.Align.END}>
{mergeBindings([charging, toFull, toEmpty]).as(
(b) => {
let s = (b[0] ? b[1] : b[2])
return `${Math.floor(s/3600)}H ${Math.floor((s%3600)/60)}M`
}
),
}),
new Widget.Label({
className: "title",
label: charging.as(c => c ? "To full" : "To empty"),
}),
]
})}>
<box className="info" />
</overlay>
)
}
</label>
<label
vexpand
valign={Gtk.Align.START}>
{charging.as(c => c ? "To full" : "To empty")}
</label>
</box>
</box>
</box>
<box

View File

@@ -13,18 +13,13 @@ function bluetoothItem(device: Bluetooth.Device) {
onClicked={() => {
execAsync(["bluetoothctl", device.connected ? "disconnect" : "connect", device.address]).then(out => console.log(out)).catch(out => console.log(out))
}}>
<overlay overlay={new Widget.Box({
className: bind(device, "connected").as(c =>
"toggleIndicator ".concat(c ? "active" : "inactive")
),
halign: Gtk.Align.END,
valign: Gtk.Align.CENTER,
})}>
<box>
<box>
<box hexpand>
<icon icon={device.icon ?? "bluetooth"} css="font-size: 32px;" />
<label valign={Gtk.Align.CENTER}>{device.alias ?? device.name}</label>
</box>
</overlay>
<box className={bind(device, "connected").as(c => "toggleIndicator ".concat(c ? "active" : "inactive"))} halign={Gtk.Align.END} valign={Gtk.Align.CENTER}/>
</box>
</button>
}

View File

@@ -68,9 +68,12 @@ function BrightWindow() {
.then((out) => (console.log(out)))
.catch((out) => (console.log(out)))
}}
><overlay overlay={toggleIndicator}>
<label halign={Gtk.Align.START}>Night Light</label>
</overlay></button>
>
<box>
<label hexpand halign={Gtk.Align.START}>Night Light</label>
{toggleIndicator}
</box>
</button>
</box>
</window>
}

View File

@@ -23,11 +23,11 @@ function NewClient(client: Hyprland.Client) {
}
let b = <box halign={Gtk.Align.END} className="clientLabel" visible={true}>
<icon
icon={(appsClass) ? appsClass.iconName : "archlinux"}
icon={(appsClass) ? appsClass.iconName : "hyprland"}
css="font-size: 24px; margin-right: 4px;"
/>
<label css="font-family: comfortaa; font-size: 14px;">{
(appsClass) ? appsClass.name : "Hummingbird"
(appsClass) ? appsClass.name : "Hyprland"
}</label>
</box>
return b

View File

@@ -4,16 +4,16 @@ import Notif from "gi://AstalNotifd"
import Pango from "gi://Pango?version=1.0"
const notifs = Notif.get_default()
const { TOP, RIGHT } = Astal.WindowAnchor
const { TOP, RIGHT, BOTTOM } = Astal.WindowAnchor
const notifPlaceholder = "/home/protoshark/.config/ags/assets/notif.svg"
let notifWindow: Widget.Window
function NewNotif(notif: Notif.Notification) {
function NewNotif(notif: Notif.Notification, inPanel: boolean = true) {
return <box
className="notifBox"
className={inPanel ? "notifBox" : "panelNotifBox"}
halign={Gtk.Align.END}
valign={Gtk.Align.START} >
<box
valign={Gtk.Align.START}
spacing={8}>
<box
halign={Gtk.Align.CENTER}
@@ -21,7 +21,11 @@ function NewNotif(notif: Notif.Notification) {
className="notifIcon"
css={`background-image: url("${notif.image ?? notifPlaceholder}")`}
/>
<box vertical={true} valign={Gtk.Align.CENTER} spacing={6}>
<box
vertical
valign={Gtk.Align.CENTER}
spacing={6}
hexpand>
<label
halign={Gtk.Align.START}
css="font-size: 16px;"
@@ -39,7 +43,45 @@ function NewNotif(notif: Notif.Notification) {
{notif.body.substring(0, 88)}
</label>
</box>
</box></box>
<box vertical valign={Gtk.Align.CENTER}>
{notif.actions.map(action => <button onClicked={() => {
notif.invoke(action.id)
}}>
<box><icon icon={action.label.toLowerCase()} css="margin: 4px -2px;"/></box>
</button>)}
</box>
</box>
}
function NotifPanel() {
return <window
anchor={ TOP | RIGHT | BOTTOM }
marginTop={36}
namespace="notifpanel" >
<box vertical={true}>
<box>
<button
hexpand
heightRequest={36}
onClicked={() => {
for (let n of notifs.notifications) {
n.dismiss()
}
}}>Clear notifications</button>
<button
hexpand
heightRequest={36}
onClicked={() => {
notifs.dontDisturb = !notifs.dontDisturb
}}>Do not disturb</button>
</box>
<scrollable widthRequest={416} vexpand>
<box vertical={true}>
{notifs.notifications.map(n => NewNotif(n, false))}
</box>
</scrollable>
</box>
</window>
}
function notify(notif: Notif.Notification) {
@@ -59,6 +101,7 @@ function notify(notif: Notif.Notification) {
notifWindow = <window
anchor={ TOP | RIGHT }
marginTop={36}
layer={Astal.Layer.OVERLAY}
css="background: transparent;"
namespace="notification">
<overlay overlay={thisNotif}>
@@ -93,7 +136,14 @@ function notifCount() {
export default function notifWidget() {
return <button
onClicked={() => {}}
onClicked={() => {
if (notifWindow) {
notifWindow.destroy()
notifWindow = null
} else {
notifWindow = NotifPanel()
}
}}
setup={(self) => self.hook(notifs, "notified", (_, id) => {
notify(notifs.get_notification(id))
})}>

View File

@@ -44,16 +44,6 @@ function cover(player: Mpris.Player) {
return coverOverlay
}
function wrap(str: string) {
if (str.length > 40) {
str = str.substring(0, 40)
}
if (str.length > 20) {
return str.substring(0, 20) + "\n" + str.substring(20)
}
return str
}
function playerBox(player: Mpris.Player) {
return <box
vertical={true}
@@ -92,12 +82,16 @@ function playerBox(player: Mpris.Player) {
new Widget.Label({
className: "playerTitle",
justify: Gtk.Justification.CENTER,
label: bind(player, "title").as(str => wrap(str))
wrap: true,
width_chars: 20,
label: bind(player, "title").as(str => str.substring(0, 60))
}),
new Widget.Label({
className: "playerArtist",
justify: Gtk.Justification.CENTER,
label: bind(player, "artist").as(str => wrap(str))
wrap: true,
width_chars: 20,
label: bind(player, "artist").as(str => str.substring(0, 60))
})
]
})

61
widget/power.tsx Normal file
View File

@@ -0,0 +1,61 @@
import { Astal } from "astal/gtk3"
import { popup } from "./popup"
import { execAsync } from "astal"
const { TOP, LEFT } = Astal.WindowAnchor
function PowerWindow() {
return <window
className="popupWindow"
anchor={ TOP | LEFT }
namespace="lazerpopup">
<box vertical css="padding: 6px 0;">
{[
["Lock", "bash -c 'pgrep -x hyprlock || hyprlock &'" ],
["Suspend", "/home/protoshark/.config/scripts/lock.sh" ],
["Logout", "hyprctl dispatch exit" ],
["Shutdown", "systemctl poweroff" ],
["Reboot", "systemctl reboot" ],
].map(action => <button onClicked={() => {
execAsync(action[1])
.then(out => print(out))
.catch(out => print(out))
popup.window.destroy()
popup.window = null
popup.state = ""
}}>
<box hexpand css="padding: 4px 8px 4px 0;">
<icon icon={action[0].toLowerCase()} css="margin: 0 6px;"/>
<label>{action[0]}</label>
</box>
</button>)}
</box>
</window>
}
export default function power() {
return <button onClicked={(self) => {
if (popup.state !== "power") {
if (popup.window !== null) {
popup.window.destroy()
popup.window = null
}
popup.window = PowerWindow();
popup.state = "power"
self.toggleClassName("selected", true)
self.hook(popup.window, "destroy", () => {
self.toggleClassName("selected", false)
popup.window = null
popup.state = ""
})
} else {
popup.window.destroy()
popup.window = null
popup.state = ""
}
}}>
<box>
<icon icon="archlinux" />
</box>
</button>
}