diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-01-16 16:35:37 +1100 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-01-16 16:35:37 +1100 |
| commit | 02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38 (patch) | |
| tree | 5e2a56becf6ba6961995e541ce9688224f704773 | |
| parent | popupwindow: switch to class (diff) | |
| download | caelestia-shell-02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38.tar.gz caelestia-shell-02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38.tar.bz2 caelestia-shell-02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38.zip | |
refactor: move ts to src
Also move popupwindow to own file
| -rw-r--r-- | app.tsx | 12 | ||||
| -rw-r--r-- | scss/notifpopups.scss | 2 | ||||
| -rw-r--r-- | src/modules/bar.tsx (renamed from modules/bar.tsx) | 2 | ||||
| -rw-r--r-- | src/modules/launcher.tsx (renamed from modules/launcher.tsx) | 5 | ||||
| -rw-r--r-- | src/modules/notifications.tsx | 57 | ||||
| -rw-r--r-- | src/modules/notifpopups.tsx | 49 | ||||
| -rw-r--r-- | src/modules/osds.tsx (renamed from modules/osds.tsx) | 4 | ||||
| -rw-r--r-- | src/services/apps.ts (renamed from services/apps.ts) | 0 | ||||
| -rw-r--r-- | src/services/math.ts (renamed from services/math.ts) | 0 | ||||
| -rw-r--r-- | src/services/monitors.ts (renamed from services/monitors.ts) | 0 | ||||
| -rw-r--r-- | src/services/players.ts (renamed from services/players.ts) | 0 | ||||
| -rw-r--r-- | src/services/updates.ts (renamed from services/updates.ts) | 2 | ||||
| -rw-r--r-- | src/utils/icons.ts (renamed from utils/icons.ts) | 0 | ||||
| -rw-r--r-- | src/utils/mpris.ts (renamed from utils/mpris.ts) | 0 | ||||
| -rw-r--r-- | src/utils/strings.ts (renamed from utils/strings.ts) | 0 | ||||
| -rw-r--r-- | src/utils/system.ts (renamed from utils/system.ts) | 0 | ||||
| -rw-r--r-- | src/utils/widgets.ts | 45 | ||||
| -rw-r--r-- | src/widgets/notification.tsx (renamed from modules/notifpopups.tsx) | 55 | ||||
| -rw-r--r-- | src/widgets/popupwindow.ts | 39 | ||||
| -rw-r--r-- | utils/widgets.tsx | 84 |
20 files changed, 209 insertions, 147 deletions
@@ -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)) diff --git a/scss/notifpopups.scss b/scss/notifpopups.scss index 325cb7b..25e52e1 100644 --- a/scss/notifpopups.scss +++ b/scss/notifpopups.scss @@ -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; diff --git a/modules/bar.tsx b/src/modules/bar.tsx index 5ed309e..b56d94a 100644 --- a/modules/bar.tsx +++ b/src/modules/bar.tsx @@ -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"; diff --git a/modules/launcher.tsx b/src/modules/launcher.tsx index 966cdfd..2fc6eef 100644 --- a/modules/launcher.tsx +++ b/src/modules/launcher.tsx @@ -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"; diff --git a/src/modules/notifications.tsx b/src/modules/notifications.tsx new file mode 100644 index 0000000..66188a1 --- /dev/null +++ b/src/modules/notifications.tsx @@ -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); + } +} diff --git a/src/modules/notifpopups.tsx b/src/modules/notifpopups.tsx new file mode 100644 index 0000000..5da3092 --- /dev/null +++ b/src/modules/notifpopups.tsx @@ -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> +); diff --git a/modules/osds.tsx b/src/modules/osds.tsx index b6e1333..a87fcc3 100644 --- a/modules/osds.tsx +++ b/src/modules/osds.tsx @@ -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; diff --git a/services/apps.ts b/src/services/apps.ts index 5396ac7..5396ac7 100644 --- a/services/apps.ts +++ b/src/services/apps.ts diff --git a/services/math.ts b/src/services/math.ts index c66798c..c66798c 100644 --- a/services/math.ts +++ b/src/services/math.ts diff --git a/services/monitors.ts b/src/services/monitors.ts index 78a0161..78a0161 100644 --- a/services/monitors.ts +++ b/src/services/monitors.ts diff --git a/services/players.ts b/src/services/players.ts index b81d4b5..b81d4b5 100644 --- a/services/players.ts +++ b/src/services/players.ts diff --git a/services/updates.ts b/src/services/updates.ts index 0b04e85..5bb6bd1 100644 --- a/services/updates.ts +++ b/src/services/updates.ts @@ -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; diff --git a/utils/icons.ts b/src/utils/icons.ts index f12aee0..f12aee0 100644 --- a/utils/icons.ts +++ b/src/utils/icons.ts diff --git a/utils/mpris.ts b/src/utils/mpris.ts index e0cc111..e0cc111 100644 --- a/utils/mpris.ts +++ b/src/utils/mpris.ts diff --git a/utils/strings.ts b/src/utils/strings.ts index e5bc43e..e5bc43e 100644 --- a/utils/strings.ts +++ b/src/utils/strings.ts diff --git a/utils/system.ts b/src/utils/system.ts index 5d77908..5d77908 100644 --- a/utils/system.ts +++ b/src/utils/system.ts diff --git a/src/utils/widgets.ts b/src/utils/widgets.ts new file mode 100644 index 0000000..08f9740 --- /dev/null +++ b/src/utils/widgets.ts @@ -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()); diff --git a/modules/notifpopups.tsx b/src/widgets/notification.tsx index c3441a9..0bef5ca 100644 --- a/modules/notifpopups.tsx +++ b/src/widgets/notification.tsx @@ -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> -); diff --git a/src/widgets/popupwindow.ts b/src/widgets/popupwindow.ts new file mode 100644 index 0000000..67aa0ff --- /dev/null +++ b/src/widgets/popupwindow.ts @@ -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(); + } +} diff --git a/utils/widgets.tsx b/utils/widgets.tsx deleted file mode 100644 index 7b16075..0000000 --- a/utils/widgets.tsx +++ /dev/null @@ -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(); - } -} |