summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-14 00:22:59 +1100
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-14 00:22:59 +1100
commite923d39cdeed4b42e747afc01b3420d6d89af6e6 (patch)
treea6e1c4fd3af32d2c8bcaf9d4f49226ced4c9c8c0 /utils
parentless border (diff)
downloadcaelestia-shell-e923d39cdeed4b42e747afc01b3420d6d89af6e6.tar.gz
caelestia-shell-e923d39cdeed4b42e747afc01b3420d6d89af6e6.tar.bz2
caelestia-shell-e923d39cdeed4b42e747afc01b3420d6d89af6e6.zip
app launcher
Diffstat (limited to 'utils')
-rw-r--r--utils/icons.ts9
-rw-r--r--utils/system.ts11
-rw-r--r--utils/widgets.tsx113
3 files changed, 127 insertions, 6 deletions
diff --git a/utils/icons.ts b/utils/icons.ts
index dff47f3..f12aee0 100644
--- a/utils/icons.ts
+++ b/utils/icons.ts
@@ -1,4 +1,5 @@
import { Gio } from "astal";
+import type AstalApps from "gi://AstalApps";
import { Apps } from "../services/apps";
// Code points from https://www.github.com/lukas-w/font-logos
@@ -73,10 +74,12 @@ const categoryIcons: Record<string, string> = {
System: "host",
};
-export const getAppCategoryIcon = (name: string) => {
+export const getAppCategoryIcon = (nameOrApp: string | AstalApps.Application) => {
const categories =
- Gio.DesktopAppInfo.new(`${name}.desktop`)?.get_categories()?.split(";") ??
- Apps.fuzzy_query(name)[0]?.categories;
+ typeof nameOrApp === "string"
+ ? Gio.DesktopAppInfo.new(`${nameOrApp}.desktop`)?.get_categories()?.split(";") ??
+ Apps.fuzzy_query(nameOrApp)[0]?.categories
+ : nameOrApp.categories;
if (categories)
for (const [key, value] of Object.entries(categoryIcons)) if (categories.includes(key)) return value;
return "terminal";
diff --git a/utils/system.ts b/utils/system.ts
index 9a328d5..99e9d7c 100644
--- a/utils/system.ts
+++ b/utils/system.ts
@@ -1,4 +1,5 @@
-import { exec, GLib } from "astal";
+import { exec, execAsync, GLib } from "astal";
+import type AstalApps from "gi://AstalApps";
import { osIcons } from "./icons";
export const inPath = (bin: string) => {
@@ -10,6 +11,14 @@ export const inPath = (bin: string) => {
return true;
};
+export const launch = (app: AstalApps.Application) => {
+ execAsync(["uwsm", "app", "--", app.entry]).catch(() => {
+ app.frequency--; // Decrement frequency cause launch also increments it
+ app.launch();
+ });
+ app.frequency++;
+};
+
export const osId = GLib.get_os_info("ID") ?? "unknown";
export const osIdLike = GLib.get_os_info("ID_LIKE");
export const osIcon = String.fromCodePoint(
diff --git a/utils/widgets.tsx b/utils/widgets.tsx
index 7c40184..a0a96cb 100644
--- a/utils/widgets.tsx
+++ b/utils/widgets.tsx
@@ -1,5 +1,5 @@
-import { Binding } from "astal";
-import { Astal, type Widget } from "astal/gtk3";
+import { Binding, property, register, timeout } from "astal";
+import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import AstalHyprland from "gi://AstalHyprland";
export const setupCustomTooltip = (self: any, text: string | Binding<string>) => {
@@ -46,3 +46,112 @@ export const setupCustomTooltip = (self: any, text: string | Binding<string>) =>
export const setupChildClickthrough = (self: any) =>
self.connect("size-allocate", () => self.get_window()?.set_child_input_shapes());
+
+export enum TransitionType {
+ FADE = "",
+ SLIDE_DOWN = "margin-top: -${height}px; margin-bottom: ${height}px;",
+ SLIDE_UP = "margin-top: ${height}px; margin-bottom: -${height}px;",
+ SLIDE_RIGHT = "margin-left: -${width}px; margin-right: ${width}px;",
+ SLIDE_LEFT = "margin-left: ${width}px; margin-right: -${width}px;",
+}
+
+@register()
+export class PopupWindow extends Widget.Window {
+ readonly transitionType: TransitionType;
+ readonly transitionDuration: number;
+ readonly transitionAmount: number;
+
+ readonly #content: Widget.Box;
+ #visible: boolean = false;
+
+ @property(Boolean)
+ get realVisible() {
+ return this.#visible;
+ }
+
+ set realVisible(v: boolean) {
+ if (v) this.show();
+ else this.hide();
+ }
+
+ constructor(
+ props: Widget.WindowProps & {
+ transitionType?: TransitionType;
+ transitionDuration?: number;
+ transitionAmount?: number;
+ }
+ ) {
+ const {
+ onKeyPressEvent,
+ clickThrough,
+ child,
+ halign = Gtk.Align.START,
+ valign = Gtk.Align.START,
+ transitionType = TransitionType.FADE,
+ transitionDuration = 200,
+ transitionAmount = 0.2,
+ ...sProps
+ } = props;
+
+ sProps.visible = false;
+ sProps.application = App;
+ sProps.namespace = `caelestia-${props.name}`;
+ sProps.anchor =
+ Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.BOTTOM | Astal.WindowAnchor.RIGHT;
+ sProps.onKeyPressEvent = (self, event) => {
+ // Close window on escape
+ if (event.get_keyval()[1] === Gdk.KEY_Escape) self.hide();
+
+ return onKeyPressEvent?.(self, event);
+ };
+ super(sProps);
+
+ this.transitionType = transitionType;
+ this.transitionDuration = transitionDuration;
+ this.transitionAmount = transitionAmount;
+
+ // Wrapper box for animations
+ this.#content = (
+ <box halign={halign} valign={valign} className={`${props.name}-wrapper`}>
+ {clickThrough ? <eventbox>{child}</eventbox> : child}
+ </box>
+ ) as Widget.Box;
+ this.#content.css = this.#getTransitionCss(false);
+ this.add(this.#content);
+
+ if (clickThrough) setupChildClickthrough(this);
+ }
+
+ #getTransitionCss(visible: boolean) {
+ return (
+ `transition-duration: ${this.transitionDuration}ms;` +
+ (visible
+ ? "opacity: 1;" + this.transitionType.replaceAll("${width}", "0").replaceAll("${height}", "0")
+ : "opacity: 0;" +
+ this.transitionType
+ .replaceAll("${width}", String(this.#content.get_preferred_width()[1] * this.transitionAmount))
+ .replaceAll("${height}", String(this.#content.get_preferred_height()[1] * this.transitionAmount)))
+ );
+ }
+
+ show() {
+ this.#visible = true;
+ this.notify("real-visible");
+
+ super.show();
+ this.#content.css = this.#getTransitionCss(true);
+ }
+
+ hide() {
+ this.#visible = false;
+ this.notify("real-visible");
+
+ this.#content.css = this.#getTransitionCss(false);
+ timeout(this.transitionDuration, () => !this.#visible && super.hide());
+ }
+
+ toggle() {
+ if (this.#visible) this.hide();
+ else this.show();
+ }
+}