diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-01-24 21:21:26 +1100 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-01-24 21:21:26 +1100 |
| commit | ba4a879cc7c44187c429cea629e05b106ca75802 (patch) | |
| tree | c8afa1cb1c30f20af611363bb016057a13fa57cc /src | |
| parent | launcher: windows mode (diff) | |
| download | caelestia-shell-ba4a879cc7c44187c429cea629e05b106ca75802.tar.gz caelestia-shell-ba4a879cc7c44187c429cea629e05b106ca75802.tar.bz2 caelestia-shell-ba4a879cc7c44187c429cea629e05b106ca75802.zip | |
launcher: use scrollable
Diffstat (limited to 'src')
| -rw-r--r-- | src/modules/launcher.tsx | 123 | ||||
| -rw-r--r-- | src/services/math.ts | 3 |
2 files changed, 86 insertions, 40 deletions
diff --git a/src/modules/launcher.tsx b/src/modules/launcher.tsx index b588e3a..115137d 100644 --- a/src/modules/launcher.tsx +++ b/src/modules/launcher.tsx @@ -47,6 +47,9 @@ const getEmptyTextFromMode = (mode: Mode) => { } }; +const limitLength = <T,>(arr: T[], cfg: { maxResults: number }) => + cfg.maxResults > 0 && arr.length > cfg.maxResults ? arr.slice(0, cfg.maxResults) : arr; + const close = (self: JSX.Element) => { const toplevel = self.get_toplevel(); if (toplevel instanceof Widget.Window) toplevel.hide(); @@ -114,7 +117,7 @@ const PinnedApp = (names: string[]) => { return widget; }; -const PinnedApps = () => <box homogeneous>{config.pins.map(PinnedApp)}</box>; +const PinnedApps = () => <box homogeneous>{config.apps.pins.map(PinnedApp)}</box>; const SearchEntry = ({ entry }: { entry: Widget.Entry }) => ( <stack @@ -141,6 +144,7 @@ const Result = ({ tooltip, onClicked, onSecondaryClick, + onMiddleClick, onDestroy, }: { icon?: string | Gio.Icon | null; @@ -150,6 +154,7 @@ const Result = ({ tooltip?: string; onClicked: (self: Widget.Button) => void; onSecondaryClick?: (self: Widget.Button) => void; + onMiddleClick?: (self: Widget.Button) => void; onDestroy?: () => void; }) => ( <button @@ -157,7 +162,10 @@ const Result = ({ cursor="pointer" tooltipText={tooltip} onClicked={onClicked} - onClick={(self, event) => event.button === Astal.MouseButton.SECONDARY && onSecondaryClick?.(self)} + onClick={(self, event) => { + if (event.button === Astal.MouseButton.SECONDARY) onSecondaryClick?.(self); + else if (event.button === Astal.MouseButton.MIDDLE) onMiddleClick?.(self); + }} onDestroy={onDestroy} > <box> @@ -167,14 +175,16 @@ const Result = ({ ) : ( <icon valign={Gtk.Align.START} className="icon" gicon={icon} /> ))} - {!icon && materialIcon && <label valign={Gtk.Align.START} className="icon" label={materialIcon} />} + {materialIcon && (!icon || (typeof icon === "string" && !Astal.Icon.lookup_icon(icon))) && ( + <label valign={Gtk.Align.START} className="icon" label={materialIcon} /> + )} {sublabel ? ( <box vertical valign={Gtk.Align.CENTER} className="has-sublabel"> <label hexpand truncate maxWidthChars={1} xalign={0} label={label} /> <label hexpand truncate maxWidthChars={1} className="sublabel" xalign={0} label={sublabel} /> </box> ) : ( - <label xalign={0} label={label} /> + <label hexpand truncate maxWidthChars={1} xalign={0} label={label} /> )} </box> </button> @@ -388,18 +398,43 @@ const WindowResult = ({ client, reload }: { client: Client; reload: () => void } }) ); + const classOrTitle = (prop: "Class" | "Title", header = true) => { + const lower = prop.toLowerCase() as "class" | "title"; + return ( + (header ? `${prop}: ` : "") + + (client[lower] || (client[`initial${prop}`] ? `${client[`initial${prop}`]} (initial)` : `No ${lower}`)) + ); + }; + const workspace = (header = false) => + (header ? "Workspace: " : "") + `${client.workspace.name} (${client.workspace.id})`; + const prop = (prop: keyof typeof client, header?: string) => + `${header ?? prop.slice(0, 1).toUpperCase() + prop.slice(1)}: ${client[prop]}`; + const result = ( <Result icon={app.iconName} materialIcon={getAppCategoryIcon(app)} - label={client.title || (client.initialTitle ? `${client.initialTitle} (initial)` : "No title")} - sublabel={client.class || (client.initialClass ? `${client.initialClass} (initial)` : "No class")} - tooltip={`Address: ${client.address}\nWorkspace: ${client.workspace.name} (${client.workspace.id})\nProcess ID: ${client.pid}\nFloating: ${client.floating}\nInhibiting idle: ${client.inhibitingIdle}`} + label={ + classOrTitle("Title", false).length < 5 + ? `${classOrTitle("Class", false)}: ${classOrTitle("Title", false)}` + : classOrTitle("Title", false) + } + sublabel={`Workspace ${workspace()} on ${hyprland.get_monitor(client.monitor).name}`} + tooltip={`${classOrTitle("Title")}\n${classOrTitle("Class")}\n${prop("address")}\n${workspace( + true + )}\n${prop("pid", "Process ID")}\n${prop("floating")}\n${prop("inhibitingIdle", "Inhibiting idle")}`} onClicked={self => { close(self); astalClient?.focus(); }} onSecondaryClick={() => menu.popup_at_pointer(null)} + onMiddleClick={() => { + astalClient?.kill(); + const id = hyprland.connect("client-removed", () => { + hyprland.disconnect(id); + reload(); + }); + }} onDestroy={() => menu.destroy()} /> ); @@ -409,24 +444,10 @@ const WindowResult = ({ client, reload }: { client: Client; reload: () => void } const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) => { const empty = Variable(true); - return ( - <stack - className="results" - transitionType={Gtk.StackTransitionType.CROSSFADE} - transitionDuration={150} - shown={bind(empty).as(t => (t ? "empty" : "list"))} - > - <box name="empty" className="empty" halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}> - <label className="icon" label="bug_report" /> - <label - label={bind(entry, "text").as(t => - t.startsWith(">") ? "No matching subcommands" : getEmptyTextFromMode(mode.get()) - )} - /> - </box> + const scrollable = ( + <scrollable name="list" hscroll={Gtk.PolicyType.NEVER}> <box vertical - name="list" setup={self => { const subcommands: Record<string, Subcommand> = { apps: { @@ -470,11 +491,16 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) }; const subcommandList = Object.keys(subcommands); - const updateEmpty = () => empty.set(self.get_children().length === 0); + const afterUpdate = () => { + empty.set(self.get_children().length === 0); + + const children = limitLength(self.get_children(), config); + const height = children.reduce((a, b) => a + b.get_preferred_height()[1], 0); + scrollable.css = `min-height: ${height}px;`; + }; const appSearch = () => { - const apps = Apps.fuzzy_query(entry.text); - if (apps.length > config.maxResults) apps.length = config.maxResults; + const apps = limitLength(Apps.fuzzy_query(entry.text), config.apps); for (const app of apps) self.add(<AppResult app={app} />); }; @@ -485,23 +511,23 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) ); self.add(<box className="separator" />); } - for (const item of MathService.get_default().history) + for (const item of limitLength(MathService.get_default().history, config.math)) self.add(<MathResult isHistory math={item} entry={entry} />); }; const fileSearch = () => - execAsync(["fd", ...config.fdOpts, entry.text, HOME]) + execAsync(["fd", ...config.files.fdOpts, entry.text, HOME]) .then(out => { const paths = out.split("\n").filter(path => path); - if (paths.length > config.maxResults) paths.length = config.maxResults; self.foreach(ch => ch.destroy()); - for (const path of paths) self.add(<FileResult path={path} />); + for (const path of limitLength(paths, config.files)) + self.add(<FileResult path={path} />); }) .catch(e => { // Ignore execAsync error if (!(e instanceof Gio.IOErrorEnum || e instanceof GLib.SpawnError)) console.error(e); }) - .finally(updateEmpty); + .finally(afterUpdate); const listWindows = () => { const hyprland = AstalHyprland.get_default(); @@ -512,13 +538,13 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) if (entry.text) { const clients = fuzzysort.go(entry.text, unsortedClients, { all: true, - limit: config.maxResults, + limit: config.windows.maxResults, keys: ["title", "class", "initialTitle", "initialClass"], scoreFn: r => - r[0].score * config.windows.title + - r[1].score * config.windows.class + - r[2].score * config.windows.initialTitle + - r[3].score * config.windows.initialClass, + r[0].score * config.windows.weights.title + + r[1].score * config.windows.weights.class + + r[2].score * config.windows.weights.initialTitle + + r[3].score * config.windows.weights.initialClass, }); self.foreach(ch => ch.destroy()); for (const { obj } of clients) @@ -526,13 +552,13 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) } else { const clients = unsortedClients.sort((a, b) => a.focusHistoryID - b.focusHistoryID); self.foreach(ch => ch.destroy()); - for (const client of clients) + for (const client of limitLength(clients, config.windows)) self.add(<WindowResult reload={listWindows} client={client} />); } } catch (e) { console.error(e); } finally { - updateEmpty(); + afterUpdate(); } }); }; @@ -572,10 +598,29 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) else if (mode.get() === "files") fileSearch(); else if (mode.get() === "windows") listWindows(); - if (ignoreFileAsync) updateEmpty(); + if (ignoreFileAsync) afterUpdate(); }); }} /> + </scrollable> + ) as Widget.Scrollable; + + return ( + <stack + className="results" + transitionType={Gtk.StackTransitionType.CROSSFADE} + transitionDuration={150} + shown={bind(empty).as(t => (t ? "empty" : "list"))} + > + <box name="empty" className="empty" halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}> + <label className="icon" label="bug_report" /> + <label + label={bind(entry, "text").as(t => + t.startsWith(">") ? "No matching subcommands" : getEmptyTextFromMode(mode.get()) + )} + /> + </box> + {scrollable} </stack> ); }; diff --git a/src/services/math.ts b/src/services/math.ts index c66798c..14e90e0 100644 --- a/src/services/math.ts +++ b/src/services/math.ts @@ -1,5 +1,6 @@ import { GLib, GObject, property, readFile, register, writeFileAsync } from "astal"; import { derivative, evaluate, rationalize, simplify } from "mathjs/number"; +import { math as config } from "../../config"; export interface HistoryItem { equation: string; @@ -16,7 +17,7 @@ export default class Math extends GObject.Object { return this.instance; } - readonly #maxHistory = 20; + readonly #maxHistory = config.maxHistory; readonly #path = `${CACHE}/math-history.json`; readonly #history: HistoryItem[] = []; |