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 8cb16a984a
commit 56fe96de47
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, .ws:hover,
.wifi:hover,
.battery:hover { .battery:hover {
background: transparentize($base05, 0.88); 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 { .wifi-header {
label { label {
font-weight: bold; font-weight: bold;

View File

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

View File

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

View File

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