bluetoothdevices: make popup window

This commit is contained in:
2 * r + 2 * t 2025-01-17 00:16:40 +11:00
parent 6d5805e662
commit 0518ec4214
10 changed files with 179 additions and 39 deletions

View file

@ -0,0 +1,43 @@
@use "sass:color";
@use "../scheme";
@use "../lib";
@use "../font";
$-accent: scheme.$rosewater;
.bluetooth-devices {
@include lib.popdown-window($-accent);
min-width: lib.s(500);
min-height: lib.s(400);
.list {
@include lib.spacing($vertical: true);
color: scheme.$text;
.device {
@include lib.spacing(8);
.icon {
font-size: lib.s(18);
}
button {
@include lib.rounded(5);
@include lib.element-decel;
padding: lib.s(3) lib.s(8);
&:hover,
&:focus {
background-color: scheme.$surface0;
}
&:active {
background-color: scheme.$surface1;
}
}
}
}
}

3
scss/popdowns/index.scss Normal file
View file

@ -0,0 +1,3 @@
@forward "notifications";
@forward "updates";
@forward "bluetoothdevices";

View file

@ -1,7 +1,7 @@
@use "sass:color";
@use "scheme";
@use "lib";
@use "font";
@use "../scheme";
@use "../lib";
@use "../font";
@mixin popup($accent) {
.separator {

View file

@ -1,12 +1,12 @@
@use "sass:color";
@use "scheme";
@use "lib";
@use "font";
@use "../scheme";
@use "../lib";
@use "../font";
$accent: scheme.$blue;
$-accent: scheme.$blue;
.updates {
@include lib.popdown-window($accent);
@include lib.popdown-window($-accent);
min-width: lib.s(600);
min-height: lib.s(400);
@ -16,11 +16,11 @@ $accent: scheme.$blue;
&:hover,
&:focus {
color: color.mix($accent, scheme.$base, 80%);
color: color.mix($-accent, scheme.$base, 80%);
}
&:active {
color: color.mix($accent, scheme.$base, 60%);
color: color.mix($-accent, scheme.$base, 60%);
}
}

View file

@ -36,6 +36,14 @@ const hookFocusedClientProp = (
callback(lastClient);
};
const togglePopup = (self: JSX.Element, event: Astal.ClickEvent, name: string) => {
const popup = App.get_window(name) as PopupWindow | null;
if (popup) {
if (popup.visible) popup.hide();
else popup.popup_at_widget(self, event);
}
};
const OSIcon = () => <label className="module os-icon" label={osIcon} />;
const ActiveWindow = () => (
@ -315,10 +323,9 @@ const Network = () => (
const Bluetooth = () => (
<button
onClick={(_, event) => {
if (event.button === Astal.MouseButton.PRIMARY) {
// TODO: bluetooth panel
} else if (event.button === Astal.MouseButton.SECONDARY) AstalBluetooth.get_default().toggle();
onClick={(self, event) => {
if (event.button === Astal.MouseButton.PRIMARY) togglePopup(self, event, "bluetooth-devices");
else if (event.button === Astal.MouseButton.SECONDARY) AstalBluetooth.get_default().toggle();
else if (event.button === Astal.MouseButton.MIDDLE)
execAsync("uwsm app -- blueman-manager").catch(console.error);
}}
@ -366,15 +373,7 @@ const StatusIcons = () => (
const PkgUpdates = () => (
<button
onClick={(self, event) => {
if (event.button === Astal.MouseButton.PRIMARY) {
const popup = App.get_window("updates") as PopupWindow | null;
if (popup) {
if (popup.visible) popup.hide();
else popup.popup_at_widget(self, event);
}
}
}}
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "updates")}
setup={self =>
setupCustomTooltip(
self,
@ -391,15 +390,9 @@ const PkgUpdates = () => (
const NotifCount = () => (
<button
onClick={(self, event) => {
if (event.button === Astal.MouseButton.PRIMARY) {
const popup = App.get_window("notifications") as PopupWindow | null;
if (popup) {
if (popup.visible) popup.hide();
else popup.popup_at_widget(self, event);
}
}
}}
onClick={(self, event) =>
event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "notifications")
}
setup={self =>
setupCustomTooltip(
self,

View file

@ -0,0 +1,93 @@
import { bind, Variable } from "astal";
import { Gtk } from "astal/gtk3";
import AstalBluetooth from "gi://AstalBluetooth";
import PopdownWindow from "../../widgets/popdownwindow";
const BluetoothDevice = (device: AstalBluetooth.Device) => (
<box className="device">
<icon className="icon" icon={bind(device, "icon").as(i => `${i}-symbolic`)} />
<label
truncate
xalign={0}
setup={self => {
const update = () =>
(self.label = `${device.alias}${
device.connected || device.paired ? ` (${device.connected ? "Connected" : "Paired"})` : ""
}`);
self.hook(device, "notify::alias", update);
self.hook(device, "notify::connected", update);
self.hook(device, "notify::paired", update);
update();
}}
/>
<box hexpand />
<button
cursor="pointer"
onClicked={self => {
if (device.connected)
device.disconnect_device((_, res) => {
self.sensitive = true;
device.disconnect_device_finish(res);
});
else
device.connect_device((_, res) => {
self.sensitive = true;
device.connect_device_finish(res);
});
self.sensitive = false;
}}
label={bind(device, "connected").as(c => (c ? "Disconnect" : "Connect"))}
/>
<button
cursor="pointer"
onClicked={() => AstalBluetooth.get_default().adapter.remove_device(device)}
label="Remove"
/>
</box>
);
const List = () => (
<box vertical valign={Gtk.Align.START} className="list">
{bind(AstalBluetooth.get_default(), "devices").as(d => d.map(BluetoothDevice))}
</box>
);
export default () => {
const bluetooth = AstalBluetooth.get_default();
const label = Variable("");
const update = () => {
const devices = bluetooth.get_devices();
const connected = devices.filter(d => d.connected).length;
label.set(`${connected} connected device${connected === 1 ? "" : "s"} (${devices.length} available)`);
};
bluetooth.get_devices().forEach(d => d.connect("notify::connected", update));
bluetooth.connect("device-added", (_, device) => device.connect("notify::connected", update));
bluetooth.connect("notify::devices", update);
update();
return (
<PopdownWindow
name="bluetooth-devices"
count={bind(bluetooth, "devices").as(n => n.length)}
countLabel={bind(label)}
headerButtons={[
{
label: bind(bluetooth, "isPowered").as(p => (p ? "Disable" : "Enable")),
onClicked: () => bluetooth.toggle(),
},
{
label: "Discovery",
onClicked: () => {
if (bluetooth.adapter.discovering) bluetooth.adapter.start_discovery();
else bluetooth.adapter.stop_discovery();
},
enabled: bind(bluetooth.adapter, "discovering"),
},
]}
emptyIcon="bluetooth_disabled"
emptyLabel="No Bluetooth devices"
list={<List />}
/>
);
};

View file

@ -1,9 +1,11 @@
import BluetoothDevices from "./bluetoothdevices";
import Notifications from "./notifications";
import Updates from "./updates";
export default () => {
<Notifications />;
<Updates />;
<BluetoothDevices />;
return null;
};

View file

@ -51,7 +51,7 @@ export default () => (
{
label: "Silence",
onClicked: () => (AstalNotifd.get_default().dontDisturb = !AstalNotifd.get_default().dontDisturb),
className: bind(AstalNotifd.get_default(), "dontDisturb").as(d => (d ? "enabled" : "")),
enabled: bind(AstalNotifd.get_default(), "dontDisturb"),
},
{
label: "Clear",

View file

@ -5,6 +5,7 @@ import PopupWindow from "./popupwindow";
export default ({
name,
count,
countLabel = count.as(c => `${c} ${name.slice(0, -1)}${c === 1 ? "" : "s"}`),
headerButtons,
emptyIcon,
emptyLabel,
@ -12,7 +13,8 @@ export default ({
}: {
name: string;
count: Binding<number>;
headerButtons: { label: string; onClicked: () => void; className?: Binding<string> }[];
countLabel?: Binding<string>;
headerButtons: { label: string | Binding<string>; onClicked: () => void; enabled?: Binding<boolean> }[];
emptyIcon: string;
emptyLabel: string;
list: JSX.Element;
@ -20,10 +22,15 @@ export default ({
<PopupWindow name={name}>
<box vertical className={name}>
<box className="header">
<label label={count.as(c => `${c} ${name.slice(0, -1)}${c === 1 ? "" : "s"}`)} />
<label label={countLabel} />
<box hexpand />
{headerButtons.map(({ label, onClicked, className }) => (
<button cursor="pointer" onClicked={onClicked} label={label} className={className} />
{headerButtons.map(({ label, onClicked, enabled }) => (
<button
cursor="pointer"
onClicked={onClicked}
label={label}
className={enabled?.as(d => (d ? "enabled" : ""))}
/>
))}
</box>
<stack

View file

@ -6,8 +6,7 @@
@use "scss/notifpopups";
@use "scss/launcher";
@use "scss/osds";
@use "scss/notifications";
@use "scss/updates";
@use "scss/popdowns";
* {
all: unset; // Remove GTK theme styles