feat(ags): update to official agsV2

This commit is contained in:
matt1432 2024-11-13 19:39:01 -05:00
parent 5d27b3d975
commit f3e06554e4
105 changed files with 245 additions and 254 deletions

View file

@ -0,0 +1,29 @@
@use '../../style/colors';
.sorted-list {
.search {
icon {
font-size: 20px;
min-width: 40px;
min-height: 40px
}
// entry {}
}
.list {
row {
border-radius: 10px;
&:hover, &:selected {
icon {
-gtk-icon-shadow: 2px 2px colors.$accent_color;
}
}
}
.placeholder {
font-size: 20px;
}
}
}

View file

@ -0,0 +1,111 @@
import { App, Astal, Gtk, Widget } from 'astal/gtk3';
import { property, register } from 'astal/gobject';
import { Binding, idle } from 'astal';
import { get_hyprland_monitor, hyprMessage } from '../../lib';
/* Types */
type CloseType = 'none' | 'stay' | 'released' | 'clicked';
type HyprTransition = 'slide' | 'slide top' | 'slide bottom' | 'slide left' |
'slide right' | 'popin' | 'fade';
type PopupCallback = (self?: Widget.Window) => void;
export type PopupWindowProps = Widget.WindowProps & {
transition?: HyprTransition | Binding<HyprTransition>
close_on_unfocus?: CloseType | Binding<CloseType>
on_open?: PopupCallback
on_close?: PopupCallback
};
@register()
export class PopupWindow extends Widget.Window {
@property(String)
declare transition: HyprTransition | Binding<HyprTransition>;
@property(String)
declare close_on_unfocus: CloseType | Binding<CloseType>;
on_open: PopupCallback;
on_close: PopupCallback;
constructor({
transition = 'slide top',
close_on_unfocus = 'released',
on_open = () => { /**/ },
on_close = () => { /**/ },
name,
visible = false,
layer = Astal.Layer.OVERLAY,
...rest
}: PopupWindowProps) {
super({
...rest,
name: `win-${name}`,
namespace: `win-${name}`,
visible: false,
layer,
setup: () => idle(() => {
// Add way to make window open on startup
if (visible) {
this.visible = true;
}
}),
});
App.add_window(this);
const setTransition = (_: PopupWindow, t: HyprTransition | Binding<HyprTransition>) => {
hyprMessage(`keyword layerrule animation ${t}, ${this.name}`).catch(console.log);
};
this.connect('notify::transition', setTransition);
this.close_on_unfocus = close_on_unfocus;
this.transition = transition;
this.on_open = on_open;
this.on_close = on_close;
this.connect('notify::visible', () => {
// Make sure we have the right animation
setTransition(this, this.transition);
if (this.visible) {
this.on_open(this);
}
else {
this.on_close(this);
}
});
};
async set_x_pos(
alloc: Gtk.Allocation,
side = 'right' as 'left' | 'right',
) {
const monitor = this.gdkmonitor ??
this.get_display().get_monitor_at_point(alloc.x, alloc.y);
const transform = get_hyprland_monitor(monitor)?.transform;
let width: number;
if (transform && (transform === 1 || transform === 3)) {
width = monitor.get_geometry().height;
}
else {
width = monitor.get_geometry().width;
}
this.margin_right = side === 'right' ?
(width - alloc.x - alloc.width) :
this.margin_right;
this.margin_left = side === 'right' ?
this.margin_left :
(alloc.x - alloc.width);
}
}
export default PopupWindow;

View file

@ -0,0 +1,14 @@
import { Widget } from 'astal/gtk3';
export default ({
size,
vertical = false,
css = '',
...rest
}: { size: number } & Widget.BoxProps) => (
<box
css={`${vertical ? 'min-height' : 'min-width'}: ${size}px; ${css}`}
{...rest}
/>
);

View file

@ -0,0 +1,61 @@
import { bind } from 'astal';
import { Gtk, Widget } from 'astal/gtk3';
import { register, property } from 'astal/gobject';
type SmoothProgressProps = Widget.BoxProps & {
transition_duration?: string
};
// PERF: this is kinda laggy
@register()
class SmoothProgress extends Widget.Box {
@property(Number)
declare fraction: number;
@property(String)
declare transition_duration: string;
constructor({
transition_duration = '1s',
...rest
}: SmoothProgressProps = {}) {
super(rest);
this.transition_duration = transition_duration;
const background = (
<box
className="background"
hexpand
vexpand
halign={Gtk.Align.FILL}
valign={Gtk.Align.FILL}
/>
);
const progress = (
<box
className="progress"
vexpand
valign={Gtk.Align.FILL}
css={bind(this, 'fraction').as((fraction) => {
return `
transition: margin-right ${this.transition_duration} linear;
margin-right: ${
Math.abs(fraction - 1) * background.get_allocated_width()
}px;
`;
})}
/>
);
this.add((
<overlay overlay={progress}>
{background}
</overlay>
));
this.show_all();
}
}
export default SmoothProgress;

