sidebar: notifpane + scroll switch panes

Also placeholder for empty lists (notifs and events)
To switch panes, primary click + scroll
This commit is contained in:
2 * r + 2 * t 2025-03-26 13:03:20 +11:00
parent c336bd184e
commit 4ae1651720
7 changed files with 94 additions and 25 deletions

View file

@ -73,6 +73,15 @@
} }
} }
.empty {
color: scheme.$subtext0;
font-size: lib.s(18);
.icon {
font-size: lib.s(48);
}
}
.user { .user {
@include lib.spacing(15); @include lib.spacing(15);

View file

@ -1,3 +1,4 @@
import type SideBar from "@/modules/sidebar";
import type { Monitor } from "@/services/monitors"; import type { Monitor } from "@/services/monitors";
import Players from "@/services/players"; import Players from "@/services/players";
import Updates from "@/services/updates"; import Updates from "@/services/updates";
@ -72,10 +73,19 @@ const togglePopup = (self: JSX.Element, event: Astal.ClickEvent, name: string) =
} }
}; };
const switchPane = (name: string) => {
const sidebar = App.get_window("sidebar") as SideBar | null;
if (sidebar) {
if (sidebar.visible && sidebar.shown.get() === name) sidebar.hide();
else sidebar.show();
sidebar.shown.set(name);
}
};
const OSIcon = () => ( const OSIcon = () => (
<button <button
className="module os-icon" className="module os-icon"
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideleft")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("dashboard")}
> >
{osIcon} {osIcon}
</button> </button>
@ -446,9 +456,7 @@ const PkgUpdates = () => (
const NotifCount = () => ( const NotifCount = () => (
<button <button
onClick={(self, event) => onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("notifpane")}
event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "notifications")
}
setup={self => setup={self =>
setupCustomTooltip( setupCustomTooltip(
self, self,

View file

@ -127,7 +127,7 @@ export default () => (
<Media player={p} /> <Media player={p} />
))} ))}
<box className="separator" /> <box className="separator" />
<Notifications /> <Notifications compact />
<box className="separator" /> <box className="separator" />
<Upcoming /> <Upcoming />
</box> </box>

View file

@ -1,7 +1,8 @@
import type { Monitor } from "@/services/monitors"; import type { Monitor } from "@/services/monitors";
import { bind, register, Variable } from "astal"; import { bind, register, Variable } from "astal";
import { App, Astal, Gtk, Widget } from "astal/gtk3"; import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import Dashboard from "./dashboard"; import Dashboard from "./dashboard";
import NotifPane from "./notifpane";
@register() @register()
export default class SideBar extends Widget.Window { export default class SideBar extends Widget.Window {
@ -18,7 +19,18 @@ export default class SideBar extends Widget.Window {
// visible: false, // visible: false,
}); });
const panes = [<Dashboard />, <NotifPane />];
this.add( this.add(
<eventbox
onScroll={(_, event) => {
if (event.modifier & Gdk.ModifierType.BUTTON1_MASK) {
const index = panes.findIndex(p => p.name === this.shown.get()) + (event.delta_y < 0 ? -1 : 1);
if (index < 0 || index >= panes.length) return;
this.shown.set(panes[index].name);
}
}}
>
<box vertical className="sidebar"> <box vertical className="sidebar">
<stack <stack
vexpand vexpand
@ -26,9 +38,10 @@ export default class SideBar extends Widget.Window {
transitionDuration={200} transitionDuration={200}
shown={bind(this.shown)} shown={bind(this.shown)}
> >
<Dashboard /> {panes}
</stack> </stack>
</box> </box>
</eventbox>
); );
} }
} }

View file

@ -3,7 +3,7 @@ import { bind } from "astal";
import { Astal, Gtk } from "astal/gtk3"; import { Astal, Gtk } from "astal/gtk3";
import AstalNotifd from "gi://AstalNotifd"; import AstalNotifd from "gi://AstalNotifd";
const List = () => ( const List = ({ compact }: { compact?: boolean }) => (
<box <box
vertical vertical
valign={Gtk.Align.START} valign={Gtk.Align.START}
@ -13,7 +13,7 @@ const List = () => (
const map = new Map<number, Notification>(); const map = new Map<number, Notification>();
const addNotification = (notification: AstalNotifd.Notification) => { const addNotification = (notification: AstalNotifd.Notification) => {
const notif = (<Notification notification={notification} compact />) as Notification; const notif = (<Notification notification={notification} compact={compact} />) as Notification;
notif.connect("destroy", () => map.get(notification.id) === notif && map.delete(notification.id)); notif.connect("destroy", () => map.get(notification.id) === notif && map.delete(notification.id));
map.get(notification.id)?.destroyWithAnims(); map.get(notification.id)?.destroyWithAnims();
map.set(notification.id, notif); map.set(notification.id, notif);
@ -42,7 +42,16 @@ const List = () => (
/> />
); );
export default () => ( const NoNotifs = () => (
<box homogeneous name="empty">
<box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className="empty">
<label className="icon" label="mark_email_unread" />
<label label="All caught up!" />
</box>
</box>
);
export default ({ compact }: { compact?: boolean }) => (
<box vertical className="notifications"> <box vertical className="notifications">
<box className="header-bar"> <box className="header-bar">
<label <label
@ -63,8 +72,15 @@ export default () => (
label="󰎟 Clear" label="󰎟 Clear"
/> />
</box> </box>
<scrollable expand hscroll={Gtk.PolicyType.NEVER}> <stack
<List /> transitionType={Gtk.StackTransitionType.CROSSFADE}
transitionDuration={200}
shown={bind(AstalNotifd.get_default(), "notifications").as(n => (n.length > 0 ? "list" : "empty"))}
>
<NoNotifs />
<scrollable expand hscroll={Gtk.PolicyType.NEVER} name="list">
<List compact={compact} />
</scrollable> </scrollable>
</stack>
</box> </box>
); );

View file

@ -61,6 +61,15 @@ const List = () => (
</box> </box>
); );
const NoEvents = () => (
<box homogeneous name="empty">
<box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className="empty">
<label className="icon" label="calendar_month" />
<label label="No upcoming events" />
</box>
</box>
);
export default () => ( export default () => (
<box vertical className="upcoming"> <box vertical className="upcoming">
<box className="header-bar"> <box className="header-bar">
@ -76,8 +85,15 @@ export default () => (
label="󰑓 Reload" label="󰑓 Reload"
/> />
</box> </box>
<scrollable className="list" hscroll={Gtk.PolicyType.NEVER}> <stack
transitionType={Gtk.StackTransitionType.CROSSFADE}
transitionDuration={200}
shown={bind(Calendar.get_default(), "numUpcoming").as(n => (n > 0 ? "list" : "empty"))}
>
<NoEvents />
<scrollable expand hscroll={Gtk.PolicyType.NEVER} name="list">
<List /> <List />
</scrollable> </scrollable>
</stack>
</box> </box>
); );

View file

@ -0,0 +1,7 @@
import Notifications from "./modules/notifications";
export default () => (
<box vertical className="pane notifpane" name="notifpane">
<Notifications />
</box>
);