Add AGS volume widget
This commit is contained in:
parent
b38217ca75
commit
284ed73a33
|
|
@ -6,7 +6,8 @@
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Bar {
|
.Bar,
|
||||||
|
.popup-window {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,3 +35,52 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 0 8px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,47 @@
|
||||||
import { App, Astal, Gdk, Gtk } from "astal/gtk3"
|
import { App, Astal, Gdk, Gtk } from "astal/gtk3"
|
||||||
import Workspaces from "./Workspaces"
|
import Workspaces from "./Workspaces"
|
||||||
import Clock from "./Clock"
|
import Clock from "./Clock"
|
||||||
|
import { VolumeWidget } from "./Volume"
|
||||||
|
|
||||||
export default function Bar(gdkmonitor: Gdk.Monitor) {
|
export default function Bar(gdkmonitor: Gdk.Monitor) {
|
||||||
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
|
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
|
||||||
|
const volume = VolumeWidget(gdkmonitor)
|
||||||
|
|
||||||
return (
|
return [
|
||||||
<window
|
(
|
||||||
className="Bar"
|
<window
|
||||||
namespace="ags-bar"
|
className="Bar"
|
||||||
gdkmonitor={gdkmonitor}
|
namespace="ags-bar"
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
gdkmonitor={gdkmonitor}
|
||||||
anchor={TOP | LEFT | RIGHT}
|
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||||
application={App}
|
anchor={TOP | LEFT | RIGHT}
|
||||||
>
|
application={App}
|
||||||
<centerbox className="bar">
|
>
|
||||||
<box
|
<centerbox className="bar">
|
||||||
className="bar-left"
|
<box
|
||||||
halign={Gtk.Align.START}
|
className="bar-left"
|
||||||
valign={Gtk.Align.CENTER}
|
halign={Gtk.Align.START}
|
||||||
>
|
valign={Gtk.Align.CENTER}
|
||||||
<Workspaces />
|
>
|
||||||
</box>
|
<Workspaces />
|
||||||
<box
|
</box>
|
||||||
className="bar-center"
|
<box
|
||||||
halign={Gtk.Align.CENTER}
|
className="bar-center"
|
||||||
valign={Gtk.Align.CENTER}
|
halign={Gtk.Align.CENTER}
|
||||||
>
|
valign={Gtk.Align.CENTER}
|
||||||
<Clock />
|
>
|
||||||
</box>
|
<Clock />
|
||||||
<box
|
</box>
|
||||||
className="bar-right"
|
<box
|
||||||
halign={Gtk.Align.END}
|
className="bar-right"
|
||||||
valign={Gtk.Align.CENTER}
|
halign={Gtk.Align.END}
|
||||||
/>
|
valign={Gtk.Align.CENTER}
|
||||||
</centerbox>
|
>
|
||||||
</window>
|
{volume.button}
|
||||||
)
|
</box>
|
||||||
|
</centerbox>
|
||||||
|
</window>
|
||||||
|
),
|
||||||
|
volume.popup,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<boolean>
|
||||||
|
width?: number
|
||||||
|
vertical?: boolean
|
||||||
|
className?: string
|
||||||
|
children: Array<JSX.Element>
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (
|
||||||
|
<box
|
||||||
|
className={`dropdown ${className}`}
|
||||||
|
vertical={vertical}
|
||||||
|
widthRequest={width}
|
||||||
|
halign={Gtk.Align.START}
|
||||||
|
valign={Gtk.Align.START}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<window
|
||||||
|
className="popup-window"
|
||||||
|
application={App}
|
||||||
|
gdkmonitor={gdkmonitor}
|
||||||
|
namespace="ags-bar-popup"
|
||||||
|
layer={Astal.Layer.OVERLAY}
|
||||||
|
keymode={Astal.Keymode.EXCLUSIVE}
|
||||||
|
anchor={TOP | BOTTOM | LEFT | RIGHT}
|
||||||
|
exclusivity={Astal.Exclusivity.IGNORE}
|
||||||
|
visible={bind(open)}
|
||||||
|
onNotifyVisible={(self) => {
|
||||||
|
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}
|
||||||
|
</window>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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 = (
|
||||||
|
<button
|
||||||
|
className="volume"
|
||||||
|
onClicked={() => open.set(!open.get())}
|
||||||
|
onScroll={(_, e) => {
|
||||||
|
const volume = Math.min(1, Math.max(0, speaker.volume - e.delta_y * 0.02))
|
||||||
|
speaker.volume = volume
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<icon icon={icon()} />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
const popup = (
|
||||||
|
<Dropdown
|
||||||
|
gdkmonitor={gdkmonitor}
|
||||||
|
target={button}
|
||||||
|
open={open}
|
||||||
|
width={240}
|
||||||
|
vertical={false}
|
||||||
|
>
|
||||||
|
<button className="mute" onClicked={() => (speaker.mute = !speaker.mute)}>
|
||||||
|
<icon icon={icon()} />
|
||||||
|
</button>
|
||||||
|
<slider
|
||||||
|
className="vol-slider"
|
||||||
|
hexpand
|
||||||
|
value={bind(speaker, "volume")}
|
||||||
|
onDragged={(self) => (speaker.volume = self.value)}
|
||||||
|
/>
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
|
||||||
|
return { button, popup }
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue