// ── violet-chaton v2 — Launcher widget ────────────────────────────────────── // App launcher avec recherche fuzzy const applications = Service.import("applications"); const AppItem = (app) => Widget.Button({ className: "app-item", onClicked: () => { app.launch(); App.closeWindow("launcher"); }, child: Widget.Box({ children: [ Widget.Icon({ icon: app.iconName || "application-x-executable", size: 24 }), Widget.Box({ vertical: true, children: [ Widget.Label({ label: app.name, xalign: 0, truncate: "end", }), Widget.Label({ label: app.description || "", xalign: 0, truncate: "end", className: "description", css: "color: #716686; font-size: 11px; font-weight: normal;", }), ], }), ], }), }); export const Launcher = (monitor) => { let apps = applications.list; const list = Widget.Box({ vertical: true, spacing: 2, }); const entry = Widget.Entry({ className: "search", placeholderText: "Rechercher...", onAccept: () => { const first = apps[0]; if (first) { first.launch(); App.closeWindow("launcher"); } }, onChange: ({ text }) => { apps = applications.query(text || ""); list.children = apps.slice(0, 12).map(AppItem); if (apps.length === 0) { list.children = [Widget.Label({ className: "no-results", label: "Aucun resultat", })]; } }, }); return Widget.Window({ name: "launcher", monitor, anchor: ["top"], layer: "overlay", visible: false, keymode: "exclusive", setup: (self) => { self.keybind("Escape", () => App.closeWindow("launcher")); self.hook(App, (_, name, visible) => { if (name === "launcher" && visible) { entry.text = ""; apps = applications.list; list.children = apps.slice(0, 12).map(AppItem); entry.grab_focus(); } }); }, child: Widget.Box({ className: "launcher", vertical: true, children: [ Widget.Scrollable({ hscroll: "never", vscroll: "automatic", css: "min-height: 400px;", child: list, }), entry, ], }), }); };