diff options
| author | srdusr <trevorgray@srdusr.com> | 2024-06-13 13:11:05 +0200 |
|---|---|---|
| committer | srdusr <trevorgray@srdusr.com> | 2024-06-13 13:11:05 +0200 |
| commit | d0fbb19623e4fb6097e1ff3ee49c6a76a0928d0e (patch) | |
| tree | 937531ddf423d3935c6e20c8a9227e39ce782241 /.config/ags/widget/launcher | |
| parent | 4ccbe0270c25ecab492508b5b0209ae53b9c35bd (diff) | |
| download | dotfiles-d0fbb19623e4fb6097e1ff3ee49c6a76a0928d0e.tar.gz dotfiles-d0fbb19623e4fb6097e1ff3ee49c6a76a0928d0e.zip | |
Add ags
Diffstat (limited to '.config/ags/widget/launcher')
| -rw-r--r-- | .config/ags/widget/launcher/AppLauncher.ts | 130 | ||||
| -rw-r--r-- | .config/ags/widget/launcher/Launcher.ts | 139 | ||||
| -rw-r--r-- | .config/ags/widget/launcher/NixRun.ts | 118 | ||||
| -rw-r--r-- | .config/ags/widget/launcher/ShRun.ts | 89 | ||||
| -rw-r--r-- | .config/ags/widget/launcher/launcher.scss | 143 |
5 files changed, 619 insertions, 0 deletions
diff --git a/.config/ags/widget/launcher/AppLauncher.ts b/.config/ags/widget/launcher/AppLauncher.ts new file mode 100644 index 0000000..4d7ca73 --- /dev/null +++ b/.config/ags/widget/launcher/AppLauncher.ts @@ -0,0 +1,130 @@ +import { type Application } from "types/service/applications" +import { launchApp, icon } from "lib/utils" +import options from "options" +import icons from "lib/icons" + +const apps = await Service.import("applications") +const { query } = apps +const { iconSize } = options.launcher.apps + +const QuickAppButton = (app: Application) => Widget.Button({ + hexpand: true, + tooltip_text: app.name, + on_clicked: () => { + App.closeWindow("launcher") + launchApp(app) + }, + child: Widget.Icon({ + size: iconSize.bind(), + icon: icon(app.icon_name, icons.fallback.executable), + }), +}) + +const AppItem = (app: Application) => { + const title = Widget.Label({ + class_name: "title", + label: app.name, + hexpand: true, + xalign: 0, + vpack: "center", + truncate: "end", + }) + + const description = Widget.Label({ + class_name: "description", + label: app.description || "", + hexpand: true, + wrap: true, + max_width_chars: 30, + xalign: 0, + justification: "left", + vpack: "center", + }) + + const appicon = Widget.Icon({ + icon: icon(app.icon_name, icons.fallback.executable), + size: iconSize.bind(), + }) + + const textBox = Widget.Box({ + vertical: true, + vpack: "center", + children: app.description ? [title, description] : [title], + }) + + return Widget.Button({ + class_name: "app-item", + attribute: { app }, + child: Widget.Box({ + children: [appicon, textBox], + }), + on_clicked: () => { + App.closeWindow("launcher") + launchApp(app) + }, + }) +} +export function Favorites() { + const favs = options.launcher.apps.favorites.bind() + return Widget.Revealer({ + visible: favs.as(f => f.length > 0), + child: Widget.Box({ + vertical: true, + children: favs.as(favs => favs.flatMap(fs => [ + Widget.Separator(), + Widget.Box({ + class_name: "quicklaunch horizontal", + children: fs + .map(f => query(f)?.[0]) + .filter(f => f) + .map(QuickAppButton), + }), + ])), + }), + }) +} + +export function Launcher() { + const applist = Variable(query("")) + const max = options.launcher.apps.max + let first = applist.value[0] + + function SeparatedAppItem(app: Application) { + return Widget.Revealer( + { attribute: { app } }, + Widget.Box( + { vertical: true }, + Widget.Separator(), + AppItem(app), + ), + ) + } + + const list = Widget.Box({ + vertical: true, + children: applist.bind().as(list => list.map(SeparatedAppItem)), + setup: self => self + .hook(apps, () => applist.value = query(""), "notify::frequents"), + }) + + return Object.assign(list, { + filter(text: string | null) { + first = query(text || "")[0] + list.children.reduce((i, item) => { + if (!text || i >= max.value) { + item.reveal_child = false + return i + } + if (item.attribute.app.match(text)) { + item.reveal_child = true + return ++i + } + item.reveal_child = false + return i + }, 0) + }, + launchFirst() { + launchApp(first) + }, + }) +} diff --git a/.config/ags/widget/launcher/Launcher.ts b/.config/ags/widget/launcher/Launcher.ts new file mode 100644 index 0000000..3b73dc5 --- /dev/null +++ b/.config/ags/widget/launcher/Launcher.ts @@ -0,0 +1,139 @@ +import { type Binding } from "lib/utils" +import PopupWindow, { Padding } from "widget/PopupWindow" +import icons from "lib/icons" +import options from "options" +import nix from "service/nix" +import * as AppLauncher from "./AppLauncher" +import * as NixRun from "./NixRun" +import * as ShRun from "./ShRun" + +const { width, margin } = options.launcher +const isnix = nix.available + +function Launcher() { + const favs = AppLauncher.Favorites() + const applauncher = AppLauncher.Launcher() + const sh = ShRun.ShRun() + const shicon = ShRun.Icon() + const nix = NixRun.NixRun() + const nixload = NixRun.Spinner() + + function HelpButton(cmd: string, desc: string | Binding<string>) { + return Widget.Box( + { vertical: true }, + Widget.Separator(), + Widget.Button( + { + class_name: "help", + on_clicked: () => { + entry.grab_focus() + entry.text = `:${cmd} ` + entry.set_position(-1) + }, + }, + Widget.Box([ + Widget.Label({ + class_name: "name", + label: `:${cmd}`, + }), + Widget.Label({ + hexpand: true, + hpack: "end", + class_name: "description", + label: desc, + }), + ]), + ), + ) + } + + const help = Widget.Revealer({ + child: Widget.Box( + { vertical: true }, + HelpButton("sh", "run a binary"), + isnix ? HelpButton("nx", options.launcher.nix.pkgs.bind().as(pkg => + `run a nix package from ${pkg}`, + )) : Widget.Box(), + ), + }) + + const entry = Widget.Entry({ + hexpand: true, + primary_icon_name: icons.ui.search, + on_accept: ({ text }) => { + if (text?.startsWith(":nx")) + nix.run(text.substring(3)) + else if (text?.startsWith(":sh")) + sh.run(text.substring(3)) + else + applauncher.launchFirst() + + App.toggleWindow("launcher") + entry.text = "" + }, + on_change: ({ text }) => { + text ||= "" + favs.reveal_child = text === "" + help.reveal_child = text.split(" ").length === 1 && text?.startsWith(":") + + if (text?.startsWith(":nx")) + nix.filter(text.substring(3)) + else + nix.filter("") + + if (text?.startsWith(":sh")) + sh.filter(text.substring(3)) + else + sh.filter("") + + if (!text?.startsWith(":")) + applauncher.filter(text) + }, + }) + + function focus() { + entry.text = "" + entry.set_position(-1) + entry.select_region(0, -1) + entry.grab_focus() + favs.reveal_child = true + } + + const layout = Widget.Box({ + css: width.bind().as(v => `min-width: ${v}pt;`), + class_name: "launcher", + vertical: true, + vpack: "start", + setup: self => self.hook(App, (_, win, visible) => { + if (win !== "launcher") + return + + entry.text = "" + if (visible) + focus() + }), + children: [ + Widget.Box([entry, nixload, shicon]), + favs, + help, + applauncher, + //nix, + sh, + ], + }) + + return Widget.Box( + { vertical: true, css: "padding: 1px" }, + Padding("applauncher", { + css: margin.bind().as(v => `min-height: ${v}pt;`), + vexpand: false, + }), + layout, + ) +} + +export default () => PopupWindow({ + name: "launcher", + layout: "top", + child: Launcher(), +}) diff --git a/.config/ags/widget/launcher/NixRun.ts b/.config/ags/widget/launcher/NixRun.ts new file mode 100644 index 0000000..cec9e09 --- /dev/null +++ b/.config/ags/widget/launcher/NixRun.ts @@ -0,0 +1,118 @@ +import icons from "lib/icons" +import nix, { type Nixpkg } from "service/nix" + +const iconVisible = Variable(false) + +function Item(pkg: Nixpkg) { + const name = Widget.Label({ + class_name: "name", + label: pkg.name.split(".").at(-1), + }) + + const subpkg = pkg.name.includes(".") ? Widget.Label({ + class_name: "description", + hpack: "end", + hexpand: true, + label: ` ${pkg.name.split(".").slice(0, -1).join(".")}`, + }) : null + + const version = Widget.Label({ + class_name: "version", + label: pkg.version, + hexpand: true, + hpack: "end", + }) + + const description = pkg.description ? Widget.Label({ + class_name: "description", + label: pkg.description, + justification: "left", + wrap: true, + hpack: "start", + max_width_chars: 40, + }) : null + + return Widget.Box( + { + attribute: { name: pkg.name }, + vertical: true, + }, + Widget.Separator(), + Widget.Button( + { + class_name: "nix-item", + on_clicked: () => { + nix.run(pkg.name) + App.closeWindow("launcher") + }, + }, + Widget.Box( + { vertical: true }, + Widget.Box([name, version]), + Widget.Box([ + description as ReturnType<typeof Widget.Label>, + subpkg as ReturnType<typeof Widget.Label>, + ]), + ), + ), + ) +} + +export function Spinner() { + const icon = Widget.Icon({ + icon: icons.nix.nix, + class_name: "spinner", + css: ` + @keyframes spin { + to { -gtk-icon-transform: rotate(1turn); } + } + + image.spinning { + animation-name: spin; + animation-duration: 1s; + animation-timing-function: linear; + animation-iteration-count: infinite; + } + `, + setup: self => self.hook(nix, () => { + self.toggleClassName("spinning", !nix.ready) + }), + }) + + return Widget.Revealer({ + transition: "slide_left", + child: icon, + reveal_child: Utils.merge([ + nix.bind("ready"), + iconVisible.bind(), + ], (ready, show) => !ready || show), + }) +} + +export function NixRun() { + const list = Widget.Box<ReturnType<typeof Item>>({ + vertical: true, + }) + + const revealer = Widget.Revealer({ + child: list, + }) + + async function filter(term: string) { + iconVisible.value = Boolean(term) + + if (!term) + revealer.reveal_child = false + + if (term.trim()) { + const found = await nix.query(term) + list.children = found.map(k => Item(nix.db[k])) + revealer.reveal_child = true + } + } + + return Object.assign(revealer, { + filter, + run: nix.run, + }) +} diff --git a/.config/ags/widget/launcher/ShRun.ts b/.config/ags/widget/launcher/ShRun.ts new file mode 100644 index 0000000..c4215ef --- /dev/null +++ b/.config/ags/widget/launcher/ShRun.ts @@ -0,0 +1,89 @@ +import icons from "lib/icons" +import options from "options" +import { bash, dependencies } from "lib/utils" + +const iconVisible = Variable(false) + +const MAX = options.launcher.sh.max +const BINS = `${Utils.CACHE_DIR}/binaries` +bash("{ IFS=:; ls -H $PATH; } | sort ") + .then(bins => Utils.writeFile(bins, BINS)) + +async function query(filter: string) { + if (!dependencies("fzf")) + return [] as string[] + + return bash(`cat ${BINS} | fzf -f ${filter} | head -n ${MAX}`) + .then(str => Array.from(new Set(str.split("\n").filter(i => i)).values())) + .catch(err => { print(err); return [] }) +} + +function run(args: string) { + Utils.execAsync(args) + .then(out => { + print(`:sh ${args.trim()}:`) + print(out) + }) + .catch(err => { + Utils.notify("ShRun Error", err, icons.app.terminal) + }) +} + +function Item(bin: string) { + return Widget.Box( + { + attribute: { bin }, + vertical: true, + }, + Widget.Separator(), + Widget.Button({ + child: Widget.Label({ + label: bin, + hpack: "start", + }), + class_name: "sh-item", + on_clicked: () => { + Utils.execAsync(bin) + App.closeWindow("launcher") + }, + }), + ) +} + +export function Icon() { + const icon = Widget.Icon({ + icon: icons.app.terminal, + class_name: "spinner", + }) + + return Widget.Revealer({ + transition: "slide_left", + child: icon, + reveal_child: iconVisible.bind(), + }) +} + +export function ShRun() { + const list = Widget.Box<ReturnType<typeof Item>>({ + vertical: true, + }) + + const revealer = Widget.Revealer({ + child: list, + }) + + async function filter(term: string) { + iconVisible.value = Boolean(term) + + if (!term) + revealer.reveal_child = false + + if (term.trim()) { + const found = await query(term) + list.children = found.map(Item) + revealer.reveal_child = true + } + } + + return Object.assign(revealer, { filter, run }) +} diff --git a/.config/ags/widget/launcher/launcher.scss b/.config/ags/widget/launcher/launcher.scss new file mode 100644 index 0000000..926abc3 --- /dev/null +++ b/.config/ags/widget/launcher/launcher.scss @@ -0,0 +1,143 @@ +@use "sass:math"; +@use "sass:color"; + +window#launcher .launcher { + @include floating_widget; + + .quicklaunch { + @include spacing; + + button { + @include button($flat: true); + padding: $padding; + } + } + + entry { + @include button; + padding: $padding; + margin: $spacing; + + selection { + color: color.mix($fg, $bg, 50%); + background-color: transparent; + } + + label, + image { + color: $fg; + } + } + + image.spinner { + color: $primary-bg; + margin-right: $spacing; + } + + separator { + margin: 4pt 0; + background-color: $popover-border-color; + } + + button.app-item { + @include button($flat: true, $reactive: false); + + >box { + @include spacing(0.5); + } + + transition: $transition; + padding: $padding; + + label { + transition: $transition; + + &.title { + color: $fg; + } + + &.description { + color: transparentize($fg, 0.3); + } + } + + image { + transition: $transition; + } + + &:hover, + &:focus { + .title { + color: $primary-bg; + } + + .description { + color: transparentize($primary-bg, .4); + } + + image { + -gtk-icon-shadow: 2px 2px $primary-bg; + } + } + + &:active { + background-color: transparentize($primary-bg, 0.5); + border-radius: $radius; + box-shadow: inset 0 0 0 $border-width $border-color; + + .title { + color: $fg; + } + } + } + + button.help, + button.nix-item { + @include button($flat: true, $reactive: false); + padding: 0 ($padding * .5); + + label { + transition: $transition; + color: $fg; + } + + .name { + font-size: 1.2em; + font-weight: bold; + } + + .description { + color: transparentize($fg, .3) + } + + &:hover, + &:focus { + label { + text-shadow: $text-shadow; + } + + .name, + .version { + color: $primary-bg; + } + + .description { + color: transparentize($primary-bg, .3) + } + } + } + + button.sh-item { + @include button($flat: true, $reactive: false); + padding: 0 ($padding * .5); + + transition: $transition; + color: $fg; + + &:hover, + &:focus { + color: $primary-bg; + text-shadow: $text-shadow; + } + } +} |
