diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/modules/bar.tsx | 152 | ||||
| -rw-r--r-- | src/modules/launcher.tsx | 23 | ||||
| -rw-r--r-- | src/modules/notifpopups.tsx | 2 | ||||
| -rw-r--r-- | src/modules/osds.tsx | 18 | ||||
| -rw-r--r-- | src/utils/system.ts | 16 | ||||
| -rw-r--r-- | src/widgets/notification.tsx | 5 | ||||
| -rw-r--r-- | src/widgets/popupwindow.ts | 2 |
7 files changed, 133 insertions, 85 deletions
diff --git a/src/modules/bar.tsx b/src/modules/bar.tsx index cf3ce5e..c1c3dfc 100644 --- a/src/modules/bar.tsx +++ b/src/modules/bar.tsx @@ -8,7 +8,7 @@ import type { AstalWidget } from "@/utils/types"; import { setupCustomTooltip } from "@/utils/widgets"; import type PopupWindow from "@/widgets/popupwindow"; import { execAsync, Variable } from "astal"; -import { bind, kebabify } from "astal/binding"; +import Binding, { bind, kebabify } from "astal/binding"; import { App, Astal, Gtk } from "astal/gtk3"; import { bar as config } from "config"; import AstalBattery from "gi://AstalBattery"; @@ -17,7 +17,7 @@ import AstalHyprland from "gi://AstalHyprland"; import AstalNetwork from "gi://AstalNetwork"; import AstalNotifd from "gi://AstalNotifd"; import AstalTray from "gi://AstalTray"; -import AstalWp01 from "gi://AstalWp"; +import AstalWp from "gi://AstalWp"; const hyprland = AstalHyprland.get_default(); @@ -83,7 +83,7 @@ const OSIcon = () => ( const ActiveWindow = () => ( <box - vertical={config.vertical} + vertical={bind(config.vertical)} className="module active-window" setup={self => { const title = Variable(""); @@ -109,14 +109,15 @@ const ActiveWindow = () => ( } /> <label - angle={config.vertical ? 270 : 0} - setup={self => - hookFocusedClientProp( - self, - "title", - c => (self.label = c?.title ? ellipsize(c.title, config.vertical ? 25 : 40) : "Desktop") - ) - } + angle={bind(config.vertical).as(v => (v ? 270 : 0))} + setup={self => { + const update = () => + (self.label = hyprland.focusedClient?.title + ? ellipsize(hyprland.focusedClient.title, config.vertical.get() ? 25 : 40) + : "Desktop"); + hookFocusedClientProp(self, "title", update); + self.hook(config.vertical, update); + }} /> </box> ); @@ -139,7 +140,7 @@ const MediaPlaying = () => { setupCustomTooltip(self, bind(label)); }} > - <box vertical={config.vertical} className="module media-playing"> + <box vertical={bind(config.vertical)} className="module media-playing"> <icon setup={self => players.hookLastPlayer(self, "notify::identity", () => { @@ -155,12 +156,14 @@ const MediaPlaying = () => { } /> <label - angle={config.vertical ? 270 : 0} - setup={self => - players.hookLastPlayer(self, ["notify::title", "notify::artist"], () => { - self.label = ellipsize(getLabel("No media"), config.vertical ? 25 : 40); // TODO: scroll text when playing or hover - }) - } + angle={bind(config.vertical).as(v => (v ? 270 : 0))} + setup={self => { + // TODO: scroll text when playing or hover + const update = () => + (self.label = ellipsize(getLabel("No media"), config.vertical.get() ? 25 : 40)); + players.hookLastPlayer(self, ["notify::title", "notify::artist"], update); + self.hook(config.vertical, update); + }} /> </box> </button> @@ -169,8 +172,8 @@ const MediaPlaying = () => { const Workspace = ({ idx }: { idx: number }) => { let wsId = hyprland.focusedWorkspace - ? Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown) * - config.modules.workspaces.shown + + ? Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown.get()) * + config.modules.workspaces.shown.get() + idx : idx; return ( @@ -184,16 +187,18 @@ const Workspace = ({ idx }: { idx: number }) => { "occupied", hyprland.clients.some(c => c.workspace?.id === wsId) ); - - self.hook(hyprland, "notify::focused-workspace", () => { + const updateWs = () => { if (!hyprland.focusedWorkspace) return; wsId = - Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown) * - config.modules.workspaces.shown + + Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown.get()) * + config.modules.workspaces.shown.get() + idx; self.toggleClassName("focused", hyprland.focusedWorkspace.id === wsId); update(); - }); + }; + + self.hook(config.modules.workspaces.shown, updateWs); + self.hook(hyprland, "notify::focused-workspace", () => updateWs); self.hook(hyprland, "client-added", update); self.hook(hyprland, "client-moved", update); self.hook(hyprland, "client-removed", update); @@ -214,10 +219,10 @@ const Workspaces = () => ( hyprland.dispatch("workspace", (event.delta_y < 0 ? "-" : "+") + 1); }} > - <box vertical={config.vertical} className="module workspaces"> - {Array.from({ length: config.modules.workspaces.shown }).map((_, idx) => ( - <Workspace idx={idx + 1} /> // Start from 1 - ))} + <box vertical={bind(config.vertical)} className="module workspaces"> + {bind(config.modules.workspaces.shown).as( + n => Array.from({ length: n }).map((_, idx) => <Workspace idx={idx + 1} />) // Start from 1 + )} </box> </eventbox> ); @@ -226,7 +231,7 @@ const TrayItem = (item: AstalTray.TrayItem) => ( <menubutton onButtonPressEvent={(_, event) => event.get_button()[1] === Astal.MouseButton.SECONDARY && item.activate(0, 0)} usePopover={false} - direction={config.vertical ? Gtk.ArrowType.RIGHT : Gtk.ArrowType.DOWN} + direction={bind(config.vertical).as(v => (v ? Gtk.ArrowType.RIGHT : Gtk.ArrowType.DOWN))} menuModel={bind(item, "menuModel")} actionGroup={bind(item, "actionGroup").as(a => ["dbusmenu", a])} setup={self => setupCustomTooltip(self, bind(item, "tooltipMarkup"))} @@ -237,7 +242,7 @@ const TrayItem = (item: AstalTray.TrayItem) => ( const Tray = () => ( <box - vertical={config.vertical} + vertical={bind(config.vertical)} className="module tray" visible={bind(AstalTray.get_default(), "items").as(i => i.length > 0)} > @@ -370,7 +375,7 @@ const BluetoothDevice = (device: AstalBluetooth.Device) => ( ); const Bluetooth = () => ( - <box vertical={config.vertical} className="bluetooth"> + <box vertical={bind(config.vertical)} className="bluetooth"> <button onClick={(self, event) => { if (event.button === Astal.MouseButton.PRIMARY) togglePopup(self, event, "bluetooth-devices"); @@ -416,7 +421,7 @@ const Bluetooth = () => ( ); const StatusIcons = () => ( - <box vertical={config.vertical} className="module status-icons"> + <box vertical={bind(config.vertical)} className="module status-icons"> <Network /> <Bluetooth /> </box> @@ -432,7 +437,7 @@ const PkgUpdates = () => ( ) } > - <box vertical={config.vertical} className="module pkg-updates"> + <box vertical={bind(config.vertical)} className="module pkg-updates"> <label className="icon" label="download" /> <label label={bind(Updates.get_default(), "numUpdates").as(String)} /> </box> @@ -453,7 +458,7 @@ const NotifCount = () => ( ) } > - <box vertical={config.vertical} className="module notif-count"> + <box vertical={bind(config.vertical)} className="module notif-count"> <label className="icon" label="info" /> <label label={bind(AstalNotifd.get_default(), "notifications").as(n => String(n.length))} /> </box> @@ -472,7 +477,7 @@ const Battery = () => { return ( <box - vertical={config.vertical} + vertical={bind(config.vertical)} className={bind(className)} setup={self => setupCustomTooltip(self, bind(tooltip))} onDestroy={() => { @@ -489,11 +494,20 @@ const Battery = () => { const DateTime = () => ( <button onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideright")} - setup={self => setupCustomTooltip(self, bindCurrentTime(config.modules.dateTime.detailedFormat))} + setup={self => + setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self)) + } > <box className="module date-time"> <label className="icon" label="calendar_month" /> - <label label={bindCurrentTime(config.modules.dateTime.format)} /> + <label + setup={self => + self.hook( + bindCurrentTime(bind(config.modules.dateTime.format), undefined, self), + (_, t) => (self.label = t) + ) + } + /> </box> </button> ); @@ -501,7 +515,9 @@ const DateTime = () => ( const DateTimeVertical = () => ( <button onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideright")} - setup={self => setupCustomTooltip(self, bindCurrentTime(config.modules.dateTime.detailedFormat))} + setup={self => + setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self)) + } > <box vertical className="module date-time"> <label className="icon" label="calendar_month" /> @@ -519,22 +535,34 @@ const Power = () => ( /> ); +const Dummy = () => <box visible={false} />; // Invisible box cause otherwise shows as text + +const bindWidget = (module: keyof typeof config.modules, Widget: () => JSX.Element) => + bind(config.modules[module].enabled).as(e => (e ? <Widget /> : <Dummy />)); + +const bindCompositeWidget = (module: keyof typeof config.modules, binding: Binding<JSX.Element>) => + bind(Variable.derive([config.modules[module].enabled, binding], (e, w) => (e ? w : <Dummy />))); + export default ({ monitor }: { monitor: Monitor }) => ( <window namespace="caelestia-bar" monitor={monitor.id} - anchor={ - Astal.WindowAnchor.TOP | - Astal.WindowAnchor.LEFT | - (config.vertical ? Astal.WindowAnchor.BOTTOM : Astal.WindowAnchor.RIGHT) - } + anchor={bind(config.vertical).as( + v => + Astal.WindowAnchor.TOP | + Astal.WindowAnchor.LEFT | + (v ? Astal.WindowAnchor.BOTTOM : Astal.WindowAnchor.RIGHT) + )} exclusivity={Astal.Exclusivity.EXCLUSIVE} > - <centerbox vertical={config.vertical} className={`bar ${config.vertical ? "vertical" : " horizontal"}`}> - <box vertical={config.vertical}> - {config.modules.osIcon.enabled && <OSIcon />} - {config.modules.activeWindow.enabled && <ActiveWindow />} - {config.modules.mediaPlaying.enabled && <MediaPlaying />} + <centerbox + vertical={bind(config.vertical)} + className={bind(config.vertical).as(v => `bar ${v ? "vertical" : " horizontal"}`)} + > + <box vertical={bind(config.vertical)}> + {bindWidget("osIcon", OSIcon)} + {bindWidget("activeWindow", ActiveWindow)} + {bindWidget("mediaPlaying", MediaPlaying)} <button expand onScroll={(_, event) => @@ -542,25 +570,31 @@ export default ({ monitor }: { monitor: Monitor }) => ( } /> </box> - {config.modules.workspaces.enabled && <Workspaces />} - <box vertical={config.vertical}> + {bindWidget("workspaces", Workspaces)} + <box vertical={bind(config.vertical)}> <button expand onScroll={(_, event) => { - const speaker = AstalWp01.get_default()?.audio.defaultSpeaker; + const speaker = AstalWp.get_default()?.audio.defaultSpeaker; if (!speaker) return; speaker.mute = false; if (event.delta_y > 0) speaker.volume -= 0.1; else speaker.volume += 0.1; }} /> - {config.modules.tray.enabled && <Tray />} - {config.modules.statusIcons.enabled && <StatusIcons />} - {config.modules.pkgUpdates.enabled && <PkgUpdates />} - {config.modules.notifCount.enabled && <NotifCount />} - {config.modules.battery.enabled && AstalBattery.get_default().isBattery && <Battery />} - {config.modules.dateTime.enabled && (config.vertical ? <DateTimeVertical /> : <DateTime />)} - {config.modules.power.enabled && <Power />} + {bindWidget("tray", Tray)} + {bindWidget("statusIcons", StatusIcons)} + {bindWidget("pkgUpdates", PkgUpdates)} + {bindWidget("notifCount", NotifCount)} + {bindCompositeWidget( + "battery", + bind(AstalBattery.get_default(), "isBattery").as(b => (b ? <Battery /> : <Dummy />)) + )} + {bindCompositeWidget( + "dateTime", + bind(config.vertical).as(v => (v ? <DateTimeVertical /> : <DateTime />)) + )} + {bindWidget("power", Power)} </box> </centerbox> </window> diff --git a/src/modules/launcher.tsx b/src/modules/launcher.tsx index 5adc86e..b7d4c01 100644 --- a/src/modules/launcher.tsx +++ b/src/modules/launcher.tsx @@ -47,8 +47,8 @@ const getEmptyTextFromMode = (mode: Mode) => { } }; -const limitLength = <T,>(arr: T[], cfg: { maxResults: number }) => - cfg.maxResults > 0 && arr.length > cfg.maxResults ? arr.slice(0, cfg.maxResults) : arr; +const limitLength = <T,>(arr: T[], cfg: { maxResults: Variable<number> }) => + cfg.maxResults.get() > 0 && arr.length > cfg.maxResults.get() ? arr.slice(0, cfg.maxResults.get()) : arr; const close = (self: JSX.Element) => { const toplevel = self.get_toplevel(); @@ -117,7 +117,7 @@ const PinnedApp = (names: string[]) => { return widget; }; -const PinnedApps = () => <box homogeneous>{config.apps.pins.map(PinnedApp)}</box>; +const PinnedApps = () => <box homogeneous>{bind(config.apps.pins).as(p => p.map(PinnedApp))}</box>; const SearchEntry = ({ entry }: { entry: Widget.Entry }) => ( <stack @@ -560,7 +560,7 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) // Create todo, notify and close execAsync(`tod t q -c ${args.join(" ")}`).catch(console.error); - if (config.todo.notify) + if (config.todo.notify.get()) notify({ summary: "Todo created", body: `Created todo with content: ${args.join(" ")}`, @@ -616,7 +616,7 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) }; const fileSearch = () => - execAsync(["fd", ...config.files.fdOpts, entry.text, HOME]) + execAsync(["fd", ...config.files.fdOpts.get(), entry.text, HOME]) .then(out => { const paths = out.split("\n").filter(path => path); self.foreach(ch => ch.destroy()); @@ -638,13 +638,16 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) if (entry.text) { const clients = fuzzysort.go(entry.text, unsortedClients, { all: true, - limit: config.windows.maxResults < 0 ? undefined : config.windows.maxResults, + limit: + config.windows.maxResults.get() < 0 + ? undefined + : config.windows.maxResults.get(), keys: ["title", "class", "initialTitle", "initialClass"], scoreFn: r => - r[0].score * config.windows.weights.title + - r[1].score * config.windows.weights.class + - r[2].score * config.windows.weights.initialTitle + - r[3].score * config.windows.weights.initialClass, + r[0].score * config.windows.weights.title.get() + + r[1].score * config.windows.weights.class.get() + + r[2].score * config.windows.weights.initialTitle.get() + + r[3].score * config.windows.weights.initialClass.get(), }); self.foreach(ch => ch.destroy()); for (const { obj } of clients) diff --git a/src/modules/notifpopups.tsx b/src/modules/notifpopups.tsx index 9e34549..f1ae9b6 100644 --- a/src/modules/notifpopups.tsx +++ b/src/modules/notifpopups.tsx @@ -40,7 +40,7 @@ export default () => ( ); // Limit number of popups - if (config.maxPopups > 0 && self.children.length > config.maxPopups) + if (config.maxPopups.get() > 0 && self.children.length > config.maxPopups.get()) map.values().next().value?.destroyWithAnims(); }); self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims()); diff --git a/src/modules/osds.tsx b/src/modules/osds.tsx index f95053e..4bbc87b 100644 --- a/src/modules/osds.tsx +++ b/src/modules/osds.tsx @@ -1,6 +1,6 @@ import Monitors, { type Monitor } from "@/services/monitors"; import PopupWindow from "@/widgets/popupwindow"; -import { execAsync, register, timeout, Variable, type Time } from "astal"; +import { bind, execAsync, register, timeout, Variable, type Time } from "astal"; import { App, Astal, Gtk, Widget } from "astal/gtk3"; import cairo from "cairo"; import { osds as config } from "config"; @@ -52,13 +52,13 @@ const SliderOsd = ({ name={type} monitor={monitor?.id} keymode={Astal.Keymode.NONE} - anchor={config[type].position} - margin={config[type].margin} + anchor={bind(config[type].position)} + margin={bind(config[type].margin)} setup={self => { let time: Time | null = null; const hideAfterTimeout = () => { time?.cancel(); - time = timeout(config[type].hideDelay, () => self.hide()); + time = timeout(config[type].hideDelay.get(), () => self.hide()); }; self.connect("show", hideAfterTimeout); windowSetup(self, () => { @@ -74,8 +74,8 @@ const SliderOsd = ({ setup={self => { const halfPi = Math.PI / 2; const vertical = - config[type].position === Astal.WindowAnchor.LEFT || - config[type].position === Astal.WindowAnchor.RIGHT; + config[type].position.get() === Astal.WindowAnchor.LEFT || + config[type].position.get() === Astal.WindowAnchor.RIGHT; const icon = Variable(""); drawAreaSetup(self, icon); @@ -134,7 +134,7 @@ const SliderOsd = ({ // Progress number, at top/right let nw = 0; let nh = 0; - if (config[type].showValue) { + if (config[type].showValue.get()) { const numLayout = parent.create_pango_layout(String(Math.round(progressValue * 100))); [nw, nh] = numLayout.get_pixel_size(); let diff; @@ -295,7 +295,7 @@ class LockOsd extends Widget.Window { const child = this.get_child(); if (!child) return; this[right ? "marginLeft" : "marginRight"] = window.visible - ? child.get_preferred_width()[1] + config.lock.spacing + ? child.get_preferred_width()[1] + config.lock.spacing.get() : 0; } }); @@ -311,7 +311,7 @@ class LockOsd extends Widget.Window { super.show(); this.#update(); this.#timeout?.cancel(); - this.#timeout = timeout(config.lock[this.lockType].hideDelay, () => this.hide()); + this.#timeout = timeout(config.lock[this.lockType].hideDelay.get(), () => this.hide()); } } diff --git a/src/utils/system.ts b/src/utils/system.ts index 8180e48..7ae23dd 100644 --- a/src/utils/system.ts +++ b/src/utils/system.ts @@ -1,4 +1,4 @@ -import { bind, execAsync, GLib, Variable, type Gio } from "astal"; +import { bind, execAsync, GLib, Variable, type Binding, type Gio } from "astal"; import type AstalApps from "gi://AstalApps"; import { osIcons } from "./icons"; @@ -63,5 +63,15 @@ export const osIcon = (() => { })(); export const currentTime = Variable(GLib.DateTime.new_now_local()).poll(1000, () => GLib.DateTime.new_now_local()); -export const bindCurrentTime = (format: string, fallback?: (time: GLib.DateTime) => string) => - bind(currentTime).as(c => c.format(format) ?? fallback?.(c) ?? new Date().toLocaleString()); +export const bindCurrentTime = ( + format: Binding<string> | string, + fallback?: (time: GLib.DateTime) => string, + self?: JSX.Element +) => { + const fmt = (c: GLib.DateTime, format: string) => c.format(format) ?? fallback?.(c) ?? new Date().toLocaleString(); + if (typeof format === "string") return bind(currentTime).as(c => fmt(c, format)); + if (!self) throw new Error("bindCurrentTime: self is required when format is a Binding"); + const time = Variable.derive([currentTime, format], (c, f) => fmt(c, f)); + self?.connect("destroy", () => time.drop()); + return bind(time); +}; diff --git a/src/widgets/notification.tsx b/src/widgets/notification.tsx index 6f67b78..10f2076 100644 --- a/src/widgets/notification.tsx +++ b/src/widgets/notification.tsx @@ -20,7 +20,7 @@ const getTime = (time: number) => { const now = GLib.DateTime.new_now_local(); const todayDay = now.get_day_of_year(); - if (config.agoTime) { + if (config.agoTime.get()) { const diff = now.difference(messageTime) / 1e6; if (diff < 60) return "Now"; if (diff < 3600) { @@ -75,6 +75,7 @@ export default class Notification extends Widget.Box { super({ className: "notification" }); const time = Variable(getTime(notification.time)).poll(60000, () => getTime(notification.time)); + this.hook(config.agoTime, () => time.set(getTime(notification.time))); this.#revealer = ( <revealer @@ -138,7 +139,7 @@ export default class Notification extends Widget.Box { }); // Close popup after timeout if transient or expire enabled in config - if (popup && (config.expire || notification.transient)) + if (popup && (config.expire.get() || notification.transient)) timeout( notification.expireTimeout > 0 ? notification.expireTimeout diff --git a/src/widgets/popupwindow.ts b/src/widgets/popupwindow.ts index 2ba49fc..624e9a5 100644 --- a/src/widgets/popupwindow.ts +++ b/src/widgets/popupwindow.ts @@ -38,7 +38,7 @@ export default class PopupWindow extends Widget.Window { let marginLeft = 0; let marginTop = 0; - if (bar.vertical) { + if (bar.vertical.get()) { marginLeft = cx + (width - x); marginTop = cy + ((height - pHeight) / 2 - y); if (marginTop < 0) marginTop = 0; |