Files
hyprlazer/widget/battery.tsx
2025-01-30 20:19:53 -08:00

155 lines
4.1 KiB
TypeScript

import Battery from "gi://AstalBattery"
import PowerProfiles from "gi://AstalPowerProfiles"
import Hyprland from "gi://AstalHyprland"
import { popup } from "./popup"
import { Astal, Gtk, Widget } from "astal/gtk3"
import { mergeBindings } from "/usr/share/astal/gjs/_astal"
import { bind } from "astal"
const hyprland = Hyprland.get_default()
const batt = Battery.get_default()
const powerProfiles = PowerProfiles.get_default()
const percentage = bind(batt, "percentage").as((p) => `${Math.round(p*100)}%`)
const charging = bind(batt, "charging")
const toFull = bind(batt, "timeToFull").as((t) => t.toString())
const toEmpty = bind(batt, "timeToEmpty").as((t) => t.toString())
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) =>
(p === profile)
? "toggleIndicator active"
: "toggleIndicator inactive"
)
})}>
<label halign={Gtk.Align.START}>{label}</label>
</overlay>
</button>
}
function BattWindow() {
const { TOP, LEFT } = Astal.WindowAnchor
return <window
className="popupWindow"
namespace="lazerpopup"
monitor={hyprland.focusedMonitor.id}
anchor={TOP | LEFT}
layer={Astal.Layer.TOP}>
<box
className="popupBattery"
vertical={true}
spacing={8}>
<box spacing={10}>
<overlay overlay={new Widget.Label({
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
className: "percentage",
label: bind(batt, "energy").as(e => `${e}Wh`),
})}>
<levelbar
className="batteryLevel"
minValue={0}
maxValue={1}
inverted={true}
widthRequest={96}
vertical={true}
value={bind(batt, "percentage").as(c => c)}
/>
</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(
(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>
</box>
</box>
<box
vertical={true}>
{profileButton("Power Saver", "power-saver")}
{profileButton("Balanced", "balanced")}
{profileButton("Performance", "performance")}
</box>
</box>
</window>
}
export default function battery() {
return <button
className="battery"
halign={Gtk.Align.START}
onClicked={(self) => {
if (popup.state !== "battery") {
if (popup.window !== null) {
popup.window.destroy()
popup.window = null
}
popup.window = BattWindow();
popup.state = "battery"
self.toggleClassName("selected", true)
self.hook(popup.window, "destroy", () => {
self.toggleClassName("selected", false)
})
} else {
popup.window.destroy()
popup.window = null
popup.state = ""
}}}
><box>
<icon
valign={Gtk.Align.CENTER}
css="font-size: 24px; margin-right: 2px;"
icon={mergeBindings([charging, bind(powerProfiles, "activeProfile")]).as(
b => {
if (b[0]) return "battery-charging-symbolic"
return `${b[1]}-symbolic`
}
)}>
</icon>
<label css="margin-top: 2px;">{percentage}</label>
</box>
</button>
}