View file

@ -0,0 +1,198 @@
// This is definitely not good practice but I couldn't figure out how to extend PopupWindow
// so here we are with a cursed function that returns a prop of the class.
import { Astal, Gtk, Widget } from 'astal/gtk3';
import { idle } from 'astal';
import { AsyncFzf, AsyncFzfOptions, FzfResultItem } from 'fzf';
import PopupWindow from '../misc/popup-window';
import { centerCursor } from '../../lib';
export interface SortedListProps<T> {
create_list: () => T[] | Promise<T[]>
create_row: (item: T) => Gtk.Widget
fzf_options?: AsyncFzfOptions<T>
unique_props?: (keyof T)[]
on_row_activated: (row: Gtk.ListBoxRow) => void
sort_func: (
a: Gtk.ListBoxRow,
b: Gtk.ListBoxRow,
entry: Widget.Entry,
fzf: FzfResultItem<T>[],
) => number
name: string
};
export class SortedList<T> {
private item_list: T[] = [];
private fzf_results: FzfResultItem<T>[] = [];
readonly window: PopupWindow;
private _item_map = new Map<T, Gtk.Widget>();
readonly create_list: () => T[] | Promise<T[]>;
readonly create_row: (item: T) => Gtk.Widget;
readonly fzf_options: AsyncFzfOptions<T>;
readonly unique_props: (keyof T)[] | undefined;
readonly on_row_activated: (row: Gtk.ListBoxRow) => void;
readonly sort_func: (
a: Gtk.ListBoxRow,
b: Gtk.ListBoxRow,
entry: Widget.Entry,
fzf: FzfResultItem<T>[],
) => number;
constructor({
create_list,
create_row,
fzf_options = {} as AsyncFzfOptions<T>,
unique_props,
on_row_activated,
sort_func,
name,
}: SortedListProps<T>) {
const list = new Gtk.ListBox({
selectionMode: Gtk.SelectionMode.SINGLE,
});
list.connect('row-activated', (_, row) => {
this.on_row_activated(row);
});
const placeholder = (
<revealer>
<label
label=" Couldn't find a match"
className="placeholder"
/>
</revealer>
) as Widget.Revealer;
const on_text_change = (text: string) => {
// @ts-expect-error this works
(new AsyncFzf<T[]>(this.item_list, this.fzf_options)).find(text)
.then((out) => {
this.fzf_results = out;
list.invalidate_sort();
const visibleApplications = list.get_children().filter((row) => row.visible).length;
placeholder.reveal_child = visibleApplications <= 0;
})
.catch(() => { /**/ });
};
const entry = (
<entry
onChanged={(self) => on_text_change(self.text)}
hexpand
/>
) as Widget.Entry;
list.set_sort_func((a, b) => {
return this.sort_func(a, b, entry, this.fzf_results);
});
const refreshItems = () => idle(async() => {
// Delete items that don't exist anymore
const new_list = await this.create_list();
[...this._item_map].forEach(([item, widget]) => {
if (!new_list.some((new_item) =>
this.unique_props?.every((prop) => item[prop] === new_item[prop]) ??
item === new_item)) {
widget.get_parent()?.destroy();
this._item_map.delete(item);
}
});
// Add missing items
new_list.forEach((item) => {
if (!this.item_list.some((old_item) =>
this.unique_props?.every((prop) => old_item[prop] === item[prop]) ??
old_item === item)) {
const itemWidget = this.create_row(item);
list.add(itemWidget);
this._item_map.set(item, itemWidget);
}
});
this.item_list = new_list;
list.show_all();
on_text_change('');
});
this.window = (
<PopupWindow
name={name}
keymode={Astal.Keymode.ON_DEMAND}
on_open={() => {
entry.text = '';
refreshItems();
centerCursor();
entry.grab_focus();
}}
>
<box
vertical
className={`${name} sorted-list`}
>
<box className="widget search">
<icon icon="preferences-system-search-symbolic" />
{entry}
<button
css="margin-left: 5px;"
cursor="pointer"
onButtonReleaseEvent={refreshItems}
>
<icon icon="view-refresh-symbolic" css="font-size: 26px;" />
</button>
</box>
<eventbox cursor="pointer">
<scrollable
className="widget list"
css="min-height: 600px; min-width: 700px;"
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
>
<box vertical>
{list}
{placeholder}
</box>
</scrollable>
</eventbox>
</box>
</PopupWindow>
) as PopupWindow;
this.create_list = create_list;
this.create_row = create_row;
this.fzf_options = fzf_options;
this.unique_props = unique_props;
this.on_row_activated = on_row_activated;
this.sort_func = sort_func;
refreshItems();
}
};
/**
* @param props props for a SortedList Widget
* @returns the widget
*/
export default function<Attr>(props: SortedListProps<Attr>) {
return (new SortedList(props)).window;
}