ags/widget/launcher/launcher.tsx
2025-02-06 12:05:55 +01:00

83 lines
2.5 KiB
TypeScript

import { Variable, bind } from "astal";
import { App, Astal, Gtk } from "astal/gtk4";
import { hideWindow } from "@lib/utils";
import Hyprland from "gi://AstalHyprland";
import Apps from "gi://AstalApps";
const hide = () => App.get_window("launcher")?.hide();
function AppButton({ app }: { app: Apps.Application }) {
return <button
cssClasses={["application"]}
onClicked={() => {
hide();
app.launch();
}}
>
<box>
<image iconName={app.iconName} />
<box valign={Gtk.Align.CENTER} vertical>
<label
cssClasses={["name"]}
xalign={0}
label={app.name}
/>
{app.description && <label
cssClasses={["description"]}
wrap
xalign={0}
label={app.description}
/>}
</box>
</box>
</button>
}
export default function Launcher(_monitor_id: number) {
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor;
const hypr = Hyprland.get_default();
const apps = new Apps.Apps();
const query = Variable("")
const list = query(text => apps.fuzzy_query(text).slice(0, 10))
const onEnter = () => {
hide()
list.get().at(0)!.launch()
}
return <window
visible={false}
monitor={bind(hypr, "focusedMonitor").as(monitor => monitor.id)}
name={"launcher"}
cssClasses={["launcher"]}
keymode={Astal.Keymode.EXCLUSIVE}
anchor={TOP | BOTTOM | LEFT | RIGHT}
onKeyPressed={hideWindow}
application={App}>
<box hexpand={false} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
<box cssClasses={["launcher"]} vertical>
<entry
placeholderText="Search"
onChanged={self => query.set(self.text)}
onActivate={onEnter}
/>
<box spacing={6} vertical>
{list.as(list => list.map(app => (
<AppButton app={app} />
)))}
</box>
<box
halign={Gtk.Align.CENTER}
cssClasses={["not-found"]}
vertical
visible={list.as(l => l.length === 0)}>
<image iconName="system-search-symbolic" />
<label label="No match found" />
</box>
</box>
</box>
</window>
}