refactor: move ts to src
Also move popupwindow to own file
This commit is contained in:
parent
476fbe69d3
commit
02fd2e97f2
20 changed files with 209 additions and 147 deletions
12
app.tsx
12
app.tsx
|
|
@ -1,11 +1,11 @@
|
|||
import { execAsync, GLib, writeFileAsync } from "astal";
|
||||
import { App } from "astal/gtk3";
|
||||
import Bar from "./modules/bar";
|
||||
import Launcher from "./modules/launcher";
|
||||
import NotifPopups from "./modules/notifpopups";
|
||||
import Osds from "./modules/osds";
|
||||
import Monitors from "./services/monitors";
|
||||
import Players from "./services/players";
|
||||
import Bar from "./src/modules/bar";
|
||||
import Launcher from "./src/modules/launcher";
|
||||
import NotifPopups from "./src/modules/notifpopups";
|
||||
import Osds from "./src/modules/osds";
|
||||
import Monitors from "./src/services/monitors";
|
||||
import Players from "./src/services/players";
|
||||
|
||||
const loadStyleAsync = async () => {
|
||||
if (!GLib.file_test(`${SRC}/scss/scheme/_index.scss`, GLib.FileTest.EXISTS))
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
padding-top: lib.s(10);
|
||||
}
|
||||
|
||||
.popup {
|
||||
.notification {
|
||||
@include lib.rounded(8, $tr: 0, $br: 0);
|
||||
@include lib.shadow;
|
||||
@include font.main;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import AstalNetwork from "gi://AstalNetwork";
|
|||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import AstalTray from "gi://AstalTray";
|
||||
import AstalWp01 from "gi://AstalWp";
|
||||
import { bar as config } from "../config";
|
||||
import { bar as config } from "../../config";
|
||||
import type { Monitor } from "../services/monitors";
|
||||
import Players from "../services/players";
|
||||
import Updates from "../services/updates";
|
||||
|
|
@ -3,12 +3,13 @@ import { Astal, Gtk, Widget } from "astal/gtk3";
|
|||
import fuzzysort from "fuzzysort";
|
||||
import type AstalApps from "gi://AstalApps";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
import { launcher as config } from "../config";
|
||||
import { launcher as config } from "../../config";
|
||||
import { Apps } from "../services/apps";
|
||||
import Math, { type HistoryItem } from "../services/math";
|
||||
import { getAppCategoryIcon } from "../utils/icons";
|
||||
import { launch } from "../utils/system";
|
||||
import { PopupWindow, setupCustomTooltip } from "../utils/widgets";
|
||||
import { setupCustomTooltip } from "../utils/widgets";
|
||||
import PopupWindow from "../widgets/popupwindow";
|
||||
|
||||
type Mode = "apps" | "files" | "math";
|
||||
|
||||
57
src/modules/notifications.tsx
Normal file
57
src/modules/notifications.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { Gtk } from "astal/gtk3";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import { PopupWindow, setupChildClickthrough } from "../utils/widgets";
|
||||
|
||||
const List = () => (
|
||||
<box
|
||||
vertical
|
||||
valign={Gtk.Align.START}
|
||||
className="list"
|
||||
setup={self => {
|
||||
const notifd = AstalNotifd.get_default();
|
||||
const map = new Map<number, NotifPopup>();
|
||||
self.hook(notifd, "notified", (self, id) => {
|
||||
const notification = notifd.get_notification(id);
|
||||
|
||||
const popup = (<NotifPopup notification={notification} />) as NotifPopup;
|
||||
popup.connect("destroy", () => map.get(notification.id) === popup && map.delete(notification.id));
|
||||
map.get(notification.id)?.destroyWithAnims();
|
||||
map.set(notification.id, popup);
|
||||
|
||||
self.add(
|
||||
<eventbox
|
||||
// Dismiss on middle click
|
||||
onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()}
|
||||
// Close on hover lost
|
||||
onHoverLost={() => popup.destroyWithAnims()}
|
||||
>
|
||||
{popup}
|
||||
</eventbox>
|
||||
);
|
||||
|
||||
// Limit number of popups
|
||||
if (config.maxPopups > 0 && self.children.length > config.maxPopups)
|
||||
map.values().next().value?.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 {
|
||||
constructor() {
|
||||
super({
|
||||
name: "notifications",
|
||||
child: (
|
||||
<box>
|
||||
<List />
|
||||
</box>
|
||||
),
|
||||
});
|
||||
|
||||
setupChildClickthrough(self);
|
||||
}
|
||||
}
|
||||
49
src/modules/notifpopups.tsx
Normal file
49
src/modules/notifpopups.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { Astal, Gtk } from "astal/gtk3";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import { notifpopups as config } from "../../config";
|
||||
import { setupChildClickthrough } from "../utils/widgets";
|
||||
import Notification from "../widgets/notification";
|
||||
|
||||
export default () => (
|
||||
<window
|
||||
namespace="caelestia-notifpopups"
|
||||
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT | Astal.WindowAnchor.BOTTOM}
|
||||
>
|
||||
<box
|
||||
vertical
|
||||
valign={Gtk.Align.START}
|
||||
className="notifpopups"
|
||||
setup={self => {
|
||||
const notifd = AstalNotifd.get_default();
|
||||
const map = new Map<number, Notification>();
|
||||
self.hook(notifd, "notified", (self, id) => {
|
||||
const notification = notifd.get_notification(id);
|
||||
|
||||
const popup = (<Notification popup notification={notification} />) as Notification;
|
||||
popup.connect("destroy", () => map.get(notification.id) === popup && map.delete(notification.id));
|
||||
map.get(notification.id)?.destroyWithAnims();
|
||||
map.set(notification.id, popup);
|
||||
|
||||
self.add(
|
||||
<eventbox
|
||||
// Dismiss on middle click
|
||||
onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()}
|
||||
// Close on hover lost
|
||||
onHoverLost={() => popup.destroyWithAnims()}
|
||||
>
|
||||
{popup}
|
||||
</eventbox>
|
||||
);
|
||||
|
||||
// Limit number of popups
|
||||
if (config.maxPopups > 0 && self.children.length > config.maxPopups)
|
||||
map.values().next().value?.destroyWithAnims();
|
||||
});
|
||||
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
|
||||
|
||||
// Change input region to child region so can click through empty space
|
||||
setupChildClickthrough(self);
|
||||
}}
|
||||
/>
|
||||
</window>
|
||||
);
|
||||
|
|
@ -5,9 +5,9 @@ import AstalWp from "gi://AstalWp";
|
|||
import Cairo from "gi://cairo";
|
||||
import Pango from "gi://Pango";
|
||||
import PangoCairo from "gi://PangoCairo";
|
||||
import { osds as config } from "../config";
|
||||
import { osds as config } from "../../config";
|
||||
import Monitors, { type Monitor } from "../services/monitors";
|
||||
import { PopupWindow } from "../utils/widgets";
|
||||
import PopupWindow from "../widgets/popupwindow";
|
||||
|
||||
const getStyle = (context: Gtk.StyleContext, prop: string) => context.get_property(prop, Gtk.StateFlags.NORMAL);
|
||||
const getNumStyle = (context: Gtk.StyleContext, prop: string) => getStyle(context, prop) as number;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { execAsync, GLib, GObject, property, readFileAsync, register, writeFileAsync } from "astal";
|
||||
import { updates as config } from "../config";
|
||||
import { updates as config } from "../../config";
|
||||
|
||||
interface Update {
|
||||
name: string;
|
||||
45
src/utils/widgets.ts
Normal file
45
src/utils/widgets.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { Binding } from "astal";
|
||||
import { Astal, Widget } from "astal/gtk3";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
|
||||
export const setupCustomTooltip = (self: any, text: string | Binding<string>) => {
|
||||
if (!text) return null;
|
||||
|
||||
const window = new Widget.Window({
|
||||
visible: false,
|
||||
namespace: "caelestia-tooltip",
|
||||
keymode: Astal.Keymode.NONE,
|
||||
exclusivity: Astal.Exclusivity.IGNORE,
|
||||
anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT,
|
||||
child: new Widget.Label({ className: "tooltip", label: text }),
|
||||
});
|
||||
self.set_tooltip_window(window);
|
||||
|
||||
let dirty = true;
|
||||
let lastX = 0;
|
||||
self.connect("size-allocate", () => (dirty = true));
|
||||
window.connect("size-allocate", () => {
|
||||
window.marginLeft = lastX + (self.get_allocated_width() - window.get_preferred_width()[1]) / 2;
|
||||
});
|
||||
if (text instanceof Binding) self.hook(text, (_: any, v: string) => !v && window.hide());
|
||||
|
||||
self.connect("query-tooltip", (_: any, x: number, y: number) => {
|
||||
if (text instanceof Binding && !text.get()) return false;
|
||||
if (dirty) {
|
||||
const { width, height } = self.get_allocation();
|
||||
const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
|
||||
window.marginLeft = cx + ((width - window.get_preferred_width()[1]) / 2 - x);
|
||||
window.marginTop = cy + (height - y);
|
||||
lastX = cx - x;
|
||||
dirty = false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
self.connect("destroy", () => window.destroy());
|
||||
|
||||
return window;
|
||||
};
|
||||
|
||||
export const setupChildClickthrough = (self: any) =>
|
||||
self.connect("size-allocate", () => self.get_window()?.set_child_input_shapes());
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
import { GLib, register, timeout } from "astal";
|
||||
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import { notifpopups as config } from "../config";
|
||||
import { notifpopups as config } from "../../config";
|
||||
import { desktopEntrySubs } from "../utils/icons";
|
||||
import { setupChildClickthrough } from "../utils/widgets";
|
||||
|
||||
const urgencyToString = (urgency: AstalNotifd.Urgency) => {
|
||||
switch (urgency) {
|
||||
|
|
@ -53,17 +52,17 @@ const Image = ({ icon }: { icon: string }) => {
|
|||
};
|
||||
|
||||
@register()
|
||||
class NotifPopup extends Widget.Box {
|
||||
export default class Notification extends Widget.Box {
|
||||
readonly #revealer;
|
||||
#destroyed = false;
|
||||
|
||||
constructor({ notification }: { notification: AstalNotifd.Notification }) {
|
||||
constructor({ notification, popup }: { notification: AstalNotifd.Notification; popup?: boolean }) {
|
||||
super();
|
||||
|
||||
this.#revealer = (
|
||||
<revealer revealChild transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN} transitionDuration={150}>
|
||||
<box className="wrapper">
|
||||
<box vertical className={`popup ${urgencyToString(notification.urgency)}`}>
|
||||
<box vertical className={`notification ${urgencyToString(notification.urgency)}`}>
|
||||
<box className="header">
|
||||
<AppIcon appIcon={notification.appIcon} desktopEntry={notification.appName} />
|
||||
<label className="app-name" label={notification.appName ?? "Unknown"} />
|
||||
|
|
@ -106,7 +105,7 @@ class NotifPopup extends Widget.Box {
|
|||
});
|
||||
|
||||
// Close popup after timeout if transient or expire enabled in config
|
||||
if (config.expire || notification.transient)
|
||||
if (popup && (config.expire || notification.transient))
|
||||
timeout(
|
||||
notification.expireTimeout > 0
|
||||
? notification.expireTimeout
|
||||
|
|
@ -131,47 +130,3 @@ class NotifPopup extends Widget.Box {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default () => (
|
||||
<window
|
||||
namespace="caelestia-notifpopups"
|
||||
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT | Astal.WindowAnchor.BOTTOM}
|
||||
>
|
||||
<box
|
||||
vertical
|
||||
valign={Gtk.Align.START}
|
||||
className="notifpopups"
|
||||
setup={self => {
|
||||
const notifd = AstalNotifd.get_default();
|
||||
const map = new Map<number, NotifPopup>();
|
||||
self.hook(notifd, "notified", (self, id) => {
|
||||
const notification = notifd.get_notification(id);
|
||||
|
||||
const popup = (<NotifPopup notification={notification} />) as NotifPopup;
|
||||
popup.connect("destroy", () => map.get(notification.id) === popup && map.delete(notification.id));
|
||||
map.get(notification.id)?.destroyWithAnims();
|
||||
map.set(notification.id, popup);
|
||||
|
||||
self.add(
|
||||
<eventbox
|
||||
// Dismiss on middle click
|
||||
onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()}
|
||||
// Close on hover lost
|
||||
onHoverLost={() => popup.destroyWithAnims()}
|
||||
>
|
||||
{popup}
|
||||
</eventbox>
|
||||
);
|
||||
|
||||
// Limit number of popups
|
||||
if (config.maxPopups > 0 && self.children.length > config.maxPopups)
|
||||
map.values().next().value?.destroyWithAnims();
|
||||
});
|
||||
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
|
||||
|
||||
// Change input region to child region so can click through empty space
|
||||
setupChildClickthrough(self);
|
||||
}}
|
||||
/>
|
||||
</window>
|
||||
);
|
||||
39
src/widgets/popupwindow.ts
Normal file
39
src/widgets/popupwindow.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { Binding, register } from "astal";
|
||||
import { App, Astal, Gdk, Widget } from "astal/gtk3";
|
||||
import AstalHyprland from "gi://AstalHyprland?version=0.1";
|
||||
|
||||
const extendProp = <T>(
|
||||
prop: T | Binding<T | undefined> | undefined,
|
||||
override: (prop: T | undefined) => T | undefined
|
||||
) => prop && (prop instanceof Binding ? prop.as(override) : override(prop));
|
||||
|
||||
@register()
|
||||
export default class PopupWindow extends Widget.Window {
|
||||
constructor(props: Widget.WindowProps) {
|
||||
super({
|
||||
keymode: Astal.Keymode.ON_DEMAND,
|
||||
exclusivity: Astal.Exclusivity.IGNORE,
|
||||
...props,
|
||||
visible: false,
|
||||
application: App,
|
||||
name: props.monitor ? extendProp(props.name, n => (n ? n + props.monitor : undefined)) : props.name,
|
||||
namespace: extendProp(props.name, n => `caelestia-${n}`),
|
||||
onKeyPressEvent: (self, event) => {
|
||||
// Close window on escape
|
||||
if (event.get_keyval()[1] === Gdk.KEY_Escape) self.hide();
|
||||
|
||||
return props.onKeyPressEvent?.(self, event);
|
||||
},
|
||||
borderWidth: 20, // To allow shadow, cause if not it gets cut off
|
||||
});
|
||||
}
|
||||
|
||||
popup_at_widget(widget: JSX.Element, event: Gdk.Event) {
|
||||
const { width, height } = widget.get_allocation();
|
||||
const [_, x, y] = event.get_coords();
|
||||
const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
|
||||
this.marginLeft = cx + ((width - this.get_preferred_width()[1]) / 2 - x);
|
||||
this.marginTop = cy + (height - y);
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
import { Binding, register } from "astal";
|
||||
import { App, Astal, Gdk, Widget } from "astal/gtk3";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
|
||||
export const setupCustomTooltip = (self: any, text: string | Binding<string>) => {
|
||||
if (!text) return null;
|
||||
|
||||
const window = (
|
||||
<window
|
||||
visible={false}
|
||||
namespace="caelestia-tooltip"
|
||||
keymode={Astal.Keymode.NONE}
|
||||
exclusivity={Astal.Exclusivity.IGNORE}
|
||||
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT}
|
||||
>
|
||||
<label className="tooltip" label={text} />
|
||||
</window>
|
||||
) as Widget.Window;
|
||||
self.set_tooltip_window(window);
|
||||
|
||||
let dirty = true;
|
||||
let lastX = 0;
|
||||
self.connect("size-allocate", () => (dirty = true));
|
||||
window.connect("size-allocate", () => {
|
||||
window.marginLeft = lastX + (self.get_allocated_width() - window.get_preferred_width()[1]) / 2;
|
||||
});
|
||||
if (text instanceof Binding) self.hook(text, (_: any, v: string) => !v && window.hide());
|
||||
|
||||
self.connect("query-tooltip", (_: any, x: number, y: number) => {
|
||||
if (text instanceof Binding && !text.get()) return false;
|
||||
if (dirty) {
|
||||
const { width, height } = self.get_allocation();
|
||||
const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
|
||||
window.marginLeft = cx + ((width - window.get_preferred_width()[1]) / 2 - x);
|
||||
window.marginTop = cy + (height - y);
|
||||
lastX = cx - x;
|
||||
dirty = false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
self.connect("destroy", () => window.destroy());
|
||||
|
||||
return window;
|
||||
};
|
||||
|
||||
export const setupChildClickthrough = (self: any) =>
|
||||
self.connect("size-allocate", () => self.get_window()?.set_child_input_shapes());
|
||||
|
||||
const extendProp = <T,>(
|
||||
prop: T | Binding<T | undefined> | undefined,
|
||||
override: (prop: T | undefined) => T | undefined
|
||||
) => prop && (prop instanceof Binding ? prop.as(override) : override(prop));
|
||||
|
||||
@register()
|
||||
export class PopupWindow extends Widget.Window {
|
||||
constructor(props: Widget.WindowProps) {
|
||||
super({
|
||||
keymode: Astal.Keymode.ON_DEMAND,
|
||||
exclusivity: Astal.Exclusivity.IGNORE,
|
||||
...props,
|
||||
visible: false,
|
||||
application: App,
|
||||
name: props.monitor ? extendProp(props.name, n => (n ? n + props.monitor : undefined)) : props.name,
|
||||
namespace: extendProp(props.name, n => `caelestia-${n}`),
|
||||
onKeyPressEvent: (self, event) => {
|
||||
// Close window on escape
|
||||
if (event.get_keyval()[1] === Gdk.KEY_Escape) self.hide();
|
||||
|
||||
return props.onKeyPressEvent?.(self, event);
|
||||
},
|
||||
borderWidth: 20, // To allow shadow, cause if not it gets cut off
|
||||
});
|
||||
}
|
||||
|
||||
popup_at_widget(widget: JSX.Element, event: Gdk.Event) {
|
||||
const { width, height } = widget.get_allocation();
|
||||
const [_, x, y] = event.get_coords();
|
||||
const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
|
||||
this.marginLeft = cx + ((width - this.get_preferred_width()[1]) / 2 - x);
|
||||
this.marginTop = cy + (height - y);
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue