83 lines
2.6 KiB
TypeScript
83 lines
2.6 KiB
TypeScript
import { generateModmaskMap, hideWindow, limit, skip } from "@lib/utils";
|
|
|
|
import { App, Astal, Gtk } from "astal/gtk4";
|
|
import { Variable, bind } from "astal";
|
|
|
|
import Hyprland from "gi://AstalHyprland";
|
|
|
|
const modmasks = generateModmaskMap();
|
|
|
|
// Incomplete Astal types?
|
|
type Bind = Hyprland.Bind & { description: string };
|
|
|
|
function ShortcutEntry({ entry }: { entry: Bind }) {
|
|
return <box cssClasses={["entry"]} spacing={4}>
|
|
{entry.modmask !== 0 && <label
|
|
cssClasses={["kbd"]}
|
|
label={modmasks[entry.modmask]?.toString()} />
|
|
}
|
|
<label cssClasses={["kbd"]} label={entry.key.toString()} />
|
|
<label label={entry.description} />
|
|
</box>
|
|
}
|
|
|
|
export default async function Shortcuts(_monitor_id: number) {
|
|
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor;
|
|
const hypr = Hyprland.get_default();
|
|
|
|
const shortcuts: Variable<Bind[]> = Variable([]);
|
|
const toSkip = Variable(0);
|
|
|
|
const list = Variable.derive([
|
|
bind(shortcuts),
|
|
bind(toSkip)
|
|
], (page, count) => limit(
|
|
skip(page, count),
|
|
10
|
|
));
|
|
|
|
const isEmpty = bind(list).as(list => list.length === 0);
|
|
const onScroll = (dy: number) => {
|
|
const value = toSkip.get();
|
|
|
|
if (dy < 0) {
|
|
if ((value - 10) < 0) return;
|
|
toSkip.set(value - 10)
|
|
} else {
|
|
if ((value + 10) > shortcuts.get().length) return;
|
|
toSkip.set(value + 10)
|
|
}
|
|
};
|
|
|
|
const setup = (self: Gtk.Window) => self.connect('notify::visible', async () => {
|
|
if (self.is_visible()) shortcuts.set(hypr.get_binds() as Bind[])
|
|
})
|
|
|
|
return <window
|
|
visible={false}
|
|
monitor={bind(hypr, "focusedMonitor").as(monitor => monitor.id)}
|
|
name={"shortcuts"}
|
|
cssClasses={["shortcuts"]}
|
|
keymode={Astal.Keymode.EXCLUSIVE}
|
|
anchor={TOP | BOTTOM | LEFT | RIGHT}
|
|
onKeyPressed={hideWindow}
|
|
application={App}
|
|
setup={setup}>
|
|
<box hexpand={false} cssClasses={["shortcuts"]} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
|
|
<box spacing={6} vertical onScroll={(_, __, dy) => onScroll(dy)}>
|
|
{bind(list).as(list => list.map(entry => (
|
|
<ShortcutEntry entry={entry} />
|
|
)))}
|
|
</box>
|
|
<box
|
|
halign={Gtk.Align.CENTER}
|
|
cssClasses={["not-found"]}
|
|
vertical
|
|
visible={isEmpty}>
|
|
<image iconName="system-search-symbolic" />
|
|
<label label="No match found" />
|
|
</box>
|
|
</box>
|
|
</window>
|
|
}
|