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

32
assets/hyprland.svg Normal file
View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 515.34 673.26">
<defs>
<style>
.cls-1 {
mix-blend-mode: difference;
}
.cls-2 {
isolation: isolate;
}
.cls-3 {
fill: url(#linear-gradient);
}
</style>
<linearGradient id="linear-gradient" x1="8.93" y1="572.38" x2="572.68" y2="102.08" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#00a2f8" />
<stop offset="1" stop-color="#00e5cf" />
</linearGradient>
</defs>
<g class="cls-2">
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<g class="cls-1">
<path class="cls-3"
d="m288.53,0c1.99,2.11,3.26,3.13,4.14,4.42,15.97,23.63,31.7,47.44,47.91,70.91,9.33,13.51,19.26,26.61,29.22,39.67,9.63,12.65,19.8,24.88,29.44,37.53,11.15,14.63,22.45,29.18,32.81,44.36,13.27,19.45,26.64,38.94,38.12,59.44,9.76,17.43,17.56,36.05,25.02,54.64,5.28,13.16,8.82,27.06,12.6,40.78,1.75,6.36,2.24,13.07,3.26,19.62,1.26,8.02,3.4,16.04,3.53,24.08.35,20.94,2.17,41.95-1.48,62.8-4.02,22.99-10.75,45-20.76,66.23-10.12,21.45-22.38,41.26-37.39,59.59-13.6,16.61-29.57,30.33-46.84,42.91-12.68,9.23-26.09,17.07-40.36,23.3-12.84,5.6-26.07,10.09-39.69,13.89-30.75,8.56-61.93,10.44-93.33,8.23-18.93-1.33-38.08-3.65-56.11-10.46-14.82-5.6-29.75-11.13-43.92-18.13-11.19-5.52-21.91-12.44-31.73-20.15-12.88-10.11-25.52-20.81-36.67-32.74-9.99-10.68-18.64-22.87-26.4-35.31-8.18-13.13-15.39-27.01-21.58-41.2-6.69-15.32-11.01-31.45-14.53-47.97-5.48-25.71-3.71-51.44-2.59-77.09.64-14.73,4.53-29.47,8.26-43.86,3.93-15.18,8.68-30.26,14.44-44.84,5.77-14.6,12.47-28.96,20.14-42.65,9.63-17.18,20.25-33.85,31.2-50.23,9.94-14.88,20.68-29.24,31.47-43.52,8.69-11.5,18.17-22.41,26.97-33.83,8.9-11.55,17.46-23.36,26.07-35.13,9.11-12.46,18.29-24.87,27.09-37.55,11.16-16.07,21.91-32.43,32.97-48.57,1.58-2.31,3.88-4.14,5.84-6.19.39.22.78.44,1.18.66.08,1.77.23,3.54.23,5.31.01,26.33.15,52.66-.17,78.99-.05,3.86-1.62,8.15-3.72,11.46-8.5,13.45-17.19,26.8-26.38,39.79-8.71,12.31-18.12,24.13-27.22,36.16-7.5,9.91-14.97,19.83-22.52,29.7-5.24,6.85-10.74,13.5-15.86,20.44-7.16,9.72-14.35,19.45-21.03,29.5-8.06,12.12-15.99,24.36-23.23,36.97-5.18,9.02-9.26,18.69-13.59,28.17-2.4,5.26-4.61,10.64-6.36,16.14-3.1,9.76-5.58,19.71-8.68,29.47-8.72,27.42-6.87,55.63-4.92,83.5.99,14.15,6.11,28.15,10.4,41.89,6.01,19.24,16.32,36.3,27.95,52.74,7.94,11.23,16.95,21.38,27.14,30.36,8.39,7.38,17.5,14.17,27.07,19.92,10.89,6.54,22.23,12.77,34.12,17.07,12.69,4.59,26.1,7.99,39.48,9.68,15.93,2.01,32.16,1.58,48.25,2.34,14.94.7,29.43-2.29,43.93-5.14,18.41-3.62,35.23-11.56,51.58-20.26,19.55-10.4,35.98-25.13,50.37-41.73,14.71-16.97,27.06-36.05,34.92-57,8.29-22.1,15.2-44.97,14.15-69.26-.57-13.13.15-26.34-1.06-39.39-.89-9.61-3.62-19.11-6.16-28.5-2.98-11.03-6.03-22.1-10.16-32.73-4.11-10.59-9.36-20.75-14.52-30.9-4.57-8.99-9.32-17.92-14.66-26.46-6.5-10.39-13.38-20.59-20.68-30.43-11.05-14.9-22.74-29.32-33.91-44.12-13.08-17.33-26.13-34.68-38.77-52.33-10.91-15.22-21.31-30.81-31.71-46.39-1.67-2.5-3-5.79-3.03-8.72-.23-28.49-.15-56.98-.13-85.47,0-.95.24-1.91.58-4.44Z" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

7
assets/lock-symbolic.svg Normal file
View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 1.3 KiB

7
assets/logout.svg Normal file
View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12,7.33a1,1,0,0,0,1-1V5.78l6-1.5V7.22L14.7,8.3a1,1,0,0,0,.24,2l.24,0L20.24,9l.07,0,.19-.09.15-.1a.93.93,0,0,0,.13-.15.78.78,0,0,0,.1-.15.55.55,0,0,0,.06-.18.65.65,0,0,0,0-.19A.24.24,0,0,0,21,8V3a1,1,0,0,0-1.24-1l-8,2A1,1,0,0,0,11,5V6.33A1,1,0,0,0,12,7.33Zm9.71,13-9-9h0l-9-9A1,1,0,0,0,2.29,3.71L11,12.41v2.94A3.45,3.45,0,0,0,9.5,15,3.5,3.5,0,1,0,13,18.5V14.41l7.29,7.3a1,1,0,0,0,1.42,0A1,1,0,0,0,21.71,20.29ZM9.5,20A1.5,1.5,0,1,1,11,18.5,1.5,1.5,0,0,1,9.5,20Z"/></svg>

Before

Width:  |  Height:  |  Size: 701 B

7
assets/reboot.svg Normal file
View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 808 B

7
assets/shutdown.svg Normal file
View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 1.0 KiB

7
assets/suspend.svg Normal file
View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 960 B

View File

@@ -21,10 +21,10 @@ button {
color: $text-1;
padding: 0;
> box, > overlay > box {
> box, > overlay > box, > label {
transition: ease-out 0.2s;
border-radius: 12px;
padding: 0 6px 0 6px;
padding: 0 6px;
margin: 2px 6px 4px 6px;
color: $text-1;
}
@@ -38,11 +38,11 @@ button {
&:hover {
color: transparent;
> box, > overlay > box {
> box, > overlay > box, > label {
background: $background-2;
}
&.selected {
> box, > overlay > box {
> box, > overlay > box, > label {
background: $highlight-1s;
}
}
@@ -118,7 +118,7 @@ button {
> box {
background: $background-3;
border-radius: 10px;
box-shadow: 0 0 10px 4px black;
box-shadow: 0 0 16px 4px black;
padding: 10px;
margin: 48px 40px 40px 12px;
}
@@ -157,13 +157,19 @@ button {
}
.toggleButton {
> box {
padding: 0;
}
label {
color: white;
}
:not(.toggleIndicator) {
background: none;
}
&.bt {
icon {
margin-right: 8px;
}
box {
background: none;
}
label {
margin-top: 2px;
margin-right: 60px;
@@ -172,20 +178,11 @@ button {
&:hover .toggleIndicator {
box-shadow: 0 0 6px 0px $highlight-3;
}
> overlay {
margin: 0;
padding: 6px 0px 6px 0px;
> label {
color: $text-1;
font-size: 16px;
margin: 2px;
}
> .active {
margin: 4px 12px 4px 0;
min-width: 32px;
background: $highlight-3;
}
}
}
.scroller {
@@ -302,9 +299,14 @@ button {
min-height: 6px;
min-width: 24px;
padding: 0;
margin: 4px 16px 4px 0;
margin: 4px 4px 4px 0;
border: 2px solid $highlight-3;
border-radius: 8px;
&.active {
margin: 4px 0px 4px 0;
min-width: 32px;
background: $highlight-3;
}
}
@keyframes notifIn {
@@ -315,7 +317,7 @@ button {
@keyframes glowIn {
from {
background: white;
box-shadow: 0 0 10px 4px white;
box-shadow: 0 0 16px 4px white;
}
}
@@ -325,15 +327,19 @@ button {
background: $background-3;
border-radius: 10px;
padding: 8px;
box-shadow: 0 0 10px 4px black;
box-shadow: 0 0 16px 4px black;
min-width: 300px;
margin: 12px 12px 40px 40px;
}
.notifIcon {
min-width: 64px;
min-height: 64px;
min-width: 48px;
min-height: 48px;
margin: 8px;
background-size: contain;
border-radius: 10px;
}
.panelNotifBox {
min-width: 400px;
}

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 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>
}