Move wifi into QuickSettings popup with collapsible network list

This commit is contained in:
Joshua Yuen 2026-09-12 15:55:33 -04:00
parent aabe5b15db
commit 578797498e
Signed by: josh
GPG Key ID: 530DA0331EFE99F5
4 changed files with 91 additions and 57 deletions

View File

@ -92,7 +92,6 @@ $module-bg: transparentize($base02, 0.4);
}
.ws:hover,
.wifi:hover,
.battery:hover {
background: transparentize($base05, 0.88);
}
@ -166,6 +165,26 @@ $module-bg: transparentize($base02, 0.4);
}
}
.wifi-section {
margin-top: 10px;
border-top: 1px solid $base02;
padding-top: 8px;
}
.wifi-toggle {
padding: 4px 6px;
border-radius: 6px;
label {
font-weight: bold;
margin: 0 2px;
}
&:hover {
background: transparentize($base05, 0.88);
}
}
.wifi-header {
label {
font-weight: bold;

View File

@ -2,7 +2,6 @@ import { App, Astal, Gdk, Gtk } from "astal/gtk3"
import Workspaces from "./Workspaces"
import Logo from "./Logo"
import Clock from "./Clock"
import { WifiWidget } from "./Wifi"
import { BatteryWidget } from "./Battery"
import QuickSettings from "./QuickSettings"
@ -55,17 +54,14 @@ function Corner({ side }: { side: "left" | "right" }) {
export default function Bar(gdkmonitor: Gdk.Monitor) {
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
const wifi = WifiWidget(gdkmonitor)
const battery = BatteryWidget(gdkmonitor)
const quicksettings = QuickSettings(gdkmonitor)
const right: Array<JSX.Element> = []
if (wifi.button) right.push(wifi.button)
if (battery.button) right.push(battery.button)
right.push(quicksettings.button)
const popups: Array<JSX.Element> = []
if (wifi.popup) popups.push(wifi.popup)
if (battery.popup) popups.push(battery.popup)
popups.push(quicksettings.popup)

View File

@ -1,6 +1,7 @@
import AstalWp from "gi://AstalWp"
import { execAsync, Variable, bind } from "astal"
import Dropdown from "./Dropdown"
import { wifiIcon, WifiSection } from "./Wifi"
const speaker = AstalWp.get_default().audio.default_speaker!
const mic = AstalWp.get_default().audio.default_microphone
@ -49,6 +50,7 @@ export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
<box spacing={6}>
<icon icon={speakerIcon()} />
{micIcon && <icon icon={micIcon()} />}
{wifiIcon && <icon icon={wifiIcon} />}
</box>
</button>
)
@ -95,6 +97,7 @@ export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
/>
</box>
)}
<WifiSection open={open} />
</Dropdown>
)

View File

@ -1,10 +1,11 @@
import Gtk from "gi://Gtk?version=3.0"
import AstalNetwork from "gi://AstalNetwork"
import { type Binding, Variable, bind } from "astal"
import Dropdown from "./Dropdown"
import { type Binding, type Variable, Variable, bind } from "astal"
const CONNECTING = [40, 50, 60, 70, 80, 90]
const wifi = AstalNetwork.get_default().wifi
function strongest(aps: AstalNetwork.AccessPoint[]) {
const bySsid = new Map<string, AstalNetwork.AccessPoint>()
for (const ap of aps) {
@ -15,6 +16,10 @@ function strongest(aps: AstalNetwork.AccessPoint[]) {
return [...bySsid.values()].sort((a, b) => b.strength - a.strength)
}
export const wifiIcon: Binding<string> | null = wifi
? bind(wifi, "icon-name")
: null
function WifiRow(
ap: AstalNetwork.AccessPoint,
activeSsid: Binding<string>,
@ -43,12 +48,10 @@ function WifiRow(
)
}
export function WifiWidget(gdkmonitor: Gdk.Monitor) {
const wifi = AstalNetwork.get_default().wifi
export function WifiSection({ open }: { open: Variable<boolean> }) {
if (!wifi) return <box />
if (!wifi) return { button: null, popup: null }
const open = Variable(false)
const expanded = Variable(false)
const pendingAP = Variable<AstalNetwork.AccessPoint | null>(null)
const failed = Variable("")
@ -97,19 +100,34 @@ export function WifiWidget(gdkmonitor: Gdk.Monitor) {
/>
)
const button = (
<button
className="wifi"
onClicked={() => open.set(!open.get())}
>
<icon icon={bind(wifi, "icon-name")} />
</button>
)
open.subscribe((v) => {
if (v) {
wifi.scan()
} else {
expanded.set(false)
pendingAP.set(null)
failed.set("")
passwordEntry.text = ""
}
})
const popup = (
<Dropdown gdkmonitor={gdkmonitor} target={button} open={open} width={260}>
return (
<box className="wifi-section" vertical>
<box className="wifi-header">
<label label="Wi-Fi" hexpand halign={Gtk.Align.START} />
<button
className="wifi-toggle"
onClicked={() => expanded.set(!expanded.get())}
>
<box spacing={6}>
<icon icon={bind(wifi, "icon-name")} />
<label label="Wi-Fi" />
<icon
icon={expanded().as((v) =>
v ? "pan-down-symbolic" : "pan-end-symbolic",
)}
/>
</box>
</button>
<button className="wifi-rescan" onClicked={() => wifi.scan()}>
<icon icon="view-refresh-symbolic" />
</button>
@ -121,42 +139,40 @@ export function WifiWidget(gdkmonitor: Gdk.Monitor) {
}}
/>
</box>
<label
className="wifi-status"
label={status()}
visible={status().as((text) => text !== "")}
/>
{passwordEntry}
<label
className="wifi-empty"
label="wi-fi is off"
visible={bind(wifi, "enabled").as((enabled) => !enabled)}
/>
<scrollable
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
maxContentHeight={360}
propagateNaturalHeight
visible={bind(wifi, "enabled")}
<revealer
transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
transitionDuration={180}
revealChild={bind(expanded)}
>
<box vertical>
{bind(wifi, "access-points").as((aps) =>
strongest(aps).map((ap) => WifiRow(ap, activeSsid, connect))
)}
<label
className="wifi-status"
label={status()}
visible={status().as((text) => text !== "")}
/>
{passwordEntry}
<label
className="wifi-empty"
label="wi-fi is off"
visible={bind(wifi, "enabled").as((enabled) => !enabled)}
/>
<scrollable
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
maxContentHeight={360}
propagateNaturalHeight
visible={bind(wifi, "enabled")}
>
<box vertical>
{bind(wifi, "access-points").as((aps) =>
strongest(aps).map((ap) =>
WifiRow(ap, activeSsid, connect)
),
)}
</box>
</scrollable>
</box>
</scrollable>
</Dropdown>
</revealer>
</box>
)
open.subscribe((v) => {
if (v) {
wifi.scan()
} else {
pendingAP.set(null)
failed.set("")
passwordEntry.text = ""
}
})
return { button, popup }
}