notifications: make popup window
This commit is contained in:
parent
02fd2e97f2
commit
ed05e9af25
12 changed files with 302 additions and 155 deletions
2
app.tsx
2
app.tsx
|
|
@ -2,6 +2,7 @@ import { execAsync, GLib, writeFileAsync } from "astal";
|
||||||
import { App } from "astal/gtk3";
|
import { App } from "astal/gtk3";
|
||||||
import Bar from "./src/modules/bar";
|
import Bar from "./src/modules/bar";
|
||||||
import Launcher from "./src/modules/launcher";
|
import Launcher from "./src/modules/launcher";
|
||||||
|
import Notifications from "./src/modules/notifications";
|
||||||
import NotifPopups from "./src/modules/notifpopups";
|
import NotifPopups from "./src/modules/notifpopups";
|
||||||
import Osds from "./src/modules/osds";
|
import Osds from "./src/modules/osds";
|
||||||
import Monitors from "./src/services/monitors";
|
import Monitors from "./src/services/monitors";
|
||||||
|
|
@ -24,6 +25,7 @@ App.start({
|
||||||
<NotifPopups />;
|
<NotifPopups />;
|
||||||
<Osds />;
|
<Osds />;
|
||||||
Monitors.get_default().forEach(m => <Bar monitor={m} />);
|
Monitors.get_default().forEach(m => <Bar monitor={m} />);
|
||||||
|
<Notifications />;
|
||||||
|
|
||||||
console.log("Caelestia started");
|
console.log("Caelestia started");
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ $scale: 0.068rem;
|
||||||
border: s($width) $style color.change($colour, $alpha: $alpha);
|
border: s($width) $style color.change($colour, $alpha: $alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin shadow($colour: scheme.$mantle, $alpha: 0.4, $x: 2, $y: 3, $blur: 8, $spread: 0) {
|
@mixin shadow($colour: black, $alpha: 0.4, $x: 2, $y: 3, $blur: 8, $spread: 0) {
|
||||||
box-shadow: s($x) s($y) s($blur) s($spread) color.change($colour, $alpha: $alpha);
|
box-shadow: s($x) s($y) s($blur) s($spread) color.change($colour, $alpha: $alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
color: scheme.$blue;
|
color: scheme.$blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications {
|
.unread {
|
||||||
color: scheme.$mauve;
|
color: scheme.$mauve;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
101
scss/notifications.scss
Normal file
101
scss/notifications.scss
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
@use "sass:color";
|
||||||
|
@use "scheme";
|
||||||
|
@use "lib";
|
||||||
|
@use "font";
|
||||||
|
|
||||||
|
@mixin popup($accent) {
|
||||||
|
.separator {
|
||||||
|
background-color: $accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
@include lib.border($accent, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications {
|
||||||
|
@include lib.rounded(8);
|
||||||
|
@include lib.border(scheme.$mauve, 0.4, 2);
|
||||||
|
@include lib.shadow;
|
||||||
|
|
||||||
|
min-width: lib.s(400);
|
||||||
|
min-height: lib.s(600);
|
||||||
|
background-color: scheme.$base;
|
||||||
|
color: scheme.$mauve;
|
||||||
|
padding: lib.s(10) lib.s(12);
|
||||||
|
|
||||||
|
.header {
|
||||||
|
@include font.mono;
|
||||||
|
@include lib.spacing(8);
|
||||||
|
|
||||||
|
padding: 0 lib.s(5);
|
||||||
|
margin-bottom: lib.s(8);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.enabled {
|
||||||
|
background-color: scheme.$mauve;
|
||||||
|
color: scheme.$base;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: color.mix(scheme.$mauve, scheme.$base, 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: color.mix(scheme.$mauve, scheme.$base, 70%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
.wrapper {
|
||||||
|
padding-bottom: lib.s(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
background-color: color.mix(scheme.$surface0, scheme.$base, 70%);
|
||||||
|
|
||||||
|
&.low {
|
||||||
|
@include popup(scheme.$overlay0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.normal {
|
||||||
|
@include lib.border(scheme.$lavender, 0.3);
|
||||||
|
@include popup(scheme.$lavender);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.critical {
|
||||||
|
@include lib.border(scheme.$red, 0.5);
|
||||||
|
@include popup(scheme.$red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > * {
|
||||||
|
background-color: scheme.$surface1;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: scheme.$surface2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: scheme.$overlay0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,86 +21,22 @@
|
||||||
min-width: lib.s(410);
|
min-width: lib.s(410);
|
||||||
padding-left: lib.s(10); // So notifications can overshoot for init animation
|
padding-left: lib.s(10); // So notifications can overshoot for init animation
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
padding-top: lib.s(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
@include lib.rounded(8, $tr: 0, $br: 0);
|
.wrapper {
|
||||||
@include lib.shadow;
|
padding-top: lib.s(10);
|
||||||
@include font.main;
|
|
||||||
@include popup(scheme.$lavender);
|
|
||||||
|
|
||||||
background-color: scheme.$base;
|
|
||||||
color: scheme.$text;
|
|
||||||
padding: lib.s(10) lib.s(12);
|
|
||||||
|
|
||||||
@include lib.spacing($vertical: true);
|
|
||||||
|
|
||||||
&.low {
|
|
||||||
@include popup(scheme.$overlay0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.critical {
|
.inner {
|
||||||
@include popup(scheme.$red);
|
@include lib.rounded(8, $tr: 0, $br: 0);
|
||||||
}
|
@include lib.shadow;
|
||||||
}
|
@include popup(scheme.$lavender);
|
||||||
|
|
||||||
.header,
|
&.low {
|
||||||
.content {
|
@include popup(scheme.$overlay0);
|
||||||
padding: 0 lib.s(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
@include font.mono;
|
|
||||||
@include lib.spacing(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
@include lib.spacing(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-icon {
|
|
||||||
font-size: lib.s(18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
@include lib.rounded(10);
|
|
||||||
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
min-width: lib.s(64);
|
|
||||||
min-height: lib.s(64);
|
|
||||||
margin-top: lib.s(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary {
|
|
||||||
@include font.title;
|
|
||||||
|
|
||||||
font-size: lib.s(16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
font-size: lib.s(14);
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
@include lib.spacing;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
@include lib.rounded(5);
|
|
||||||
@include lib.element-decel;
|
|
||||||
|
|
||||||
padding: lib.s(5) lib.s(10);
|
|
||||||
background-color: scheme.$surface0;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background-color: scheme.$surface1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&.critical {
|
||||||
background-color: scheme.$surface2;
|
@include popup(scheme.$red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,78 @@ label.icon {
|
||||||
@include font.icon;
|
@include font.icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
.inner {
|
||||||
|
@include lib.rounded(8);
|
||||||
|
@include font.main;
|
||||||
|
|
||||||
|
background-color: scheme.$base;
|
||||||
|
color: scheme.$text;
|
||||||
|
padding: lib.s(10) lib.s(12);
|
||||||
|
|
||||||
|
@include lib.spacing($vertical: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header,
|
||||||
|
.content {
|
||||||
|
padding: 0 lib.s(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
@include font.mono;
|
||||||
|
@include lib.spacing(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
@include lib.spacing(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-icon {
|
||||||
|
font-size: lib.s(18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
@include lib.rounded(10);
|
||||||
|
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
min-width: lib.s(64);
|
||||||
|
min-height: lib.s(64);
|
||||||
|
margin-top: lib.s(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
@include font.title;
|
||||||
|
|
||||||
|
font-size: lib.s(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
font-size: lib.s(14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
@include lib.spacing;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
@include lib.rounded(5);
|
||||||
|
@include lib.element-decel;
|
||||||
|
|
||||||
|
padding: lib.s(5) lib.s(10);
|
||||||
|
background-color: scheme.$surface0;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: scheme.$surface1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: scheme.$surface2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
separator,
|
separator,
|
||||||
.separator {
|
.separator {
|
||||||
@include lib.rounded(2);
|
@include lib.rounded(2);
|
||||||
|
|
@ -112,9 +184,6 @@ tooltip {
|
||||||
|
|
||||||
scrollbar {
|
scrollbar {
|
||||||
trough {
|
trough {
|
||||||
@include lib.rounded(1000);
|
|
||||||
|
|
||||||
min-width: lib.s(12);
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,12 +191,13 @@ scrollbar {
|
||||||
@include lib.rounded(1000);
|
@include lib.rounded(1000);
|
||||||
@include lib.element-decel;
|
@include lib.element-decel;
|
||||||
|
|
||||||
min-width: lib.s(6);
|
min-width: lib.s(3);
|
||||||
min-height: lib.s(30);
|
min-height: lib.s(30);
|
||||||
background-color: color.change(scheme.$overlay0, $alpha: 0.3);
|
background-color: color.change(scheme.$overlay0, $alpha: 0.3);
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
|
min-width: lib.s(6);
|
||||||
background-color: color.change(scheme.$overlay0, $alpha: 0.4);
|
background-color: color.change(scheme.$overlay0, $alpha: 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { getAppCategoryIcon } from "../utils/icons";
|
||||||
import { ellipsize } from "../utils/strings";
|
import { ellipsize } from "../utils/strings";
|
||||||
import { osIcon } from "../utils/system";
|
import { osIcon } from "../utils/system";
|
||||||
import { setupCustomTooltip } from "../utils/widgets";
|
import { setupCustomTooltip } from "../utils/widgets";
|
||||||
|
import type PopupWindow from "../widgets/popupwindow";
|
||||||
|
|
||||||
const hyprland = AstalHyprland.get_default();
|
const hyprland = AstalHyprland.get_default();
|
||||||
|
|
||||||
|
|
@ -385,11 +386,19 @@ const PkgUpdates = () => (
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Notifications = () => {
|
const Unread = () => {
|
||||||
const unreadCount = Variable(0);
|
const unreadCount = Variable(0);
|
||||||
return (
|
return (
|
||||||
<box
|
<button
|
||||||
className="module notifications"
|
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);
|
||||||
|
}
|
||||||
|
} else if (event.button === Astal.MouseButton.SECONDARY) unreadCount.set(0);
|
||||||
|
}}
|
||||||
setup={self =>
|
setup={self =>
|
||||||
setupCustomTooltip(
|
setupCustomTooltip(
|
||||||
self,
|
self,
|
||||||
|
|
@ -397,39 +406,37 @@ const Notifications = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<label className="icon" label="info" />
|
<box className="module unread">
|
||||||
<label
|
<label className="icon" label="info" />
|
||||||
label="0"
|
<label
|
||||||
setup={self => {
|
label={bind(unreadCount).as(String)}
|
||||||
const notifd = AstalNotifd.get_default();
|
setup={self => {
|
||||||
let notifsOpen = false;
|
const notifd = AstalNotifd.get_default();
|
||||||
let unread = new Set<number>();
|
let notifsOpen = false;
|
||||||
|
let unread = new Set<number>();
|
||||||
|
self.hook(unreadCount, (_, u) => u === 0 && unread.clear());
|
||||||
|
|
||||||
self.hook(notifd, "notified", (self, id, replaced) => {
|
self.hook(notifd, "notified", (_, id, replaced) => {
|
||||||
if (!notifsOpen && !replaced) {
|
if (!notifsOpen && !replaced) {
|
||||||
unread.add(id);
|
unread.add(id);
|
||||||
unreadCount.set(unread.size);
|
unreadCount.set(unread.size);
|
||||||
self.label = String(unread.size);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.hook(notifd, "resolved", (self, id) => {
|
|
||||||
if (unread.delete(id)) {
|
|
||||||
unreadCount.set(unread.size);
|
|
||||||
self.label = String(unread.size);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.hook(App, "window-toggled", (_, window) => {
|
|
||||||
if (window.name === "notifications") {
|
|
||||||
notifsOpen = window.visible;
|
|
||||||
if (notifsOpen) {
|
|
||||||
unread.clear();
|
|
||||||
unreadCount.set(0);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
self.hook(notifd, "resolved", (_, id) => {
|
||||||
}}
|
if (unread.delete(id)) {
|
||||||
/>
|
unreadCount.set(unread.size);
|
||||||
</box>
|
}
|
||||||
|
});
|
||||||
|
self.hook(App, "window-toggled", (_, window) => {
|
||||||
|
if (window.name === "notifications") {
|
||||||
|
notifsOpen = window.visible;
|
||||||
|
if (notifsOpen) unreadCount.set(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -491,7 +498,7 @@ export default ({ monitor }: { monitor: Monitor }) => (
|
||||||
<Tray />
|
<Tray />
|
||||||
<StatusIcons />
|
<StatusIcons />
|
||||||
<PkgUpdates />
|
<PkgUpdates />
|
||||||
<Notifications />
|
<Unread />
|
||||||
<DateTime />
|
<DateTime />
|
||||||
<Power />
|
<Power />
|
||||||
</box>
|
</box>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { Gtk } from "astal/gtk3";
|
import { bind } from "astal";
|
||||||
|
import { Astal, Gtk } from "astal/gtk3";
|
||||||
import AstalNotifd from "gi://AstalNotifd";
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
import { PopupWindow, setupChildClickthrough } from "../utils/widgets";
|
import Notification from "../widgets/notification";
|
||||||
|
import PopupWindow from "../widgets/popupwindow";
|
||||||
|
|
||||||
const List = () => (
|
const List = () => (
|
||||||
<box
|
<box
|
||||||
|
|
@ -9,49 +11,63 @@ const List = () => (
|
||||||
className="list"
|
className="list"
|
||||||
setup={self => {
|
setup={self => {
|
||||||
const notifd = AstalNotifd.get_default();
|
const notifd = AstalNotifd.get_default();
|
||||||
const map = new Map<number, NotifPopup>();
|
const map = new Map<number, Notification>();
|
||||||
self.hook(notifd, "notified", (self, id) => {
|
|
||||||
const notification = notifd.get_notification(id);
|
|
||||||
|
|
||||||
const popup = (<NotifPopup notification={notification} />) as NotifPopup;
|
const addNotification = (notification: AstalNotifd.Notification) => {
|
||||||
popup.connect("destroy", () => map.get(notification.id) === popup && map.delete(notification.id));
|
const notif = (<Notification notification={notification} />) as Notification;
|
||||||
|
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, popup);
|
map.set(notification.id, notif);
|
||||||
|
|
||||||
self.add(
|
self.pack_end(
|
||||||
<eventbox
|
<eventbox
|
||||||
// Dismiss on middle click
|
// Dismiss on middle click
|
||||||
onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()}
|
onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()}
|
||||||
// Close on hover lost
|
|
||||||
onHoverLost={() => popup.destroyWithAnims()}
|
|
||||||
>
|
>
|
||||||
{popup}
|
{notif}
|
||||||
</eventbox>
|
</eventbox>,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
0
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Limit number of popups
|
notifd
|
||||||
if (config.maxPopups > 0 && self.children.length > config.maxPopups)
|
.get_notifications()
|
||||||
map.values().next().value?.destroyWithAnims();
|
.sort((a, b) => a.time - b.time)
|
||||||
});
|
.forEach(addNotification);
|
||||||
|
|
||||||
|
self.hook(notifd, "notified", (_, id) => addNotification(notifd.get_notification(id)));
|
||||||
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
|
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
|
||||||
|
|
||||||
// Change input region to child region so can click through empty space
|
|
||||||
setupChildClickthrough(self);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default class Notifications extends PopupWindow {
|
export default () => (
|
||||||
constructor() {
|
<PopupWindow name="notifications">
|
||||||
super({
|
<box vertical className="notifications">
|
||||||
name: "notifications",
|
<box className="header">
|
||||||
child: (
|
<label
|
||||||
<box>
|
label={bind(AstalNotifd.get_default(), "notifications").as(
|
||||||
<List />
|
n => `${n.length} notification${n.length === 1 ? "" : "s"}`
|
||||||
</box>
|
)}
|
||||||
),
|
/>
|
||||||
});
|
<box hexpand />
|
||||||
|
<button
|
||||||
setupChildClickthrough(self);
|
cursor="pointer"
|
||||||
}
|
onClicked={() => (AstalNotifd.get_default().dontDisturb = !AstalNotifd.get_default().dontDisturb)}
|
||||||
}
|
label="Silence"
|
||||||
|
className={bind(AstalNotifd.get_default(), "dontDisturb").as(d => (d ? "enabled" : ""))}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => AstalNotifd.get_default().notifications.forEach(n => n.dismiss())}
|
||||||
|
label="Clear"
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
<scrollable expand hscroll={Gtk.PolicyType.NEVER}>
|
||||||
|
<List />
|
||||||
|
</scrollable>
|
||||||
|
</box>
|
||||||
|
</PopupWindow>
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Astal, Gtk } from "astal/gtk3";
|
import { App, Astal, Gtk } from "astal/gtk3";
|
||||||
import AstalNotifd from "gi://AstalNotifd";
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
import { notifpopups as config } from "../../config";
|
import { notifpopups as config } from "../../config";
|
||||||
import { setupChildClickthrough } from "../utils/widgets";
|
import { setupChildClickthrough } from "../utils/widgets";
|
||||||
|
|
@ -16,7 +16,11 @@ export default () => (
|
||||||
setup={self => {
|
setup={self => {
|
||||||
const notifd = AstalNotifd.get_default();
|
const notifd = AstalNotifd.get_default();
|
||||||
const map = new Map<number, Notification>();
|
const map = new Map<number, Notification>();
|
||||||
|
let notifsOpen = false;
|
||||||
|
|
||||||
self.hook(notifd, "notified", (self, id) => {
|
self.hook(notifd, "notified", (self, id) => {
|
||||||
|
if (notifsOpen) return;
|
||||||
|
|
||||||
const notification = notifd.get_notification(id);
|
const notification = notifd.get_notification(id);
|
||||||
|
|
||||||
const popup = (<Notification popup notification={notification} />) as Notification;
|
const popup = (<Notification popup notification={notification} />) as Notification;
|
||||||
|
|
@ -41,6 +45,10 @@ export default () => (
|
||||||
});
|
});
|
||||||
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
|
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
|
||||||
|
|
||||||
|
self.hook(App, "window-toggled", (_, window) => {
|
||||||
|
if (window.name === "notifications") notifsOpen = window.visible;
|
||||||
|
});
|
||||||
|
|
||||||
// Change input region to child region so can click through empty space
|
// Change input region to child region so can click through empty space
|
||||||
setupChildClickthrough(self);
|
setupChildClickthrough(self);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -57,12 +57,16 @@ export default class Notification extends Widget.Box {
|
||||||
#destroyed = false;
|
#destroyed = false;
|
||||||
|
|
||||||
constructor({ notification, popup }: { notification: AstalNotifd.Notification; popup?: boolean }) {
|
constructor({ notification, popup }: { notification: AstalNotifd.Notification; popup?: boolean }) {
|
||||||
super();
|
super({ className: "notification" });
|
||||||
|
|
||||||
this.#revealer = (
|
this.#revealer = (
|
||||||
<revealer revealChild transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN} transitionDuration={150}>
|
<revealer
|
||||||
|
revealChild={popup}
|
||||||
|
transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
|
||||||
|
transitionDuration={150}
|
||||||
|
>
|
||||||
<box className="wrapper">
|
<box className="wrapper">
|
||||||
<box vertical className={`notification ${urgencyToString(notification.urgency)}`}>
|
<box vertical className={`inner ${urgencyToString(notification.urgency)}`}>
|
||||||
<box className="header">
|
<box className="header">
|
||||||
<AppIcon appIcon={notification.appIcon} desktopEntry={notification.appName} />
|
<AppIcon appIcon={notification.appIcon} desktopEntry={notification.appName} />
|
||||||
<label className="app-name" label={notification.appName ?? "Unknown"} />
|
<label className="app-name" label={notification.appName ?? "Unknown"} />
|
||||||
|
|
@ -101,6 +105,7 @@ export default class Notification extends Widget.Box {
|
||||||
const width = this.get_preferred_width()[1];
|
const width = this.get_preferred_width()[1];
|
||||||
this.css = `margin-left: ${width}px; margin-right: -${width}px;`;
|
this.css = `margin-left: ${width}px; margin-right: -${width}px;`;
|
||||||
timeout(1, () => {
|
timeout(1, () => {
|
||||||
|
this.#revealer.revealChild = true;
|
||||||
this.css = `transition: 300ms cubic-bezier(0.05, 0.9, 0.1, 1.1); margin-left: 0; margin-right: 0;`;
|
this.css = `transition: 300ms cubic-bezier(0.05, 0.9, 0.1, 1.1); margin-left: 0; margin-right: 0;`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,11 @@ export default class PopupWindow extends Widget.Window {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
popup_at_widget(widget: JSX.Element, event: Gdk.Event) {
|
popup_at_widget(widget: JSX.Element, event: Gdk.Event | Astal.ClickEvent) {
|
||||||
const { width, height } = widget.get_allocation();
|
const { width, height } = widget.get_allocation();
|
||||||
const [_, x, y] = event.get_coords();
|
const [_, x, y] = event instanceof Gdk.Event ? event.get_coords() : [null, event.x, event.y];
|
||||||
const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
|
const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
|
||||||
|
this.anchor = Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT;
|
||||||
this.marginLeft = cx + ((width - this.get_preferred_width()[1]) / 2 - x);
|
this.marginLeft = cx + ((width - this.get_preferred_width()[1]) / 2 - x);
|
||||||
this.marginTop = cy + (height - y);
|
this.marginTop = cy + (height - y);
|
||||||
this.show();
|
this.show();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
@use "scss/notifpopups";
|
@use "scss/notifpopups";
|
||||||
@use "scss/launcher";
|
@use "scss/launcher";
|
||||||
@use "scss/osds";
|
@use "scss/osds";
|
||||||
|
@use "scss/notifications";
|
||||||
|
|
||||||
* {
|
* {
|
||||||
all: unset; // Remove GTK theme styles
|
all: unset; // Remove GTK theme styles
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue