refactor(agsV2): abstract sorted-list code
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-10-28 18:18:07 -04:00
parent ab6b00c259
commit ccdde81356
8 changed files with 250 additions and 295 deletions

View file

@ -17,6 +17,7 @@ window, viewport {
@import 'widgets/date/style.scss'; @import 'widgets/date/style.scss';
@import 'widgets/icon-browser/style.scss'; @import 'widgets/icon-browser/style.scss';
@import 'widgets/lockscreen/style.scss'; @import 'widgets/lockscreen/style.scss';
@import 'widgets/misc/style.scss';
@import 'widgets/notifs/style.scss'; @import 'widgets/notifs/style.scss';
@import 'widgets/powermenu/style.scss'; @import 'widgets/powermenu/style.scss';
@import 'widgets/screenshot/style.scss'; @import 'widgets/screenshot/style.scss';

View file

@ -1,66 +1,36 @@
import { App, Astal, Gtk, Widget } from 'astal/gtk3'; import { App } from 'astal/gtk3';
import { idle } from 'astal';
import AstalApps from 'gi://AstalApps'; import AstalApps from 'gi://AstalApps';
import { Fzf, FzfResultItem } from 'fzf'; import SortedList from '../misc/sorted-list';
import PopupWindow from '../misc/popup-window';
import { centerCursor } from '../../lib';
import AppItemWidget, { AppItem } from './app-item';
import { launchApp } from './launch'; import { launchApp } from './launch';
import AppItemWidget, { AppItem } from './app-item';
export default () => { export default () => SortedList({
let Applications: AstalApps.Application[] = []; name: 'applauncher',
let fzfResults = [] as FzfResultItem<AstalApps.Application>[];
const list = new Gtk.ListBox({ create_list: () => AstalApps.Apps.new().get_list(),
selectionMode: Gtk.SelectionMode.SINGLE,
});
list.connect('row-activated', (_, row) => { create_row: (app) => AppItemWidget({ app }),
fzf_options: {
selector: (app) => app.name + app.executable,
tiebreakers: [
(a, b) => b.item.frequency - a.item.frequency,
],
},
on_row_activated: (row) => {
const app = (row.get_children()[0] as AppItem).app; const app = (row.get_children()[0] as AppItem).app;
launchApp(app); launchApp(app);
App.get_window('win-applauncher')?.set_visible(false); App.get_window('win-applauncher')?.set_visible(false);
}); },
const placeholder = ( sort_func: (a, b, entry, fzfResults) => {
<revealer>
<label
label=" Couldn't find a match"
className="placeholder"
/>
</revealer>
) as Widget.Revealer;
const on_text_change = (text: string) => {
const fzf = new Fzf(Applications, {
selector: (app) => app.name + app.executable,
tiebreakers: [
(a, b) => b.item.frequency - a.item.frequency,
],
});
fzfResults = fzf.find(text);
list.invalidate_sort();
const visibleApplications = list.get_children().filter((row) => row.visible).length;
placeholder.reveal_child = visibleApplications <= 0;
};
const entry = (
<entry
onChanged={(self) => on_text_change(self.text)}
hexpand
/>
) as Widget.Entry;
list.set_sort_func((a, b) => {
const row1 = (a.get_children()[0] as AppItem).app; const row1 = (a.get_children()[0] as AppItem).app;
const row2 = (b.get_children()[0] as AppItem).app; const row2 = (b.get_children()[0] as AppItem).app;
@ -79,72 +49,5 @@ export default () => {
return s2 - s1; return s2 - s1;
} }
}); },
});
const refreshApplications = () => idle(() => {
(list.get_children() as Gtk.ListBoxRow[])
.forEach((child) => {
child.destroy();
});
Applications = AstalApps.Apps.new().get_list();
Applications
.flatMap((app) => AppItemWidget({ app }))
.forEach((child) => {
list.add(child);
});
list.show_all();
on_text_change('');
});
refreshApplications();
return (
<PopupWindow
name="applauncher"
keymode={Astal.Keymode.ON_DEMAND}
on_open={() => {
entry.text = '';
centerCursor();
}}
>
<box
vertical
className="applauncher"
>
<box className="widget app-search">
<icon icon="preferences-system-search-symbolic" />
{entry}
<button
css="margin-left: 5px;"
cursor="pointer"
onButtonReleaseEvent={refreshApplications}
>
<icon icon="view-refresh-symbolic" css="font-size: 26px;" />
</button>
</box>
<eventbox cursor="pointer">
<scrollable
className="widget app-list"
css="min-height: 600px; min-width: 600px;"
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
>
<box vertical>
{list}
{placeholder}
</box>
</scrollable>
</eventbox>
</box>
</PopupWindow>
);
};

View file

@ -1,32 +1,6 @@
.applauncher { .applauncher {
.app-search { .app {
icon { margin: 20px;
font-size: 20px; font-size: 16px;
min-width: 40px;
min-height: 40px
}
entry {}
}
.app-list {
row {
border-radius: 10px;
&:hover, &:selected {
icon {
-gtk-icon-shadow: 2px 2px $accent_color;
}
}
.app {
margin: 20px;
font-size: 16px;
}
}
.placeholder {
font-size: 20px;
}
} }
} }

View file

@ -1,54 +1,29 @@
import { Astal, Gtk, Widget } from 'astal/gtk3'; import { Gtk, Widget } from 'astal/gtk3';
import { idle } from 'astal';
import { Fzf, FzfResultItem } from 'fzf'; import SortedList from '../misc/sorted-list';
import PopupWindow from '../misc/popup-window';
import { centerCursor } from '../../lib';
export default () => { export default () => SortedList({
let Icons: string[] = []; name: 'icon-browser',
let fzfResults = [] as FzfResultItem<string>[];
const list = new Gtk.ListBox({ create_list: () => Gtk.IconTheme.get_default().list_icons(null)
selectionMode: Gtk.SelectionMode.SINGLE, .filter((icon) => icon.endsWith('symbolic'))
}); .sort(),
list.connect('row-activated', (_, row) => { create_row: (icon) => (
<box>
<icon css="font-size: 60px; margin-right: 25px;" icon={icon} />
<label label={icon} />
</box>
),
on_row_activated: (row) => {
const icon = ((row.get_children()[0] as Widget.Box).get_children()[0] as Widget.Icon).icon; const icon = ((row.get_children()[0] as Widget.Box).get_children()[0] as Widget.Icon).icon;
console.log(icon); console.log(icon);
}); },
const placeholder = ( sort_func: (a, b, entry, fzfResults) => {
<revealer>
<label
label=" Couldn't find a match"
className="placeholder"
/>
</revealer>
) as Widget.Revealer;
const on_text_change = (text: string) => {
const fzf = new Fzf(Icons);
fzfResults = fzf.find(text);
list.invalidate_sort();
const visibleIcons = list.get_children().filter((row) => row.visible).length;
placeholder.reveal_child = visibleIcons <= 0;
};
const entry = (
<entry
onChanged={(self) => on_text_change(self.text)}
hexpand
/>
) as Widget.Entry;
list.set_sort_func((a, b) => {
const row1 = ((a.get_children()[0] as Widget.Box).get_children()[0] as Widget.Icon).icon; const row1 = ((a.get_children()[0] as Widget.Box).get_children()[0] as Widget.Icon).icon;
const row2 = ((b.get_children()[0] as Widget.Box).get_children()[0] as Widget.Icon).icon; const row2 = ((b.get_children()[0] as Widget.Box).get_children()[0] as Widget.Icon).icon;
@ -67,76 +42,5 @@ export default () => {
return s2 - s1; return s2 - s1;
} }
}); },
});
const refreshIcons = () => idle(() => {
(list.get_children() as Gtk.ListBoxRow[])
.forEach((child) => {
child.destroy();
});
Icons = Gtk.IconTheme.get_default().list_icons(null)
.filter((icon) => icon.endsWith('symbolic'))
.sort();
Icons
.flatMap((icon) => (
<box>
<icon css="font-size: 60px; margin-right: 25px;" icon={icon} />
<label label={icon} />
</box>
))
.forEach((child) => {
list.add(child);
});
list.show_all();
on_text_change('');
});
refreshIcons();
return (
<PopupWindow
name="icon-browser"
keymode={Astal.Keymode.ON_DEMAND}
on_open={() => {
entry.text = '';
centerCursor();
}}
>
<box
vertical
className="icon-browser"
>
<box className="widget icon-search">
<icon icon="preferences-system-search-symbolic" />
{entry}
<button
css="margin-left: 5px;"
onButtonReleaseEvent={refreshIcons}
>
<icon icon="view-refresh-symbolic" css="font-size: 26px;" />
</button>
</box>
<scrollable
className="widget icon-list"
css="min-height: 600px; min-width: 600px;"
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
>
<box vertical>
{list}
{placeholder}
</box>
</scrollable>
</box>
</PopupWindow>
);
};

View file

@ -1,32 +1,4 @@
.icon-browser { .icon-browser .icon-list row box {
.icon-search { margin: 20px;
icon { font-size: 16px;
font-size: 20px;
min-width: 40px;
min-height: 40px
}
entry {}
}
.icon-list {
row {
border-radius: 10px;
&:hover, &:selected {
icon {
-gtk-icon-shadow: 2px 2px $accent_color;
}
}
box {
margin: 20px;
font-size: 16px;
}
}
.placeholder {
font-size: 20px;
}
}
} }

View file

@ -8,9 +8,9 @@ import { get_hyprland_monitor, hyprMessage } from '../../lib';
type CloseType = 'none' | 'stay' | 'released' | 'clicked'; type CloseType = 'none' | 'stay' | 'released' | 'clicked';
type HyprTransition = 'slide' | 'slide top' | 'slide bottom' | 'slide left' | type HyprTransition = 'slide' | 'slide top' | 'slide bottom' | 'slide left' |
'slide right' | 'popin' | 'fade'; 'slide right' | 'popin' | 'fade';
type PopupCallback = (self: PopupWindow) => void; type PopupCallback = (self?: Widget.Window) => void;
type PopupWindowProps = Widget.WindowProps & { export type PopupWindowProps = Widget.WindowProps & {
transition?: HyprTransition | Binding<HyprTransition> transition?: HyprTransition | Binding<HyprTransition>
close_on_unfocus?: CloseType | Binding<CloseType> close_on_unfocus?: CloseType | Binding<CloseType>
on_open?: PopupCallback on_open?: PopupCallback

View file

@ -0,0 +1,174 @@
// 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 { Fzf, FzfOptions, FzfResultItem } from 'fzf';
import PopupWindow, { PopupWindow as PopupWindowClass } from '../misc/popup-window';
import { centerCursor } from '../../lib';
export interface SortedListProps<T> {
create_list: () => T[]
create_row: (item: T) => Gtk.Widget
fzf_options?: FzfOptions<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: PopupWindowClass;
readonly create_list: () => T[];
readonly create_row: (item: T) => Gtk.Widget;
readonly fzf_options: FzfOptions<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,
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 should be okay
this.fzf_results = (new Fzf(this.item_list, this.fzf_options)).find(text);
list.invalidate_sort();
const visibleApplications = list.get_children().filter((row) => row.visible).length;
placeholder.reveal_child = visibleApplications <= 0;
};
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(() => {
(list.get_children() as Gtk.ListBoxRow[])
.forEach((child) => {
child.destroy();
});
this.item_list = this.create_list();
this.item_list
.flatMap((prop) => this.create_row(prop))
.forEach((child) => {
list.add(child);
});
list.show_all();
on_text_change('');
});
this.window = (
<PopupWindow
name={name}
keymode={Astal.Keymode.ON_DEMAND}
on_open={() => {
entry.text = '';
centerCursor();
}}
>
<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: 600px;"
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
>
<box vertical>
{list}
{placeholder}
</box>
</scrollable>
</eventbox>
</box>
</PopupWindow>
) as PopupWindowClass;
this.create_list = create_list;
this.create_row = create_row;
this.fzf_options = fzf_options;
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;
}

View file

@ -0,0 +1,27 @@
.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 $accent_color;
}
}
}
.placeholder {
font-size: 20px;
}
}
}