summaryrefslogtreecommitdiff
path: root/src/modules/navbar.tsx
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-04-08 16:01:36 +1000
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-04-08 16:01:36 +1000
commitb0f857f0d1a1bec3d9c56a6e76d54932bfc6bf01 (patch)
treefe3ede646c1a2f4f7abf3ba62cb3433113b35015 /src/modules/navbar.tsx
parentdev: use typescript 5.7.3 to fix jsx type errors (diff)
downloadcaelestia-shell-b0f857f0d1a1bec3d9c56a6e76d54932bfc6bf01.tar.gz
caelestia-shell-b0f857f0d1a1bec3d9c56a6e76d54932bfc6bf01.tar.bz2
caelestia-shell-b0f857f0d1a1bec3d9c56a6e76d54932bfc6bf01.zip
feat: add navbar
For controlling sidebar panes + other stuff later
Diffstat (limited to 'src/modules/navbar.tsx')
-rw-r--r--src/modules/navbar.tsx120
1 files changed, 120 insertions, 0 deletions
diff --git a/src/modules/navbar.tsx b/src/modules/navbar.tsx
new file mode 100644
index 0000000..b844ff4
--- /dev/null
+++ b/src/modules/navbar.tsx
@@ -0,0 +1,120 @@
+import type { Monitor } from "@/services/monitors";
+import type { AstalWidget } from "@/utils/types";
+import { Variable } from "astal";
+import { Astal, Gtk } from "astal/gtk3";
+import { navbar as config } from "config";
+import AstalHyprland from "gi://AstalHyprland";
+import SideBar, { awaitSidebar, paneNames, switchPane, type PaneName } from "./sidebar";
+
+const getPaneIcon = (name: PaneName) => {
+ if (name === "dashboard") return "dashboard";
+ if (name === "audio") return "tune";
+ if (name === "connectivity") return "settings_ethernet";
+ if (name === "packages") return "package_2";
+ if (name === "notifpane") return "notifications";
+ return "date_range";
+};
+
+const getPaneName = (name: PaneName) => {
+ if (name === "dashboard") return "Dash";
+ if (name === "audio") return "Audio";
+ if (name === "connectivity") return "Conn";
+ if (name === "packages") return "Pkgs";
+ if (name === "notifpane") return "Alrts";
+ return "Time";
+};
+
+const hookIsCurrent = (
+ self: AstalWidget,
+ sidebar: Variable<SideBar | null>,
+ name: PaneName,
+ callback: (isCurrent: boolean) => void
+) => {
+ const unsub = sidebar.subscribe(s => {
+ if (!s) return;
+ self.hook(s.shown, (_, v) => callback(s.visible && v === name));
+ self.hook(s, "notify::visible", () => callback(s.visible && s.shown.get() === name));
+ callback(s.visible && s.shown.get() === name);
+ unsub();
+ });
+};
+
+const PaneButton = ({
+ monitor,
+ name,
+ sidebar,
+}: {
+ monitor: Monitor;
+ name: PaneName;
+ sidebar: Variable<SideBar | null>;
+}) => (
+ <button
+ cursor="pointer"
+ onClick={() => switchPane(monitor, name)}
+ setup={self => hookIsCurrent(self, sidebar, name, c => self.toggleClassName("current", c))}
+ >
+ <box vertical className="pane-button">
+ <label className="icon" label={getPaneIcon(name)} />
+ <revealer
+ transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
+ transitionDuration={150}
+ setup={self => hookIsCurrent(self, sidebar, name, c => self.set_reveal_child(c))}
+ >
+ <label className="label" label={getPaneName(name)} />
+ </revealer>
+ </box>
+ </button>
+);
+
+export default ({ monitor }: { monitor: Monitor }) => {
+ const sidebar = Variable<SideBar | null>(null);
+ awaitSidebar(monitor).then(s => sidebar.set(s));
+
+ return (
+ <window
+ namespace="caelestia-navbar"
+ monitor={monitor.id}
+ anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.BOTTOM}
+ exclusivity={Astal.Exclusivity.EXCLUSIVE}
+ visible={config.persistent.get()}
+ setup={self => {
+ const hyprland = AstalHyprland.get_default();
+ const visible = Variable(config.persistent.get());
+
+ visible.poll(100, () => {
+ const width = self.visible
+ ? Math.max(config.appearWidth.get(), self.get_allocated_width())
+ : config.appearWidth.get();
+ return hyprland.get_cursor_position().x < width;
+ });
+ if (config.persistent.get()) visible.stopPoll();
+
+ self.hook(config.persistent, (_, v) => {
+ if (v) {
+ visible.stopPoll();
+ visible.set(true);
+ } else visible.startPoll();
+ });
+
+ self.hook(visible, (_, v) => self.set_visible(v));
+ self.connect("destroy", () => visible.drop());
+ }}
+ >
+ <eventbox
+ onScroll={(_, event) => {
+ const shown = sidebar.get()?.shown;
+ if (!shown) return;
+ const idx = paneNames.indexOf(shown.get());
+ if (event.delta_y > 0) shown.set(paneNames[Math.min(paneNames.length - 1, idx + 1)]);
+ else shown.set(paneNames[Math.max(0, idx - 1)]);
+ }}
+ >
+ <box vertical className="navbar">
+ {paneNames.map(n => (
+ <PaneButton monitor={monitor} name={n} sidebar={sidebar} />
+ ))}
+ </box>
+ </eventbox>
+ </window>
+ );
+};