feat: Shortcuts widget
This commit is contained in:
parent
39d46c3311
commit
faab05ec8b
14
ags/app.ts
14
ags/app.ts
@ -6,9 +6,10 @@ import Hyprland from "gi://AstalHyprland";
|
||||
|
||||
import QuickSettings from "widget/quick_settings/quick_settings";
|
||||
import BluetoothWindow from "widget/quick_settings/bluetooth";
|
||||
import Shortcuts from "./widget/launcher/shortcuts";
|
||||
import Clipboard from "widget/launcher/clipboard";
|
||||
import Launcher from "widget/launcher/launcher";
|
||||
import BatteryInfo from "widget/bar/battery_info";
|
||||
import Launcher from "widget/launcher/launcher";
|
||||
import Bar from "widget/bar/bar";
|
||||
|
||||
const hypr = Hyprland.get_default();
|
||||
@ -19,15 +20,16 @@ const components = [
|
||||
QuickSettings,
|
||||
BluetoothWindow,
|
||||
BatteryInfo,
|
||||
Clipboard
|
||||
Clipboard,
|
||||
Shortcuts
|
||||
];
|
||||
|
||||
const setupBars = async (monitor_id: number) => {
|
||||
const windows = await Promise.all(
|
||||
components.map(item => Promise.resolve(item(monitor_id)) as Promise<Gtk.Window>)
|
||||
);
|
||||
const windows = await Promise.all(
|
||||
components.map(item => Promise.resolve(item(monitor_id)) as Promise<Gtk.Window>)
|
||||
);
|
||||
|
||||
return windows;
|
||||
return windows;
|
||||
};
|
||||
|
||||
const STYLES = GLib.get_user_config_dir() + "/ags/styles/style.scss";
|
||||
|
@ -5,6 +5,37 @@ export type Belongs<T, U> = {
|
||||
[K in keyof T]: T[K] extends U ? K : never;
|
||||
}[keyof T];
|
||||
|
||||
export const generateModmaskMap = () => {
|
||||
const MODIFIERS: Record<number, string> = {
|
||||
1: "SHIFT",
|
||||
4: "CTRL",
|
||||
8: "ALT",
|
||||
64: "SUPER"
|
||||
};
|
||||
|
||||
const modmaskMap: Record<number, string> = {};
|
||||
|
||||
const modifierValues = Object.keys(MODIFIERS).map(Number);
|
||||
const totalCombinations = 1 << modifierValues.length;
|
||||
|
||||
for (let i = 0; i < totalCombinations; i++) {
|
||||
let modmask = 0;
|
||||
const labels: string[] = [];
|
||||
|
||||
modifierValues.forEach((value, index) => {
|
||||
if (i & (1 << index)) {
|
||||
modmask |= value;
|
||||
labels.push(MODIFIERS[value]);
|
||||
}
|
||||
});
|
||||
|
||||
modmaskMap[modmask] = labels.reverse().join(" + ");
|
||||
}
|
||||
|
||||
return modmaskMap;
|
||||
};
|
||||
|
||||
|
||||
export const hideWindow = (self: Gtk.Window, keyval: number) => {
|
||||
const keys = ["quick_settings"].includes(self.name)
|
||||
? [Gdk.KEY_Escape]
|
||||
|
@ -1,5 +1,6 @@
|
||||
@forward '_bar';
|
||||
@forward '_launcher';
|
||||
@forward '_clipboard';
|
||||
@forward '_shortcuts';
|
||||
@forward '_battery_info';
|
||||
@forward '_quick_settings';
|
52
ags/styles/components/_shortcuts.scss
Normal file
52
ags/styles/components/_shortcuts.scss
Normal file
@ -0,0 +1,52 @@
|
||||
@use '../theme_colors.scss' as *;
|
||||
|
||||
.shortcuts {
|
||||
.entry {
|
||||
padding: 10px;
|
||||
background: $base0B;
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: $base00;
|
||||
|
||||
>label {
|
||||
color: $base0B;
|
||||
}
|
||||
|
||||
>.kbd {
|
||||
background: $base0B;
|
||||
color: $base00;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
color: $base00;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.kbd {
|
||||
padding: 2px 6px;
|
||||
font-size: 0.9em;
|
||||
font-family: monospace;
|
||||
border-radius: 4px;
|
||||
margin: 0 2px;
|
||||
|
||||
background: $base00;
|
||||
color: $base0B;
|
||||
}
|
||||
}
|
||||
|
||||
.not-found {
|
||||
background: $base0B;
|
||||
color: $base00;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
image {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
@ -69,7 +69,7 @@ export default async function Clipboard(_monitor_id: number) {
|
||||
};
|
||||
|
||||
const setup = (self: Gtk.Window) => self.connect('notify::visible', async () => {
|
||||
await getClipboardHistory(history)
|
||||
if (self.is_visible()) await getClipboardHistory(history)
|
||||
})
|
||||
|
||||
return <window
|
||||
|
82
ags/widget/launcher/shortcuts.tsx
Normal file
82
ags/widget/launcher/shortcuts.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
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>
|
||||
}
|
@ -58,6 +58,7 @@ bindd = , PRINT, Take partial screenshot, exec, hyprshot
|
||||
bindd = $mainMod, PRINT, Take fullscreen screenshot, exec, hyprshot -zm output -o $screenshot_dir
|
||||
|
||||
bindd = $mainMod, Space, Open app launcher, exec, ags toggle launcher
|
||||
bindd = $mainMod, Slash, Open cheatsheet, exec, ags toggle shortcuts
|
||||
bindd = $mainMod, V, Open clipboard history, exec, ags toggle clipboard
|
||||
bindd = $mainMod + Shift, T, Toggle bar, exec, ~/.config/hypr/scripts/toggle-bar-visibility.sh
|
||||
bindd = $mainMod, SUPER_L, Open quick settings, exec, ags toggle quick_settings
|
||||
|
@ -5,8 +5,8 @@ general {
|
||||
}
|
||||
|
||||
background {
|
||||
blur_passes = 2
|
||||
contrast = 0.8
|
||||
path = screenshot
|
||||
blur_passes = 2
|
||||
brightness = 0.4
|
||||
}
|
||||
|
||||
|
@ -1 +1 @@
|
||||
/nix/store/89nadapq2lq48hfm9v2a1zzd759n5pw4-home-manager-files/.config/hypr/hyprpaper.conf
|
||||
/nix/store/84hb9j3a4klrcx7fcd9m2989dbk6ha1l-home-manager-files/.config/hypr/hyprpaper.conf
|
Loading…
x
Reference in New Issue
Block a user