diff --git a/modules/home/ags/app/style.scss b/modules/home/ags/app/style.scss
index d589338..68a554c 100644
--- a/modules/home/ags/app/style.scss
+++ b/modules/home/ags/app/style.scss
@@ -6,7 +6,8 @@
font-size: $font-size;
}
-.Bar {
+.Bar,
+.popup-window {
background: transparent;
}
@@ -34,3 +35,52 @@
font-weight: bold;
margin: 0 8px;
}
+
+.volume {
+ border-radius: 6px;
+ margin: 0 2px;
+ padding: 0 6px;
+}
+
+.ws:hover,
+.volume:hover {
+ background: transparentize($base05, 0.88);
+}
+
+.volume icon,
+.mute icon {
+ font-size: 16px;
+}
+
+.dropdown {
+ background: $base01;
+ border-radius: 10px;
+ padding: 8px 12px;
+ color: $base05;
+}
+
+.mute {
+ margin-right: 8px;
+ padding: 0 6px;
+ border-radius: 6px;
+}
+
+.vol-slider {
+ trough {
+ background: $base03;
+ border-radius: 999px;
+ min-height: 12px;
+
+ highlight {
+ background: $base0D;
+ border-radius: 999px;
+ }
+
+ slider {
+ min-width: 16px;
+ min-height: 16px;
+ background: $base05;
+ border-radius: 999px;
+ }
+ }
+}
diff --git a/modules/home/ags/app/widget/Bar.tsx b/modules/home/ags/app/widget/Bar.tsx
index 6a31361..2142d6d 100644
--- a/modules/home/ags/app/widget/Bar.tsx
+++ b/modules/home/ags/app/widget/Bar.tsx
@@ -1,40 +1,47 @@
import { App, Astal, Gdk, Gtk } from "astal/gtk3"
import Workspaces from "./Workspaces"
import Clock from "./Clock"
+import { VolumeWidget } from "./Volume"
export default function Bar(gdkmonitor: Gdk.Monitor) {
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
+ const volume = VolumeWidget(gdkmonitor)
- return (
-
-
-
-
-
-
-
-
-
-
-
- )
+ return [
+ (
+
+
+
+
+
+
+
+
+
+ {volume.button}
+
+
+
+ ),
+ volume.popup,
+ ]
}
diff --git a/modules/home/ags/app/widget/Dropdown.tsx b/modules/home/ags/app/widget/Dropdown.tsx
new file mode 100644
index 0000000..933f229
--- /dev/null
+++ b/modules/home/ags/app/widget/Dropdown.tsx
@@ -0,0 +1,86 @@
+import { App, Astal, Gdk, Gtk } from "astal/gtk3"
+import { Variable, bind } from "astal"
+
+interface DropdownProps {
+ gdkmonitor: Gdk.Monitor
+ target: Gtk.Widget
+ open: Variable
+ width?: number
+ vertical?: boolean
+ className?: string
+ children: Array
+}
+
+export default function Dropdown({
+ gdkmonitor,
+ target,
+ open,
+ width = 260,
+ vertical = true,
+ className = "",
+ children,
+}: DropdownProps) {
+ const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor
+ const GAP = 8
+
+ const child = (
+
+ {children}
+
+ )
+
+ function reposition() {
+ const toplevel = target.get_toplevel()
+ const [, tx, ty] = target.translate_coordinates(toplevel, 0, 0)
+ const { width: bw, height: bh } = target.get_allocation()
+ const { width: sw } = gdkmonitor.get_geometry()
+
+ const x = Math.min(Math.max(tx + bw / 2 - width / 2, GAP), sw - width - GAP)
+ const y = ty + bh + GAP
+
+ child.marginStart = Math.round(x)
+ child.marginTop = Math.round(y)
+ }
+
+ open.subscribe((v) => {
+ if (v) reposition()
+ })
+
+ return (
+ {
+ if (!self.visible) open.set(false)
+ }}
+ onButtonPressEvent={(self, e) => {
+ const event = e as unknown as Gdk.Event
+ const [, px, py] = event.get_coords()
+ const { x, y, width: w, height: h } = child.get_allocation()
+ const xOut = px < x || px > x + w
+ const yOut = py < y || py > y + h
+
+ if (xOut || yOut) self.visible = false
+ }}
+ onKeyPressEvent={(self, e) => {
+ const event = e as unknown as Gdk.Event
+ if (event.get_keyval()[1] === Gdk.KEY_Escape) self.visible = false
+ }}
+ >
+ {child}
+
+ )
+}
diff --git a/modules/home/ags/app/widget/Volume.tsx b/modules/home/ags/app/widget/Volume.tsx
new file mode 100644
index 0000000..beb9a28
--- /dev/null
+++ b/modules/home/ags/app/widget/Volume.tsx
@@ -0,0 +1,54 @@
+import AstalWp from "gi://AstalWp"
+import { Variable, bind } from "astal"
+import Dropdown from "./Dropdown"
+
+const speaker = AstalWp.get_default().audio.default_speaker!
+
+const icon = Variable.derive(
+ [bind(speaker, "volume"), bind(speaker, "mute")],
+ (volume, mute) => {
+ if (mute || volume <= 0.01) return "audio-volume-muted-symbolic"
+ if (volume < 0.33) return "audio-volume-low-symbolic"
+ if (volume < 0.66) return "audio-volume-medium-symbolic"
+ return "audio-volume-high-symbolic"
+ }
+)
+
+export function VolumeWidget(gdkmonitor: Gdk.Monitor) {
+ const open = Variable(false)
+
+ const button = (
+
+ )
+
+ const popup = (
+
+
+ (speaker.volume = self.value)}
+ />
+
+ )
+
+ return { button, popup }
+}