From 5cf36b005814ee254271648691513d5361096649 Mon Sep 17 00:00:00 2001
From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>
Date: Fri, 28 Feb 2025 12:48:42 +1100
Subject: launcher: apps mode

---
 src/modules/launcher.tsx | 82 ++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 72 insertions(+), 10 deletions(-)

(limited to 'src/modules/launcher.tsx')

diff --git a/src/modules/launcher.tsx b/src/modules/launcher.tsx
index 356b6d4..b6af58e 100644
--- a/src/modules/launcher.tsx
+++ b/src/modules/launcher.tsx
@@ -1,6 +1,11 @@
+import { Apps as AppsService } from "@/services/apps";
+import { getAppCategoryIcon } from "@/utils/icons";
+import { launch } from "@/utils/system";
+import { FlowBox } from "@/utils/widgets";
 import PopupWindow from "@/widgets/popupwindow";
 import { bind, register, Variable } from "astal";
-import { Astal, Gtk, Widget } from "astal/gtk3";
+import { App, Astal, Gtk, Widget } from "astal/gtk3";
+import type AstalApps from "gi://AstalApps";
 
 type Mode = "apps" | "files" | "math" | "windows";
 
@@ -9,25 +14,77 @@ interface ModeContent {
     handleActivate(search: string): void;
 }
 
+const close = () => App.get_window("launcher")?.hide();
+
+const getModeIcon = (mode: Mode) => {
+    if (mode === "apps") return "apps";
+    if (mode === "files") return "folder";
+    if (mode === "math") return "calculate";
+    if (mode === "windows") return "select_window";
+    return "search";
+};
+
+const getPrettyMode = (mode: Mode) => {
+    if (mode === "apps") return "Apps";
+    if (mode === "files") return "Files";
+    if (mode === "math") return "Math";
+    if (mode === "windows") return "Windows";
+    return mode;
+};
+
+const AppResult = ({ app }: { app: AstalApps.Application }) => (
+    <Gtk.FlowBoxChild visible canFocus={false}>
+        <button
+            className="result"
+            cursor="pointer"
+            onClicked={() => {
+                launch(app);
+                close();
+            }}
+        >
+            <box>
+                {app.iconName && Astal.Icon.lookup_icon(app.iconName) ? (
+                    <icon className="icon" icon={app.iconName} />
+                ) : (
+                    <label className="icon" label={getAppCategoryIcon(app)} />
+                )}
+                <label truncate label={app.name} />
+            </box>
+        </button>
+    </Gtk.FlowBoxChild>
+);
+
 @register()
 class Apps extends Widget.Box implements ModeContent {
+    #content: FlowBox;
+
     constructor() {
-        super({ name: "apps" });
+        super({ name: "apps", className: "apps" });
+
+        this.#content = (<FlowBox homogeneous valign={Gtk.Align.START} maxChildrenPerLine={2} />) as FlowBox;
+
+        this.add(
+            <scrollable expand hscroll={Gtk.PolicyType.NEVER}>
+                {this.#content}
+            </scrollable>
+        );
     }
 
     updateContent(search: string): void {
-        throw new Error("Method not implemented.");
+        this.#content.foreach(c => c.destroy());
+        AppsService.fuzzy_query(search).forEach(app => this.#content.add(<AppResult app={app} />));
     }
 
-    handleActivate(search: string): void {
-        throw new Error("Method not implemented.");
+    handleActivate(): void {
+        this.#content.get_child_at_index(0)?.get_child()?.grab_focus();
+        this.#content.get_child_at_index(0)?.get_child()?.activate();
     }
 }
 
 @register()
 class Files extends Widget.Box implements ModeContent {
     constructor() {
-        super({ name: "files" });
+        super({ name: "files", className: "files" });
     }
 
     updateContent(search: string): void {
@@ -42,7 +99,7 @@ class Files extends Widget.Box implements ModeContent {
 @register()
 class Math extends Widget.Box implements ModeContent {
     constructor() {
-        super({ name: "math" });
+        super({ name: "math", className: "math" });
     }
 
     updateContent(search: string): void {
@@ -57,7 +114,7 @@ class Math extends Widget.Box implements ModeContent {
 @register()
 class Windows extends Widget.Box implements ModeContent {
     constructor() {
-        super({ name: "windows" });
+        super({ name: "windows", className: "windows" });
     }
 
     updateContent(search: string): void {
@@ -83,8 +140,12 @@ const ModeSwitcher = ({ mode, modes }: { mode: Variable<Mode>; modes: Mode[] })
                 className={bind(mode).as(c => `mode ${c === m ? "selected" : ""}`)}
                 cursor="pointer"
                 onClicked={() => mode.set(m)}
-                label={m}
-            />
+            >
+                <box halign={Gtk.Align.CENTER}>
+                    <label className="icon" label={getModeIcon(m)} />
+                    <label label={getPrettyMode(m)} />
+                </box>
+            </button>
         ))}
     </box>
 );
@@ -144,6 +205,7 @@ export default class Launcher extends PopupWindow {
 
         this.mode = mode;
 
+        content[mode.get()].updateContent(entry.get_text());
         this.hook(mode, (_, v: Mode) => {
             entry.set_text("");
             content[v].updateContent(entry.get_text());
-- 
cgit v1.2.3-freya

