refactor(ags): start update types and use classes for cbox and popup
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-01-29 18:54:07 -05:00
parent 49f3a92ce3
commit 9c64b00243
30 changed files with 914 additions and 610 deletions

View file

@ -2,7 +2,7 @@
"env": { "env": {
"es2021": true "es2021": true
}, },
"extends": "eslint:recommended", "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"overrides": [], "overrides": [],
"parserOptions": { "parserOptions": {
@ -19,9 +19,6 @@
"no-unreachable-loop": ["error", { "ignore": [ "no-unreachable-loop": ["error", { "ignore": [
"ForInStatement", "ForOfStatement" "ForInStatement", "ForOfStatement"
]}], ]}],
"no-use-before-define": ["error", {
"functions": false
}],
"block-scoped-var": ["error"], "block-scoped-var": ["error"],
@ -79,6 +76,7 @@
"no-unused-vars": "off", "no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-unsafe-declaration-merging": "off",
"@stylistic/array-bracket-newline": ["warn", "consistent"], "@stylistic/array-bracket-newline": ["warn", "consistent"],
"@stylistic/array-bracket-spacing": ["warn", "never"], "@stylistic/array-bracket-spacing": ["warn", "never"],

129
modules/ags/config/global-types.d.ts vendored Normal file
View file

@ -0,0 +1,129 @@
import { Widget } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
import { Widget as agsWidget } from 'types/widgets/widget';
export type AgsWidget = agsWidget<unknown> & Widget;
// For ./ts/applauncher/main.ts
import { Application } from 'types/service/applications.ts';
import { CursorBoxProps } from 'ts/misc/cursorbox';
export type AgsAppItem = AgsEventBox<Widget, { app: Application }
& CursorBoxProps<Widget, unknown>>;
// For ./ts/bar/hovers/keyboard.ts
export type Keyboard = {
address: string;
name: string;
rules: string;
model: string;
layout: string;
variant: string;
options: string;
active_keymap: string;
main: boolean;
};
// For ./ts/bar/items/workspaces.ts
// TODO: improve type
export type Workspace = AgsRevealer<unknown & Widget, unknown & { id: number }>;
// For ./ts/bar/fullscreen.ts
export type DefaultProps = RevealerProps<CenterBoxGeneric>;
// For ./ts/media-player/gesture.ts
export type Gesture = {
attribute?: object
setup?(self: OverlayGeneric): void
props?: OverlayProps<unknown & Widget, unknown>
};
// For ./ts/media-player/mpris.ts
type PlayerDragProps = unknown & { dragging: boolean };
export type PlayerDrag = AgsCenterBox<
unknown & Widget, unknown & Widget, unknown & Widget, unknown & PlayerDragProps
>;
type Colors = {
imageAccent: string;
buttonAccent: string;
buttonText: string;
hoverAccent: string;
};
// For ./ts/media-player
export type PlayerBoxProps = {
bgStyle: string,
player: MprisPlayer,
};
export type PlayerBox = AgsCenterBox<
unknown & Widget, unknown & Widget, unknown & Widget, PlayerBoxProps
>;
export type PlayerOverlay = AgsOverlay<AgsWidget, {
players: Map;
setup: boolean;
dragging: boolean;
includesWidget(playerW: PlayerBox): PlayerBox;
showTopOnly(): void;
moveToTop(player: PlayerBox): void;
}>;
export type PlayerButtonType = {
player: MprisPlayer
colors: Var<Colors>
items: Array<[name: string, widget: AgsWidget]>
onClick: string
prop: string
};
// For CursorBox
import { CursorBox, CursorBoxProps } from 'ts/misc/cursorbox';
export type CursorBox = CursorBox;
export type CursorBoxProps = CursorBoxProps;
// For PopupWindow
export type PopupChild = Binding<
Var<Widget>,
'is_listening' | 'is_polling' | 'value',
Widget[]
>;
export type CloseType = 'none' | 'stay' | 'released' | 'clicked';
export type PopupWindowProps<Child extends Widget, Attr = unknown> =
WindowProps<Child> & {
transition?: RevealerProps<Widget>['transition']
transition_duration?: number
bezier?: string
on_open?(self: AgsWindow<Widget, unknown>): void
on_close?(self: AgsWindow<Widget, unknown>): void
blur?: boolean
close_on_unfocus?: CloseType
attribute?: Attr;
content?: Widget
anchor?: Array<'top' | 'bottom' | 'right' | 'left'>;
};
import { PopupWindow } from 'ts/misc/popup';
export type PopupWindow = PopupWindow;
// Generic widgets
import AgsBox from 'types/widgets/box.ts';
export type BoxGeneric = AgsBox<unknown & Widget, unknown>;
import AgsCenterBox, { CenterBoxProps } from 'types/widgets/centerbox';
export type CenterBoxGeneric = AgsCenterBox<
unknown & Widget, unknown & Widget, unknown & Widget, unknown
>;
export type CenterBoxPropsGeneric = CenterBoxProps<
unknown & Widget, unknown & Widget, unknown & Widget, unknown
>;
import AgsEventBox from 'types/widgets/eventbox.ts';
export type EventBoxGeneric = AgsEventBox<unknown & Widget, unknown>;
import AgsLabel from 'types/widgets/label.ts';
export type LabelGeneric = AgsLabel<unknown>;
import AgsOverlay, { OverlayProps } from 'types/widgets/overlay.ts';
export type OverlayGeneric = AgsOverlay<unknown & Widget, unknown>;
import AgsRevealer, { RevealerProps } from 'types/widgets/revealer.ts';
export type RevealerGeneric = AgsRevealer<unknown & Widget, unknown>;
import AgsStack from 'types/widgets/stack.ts';
export type StackGeneric = AgsStack<unknown & Widget, unknown>;

View file

@ -27,7 +27,7 @@
padding: 4.5px 9px; padding: 4.5px 9px;
} }
&.hover box { &:hover box {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: rgba(238, 238, 238, 0.154); background-color: rgba(238, 238, 238, 0.154);
color: #f1f1f1; color: #f1f1f1;

View file

@ -31,7 +31,7 @@
padding: 4.5px 9px; padding: 4.5px 9px;
} }
&.hover box { &:hover box {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: rgba(238, 238, 238, 0.154); background-color: rgba(238, 238, 238, 0.154);
color: #f1f1f1; color: #f1f1f1;

View file

@ -48,7 +48,7 @@
border 0.5s ease-in-out; border 0.5s ease-in-out;
} }
.toggle-on.hover, .toggle-off.hover { .toggle-on:hover, .toggle-off:hover {
background-color: rgba(127, 132, 156, 0.4); background-color: rgba(127, 132, 156, 0.4);
transition: background-color 0.5s ease-in-out, transition: background-color 0.5s ease-in-out,
border 0.5s ease-in-out; border 0.5s ease-in-out;

View file

@ -15,7 +15,7 @@ const ON_CLICK_TRIGGERS = [
]; ];
// Types // Types
import AgsWindow from 'types/widgets/window'; import { PopupWindow } from 'global-types';
import { Subprocess } from 'types/@girs/gio-2.0/gio-2.0.cjs'; import { Subprocess } from 'types/@girs/gio-2.0/gio-2.0.cjs';
type Layer = { type Layer = {
address: string; address: string;
@ -54,7 +54,7 @@ class Pointers extends Service {
#process = null as Subprocess | null; #process = null as Subprocess | null;
#lastLine = ''; #lastLine = '';
#pointers = [] as Array<String>; #pointers = [] as Array<string>;
get process() { get process() {
return this.#process; return this.#process;
@ -114,11 +114,11 @@ class Pointers extends Service {
#initAppConnection() { #initAppConnection() {
App.connect('window-toggled', () => { App.connect('window-toggled', () => {
const anyVisibleAndClosable = const anyVisibleAndClosable =
(Array.from(App.windows) as Array<[string, AgsWindow]>) (Array.from(App.windows) as Array<[string, PopupWindow]>)
.some((w) => { .some((w) => {
const closable = w[1].attribute?.close_on_unfocus && const closable = w[1].close_on_unfocus &&
!(w[1].attribute.close_on_unfocus === 'none' || !(w[1].close_on_unfocus === 'none' ||
w[1].attribute.close_on_unfocus === 'stay'); w[1].close_on_unfocus === 'stay');
return w[1].visible && closable; return w[1].visible && closable;
}); });
@ -134,10 +134,10 @@ class Pointers extends Service {
} }
static detectClickedOutside(clickStage: string) { static detectClickedOutside(clickStage: string) {
const toClose = (Array.from(App.windows) as Array<[string, AgsWindow]>) const toClose = (Array.from(App.windows) as Array<[string, PopupWindow]>)
.some((w) => { .some((w) => {
const closable = (w[1].attribute?.close_on_unfocus && const closable = (w[1].close_on_unfocus &&
w[1].attribute.close_on_unfocus === clickStage); w[1].close_on_unfocus === clickStage);
return w[1].visible && closable; return w[1].visible && closable;
}); });
@ -190,11 +190,11 @@ class Pointers extends Service {
const widgets = overlayLayer.filter((n) => { const widgets = overlayLayer.filter((n) => {
const window = const window =
(App.getWindow(n.namespace) as AgsWindow); (App.getWindow(n.namespace) as PopupWindow);
return window && return window &&
window.attribute?.close_on_unfocus && window.close_on_unfocus &&
window.attribute?.close_on_unfocus === window.close_on_unfocus ===
clickStage; clickStage;
}); });

View file

@ -1,8 +1,7 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js'; import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
// FIXME: find cleaner way to import this // @ts-expect-error find cleaner way to import this
// @ts-expect-error import { Fzf, FzfResultItem } from 'file:///home/matt/.nix/modules/ags/config/node_modules/fzf/dist/fzf.es.js';
import { Fzf } from 'file:///home/matt/.nix/modules/ags/config/node_modules/fzf/dist/fzf.es.js';
import { Box, Entry, Icon, Label, ListBox, Revealer, Scrollable } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Entry, Icon, Label, ListBox, Revealer, Scrollable } from 'resource:///com/github/Aylur/ags/widget.js';
@ -10,14 +9,13 @@ import PopupWindow from '../misc/popup.ts';
import AppItem from './app-item.ts'; import AppItem from './app-item.ts';
// Types // Types
import { Application } from 'types/service/applications.ts';
import { ListBoxRow } from 'types/@girs/gtk-3.0/gtk-3.0.cjs'; import { ListBoxRow } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
import AgsEventBox from 'types/widgets/eventbox'; import { Application } from 'types/service/applications.ts';
import { AgsAppItem } from 'global-types';
const Applauncher = (window_name = 'applauncher') => { const Applauncher = (window_name = 'applauncher') => {
let fzfResults: Array<any>; let fzfResults: FzfResultItem<Application>[];
// @ts-expect-error
const list = ListBox(); const list = ListBox();
const setSort = (text: string) => { const setSort = (text: string) => {
@ -31,12 +29,9 @@ const Applauncher = (window_name = 'applauncher') => {
}); });
fzfResults = fzf.find(text); fzfResults = fzf.find(text);
list.set_sort_func( list.set_sort_func((a, b) => {
(a: ListBoxRow, b: ListBoxRow) => { const row1 = (a.get_children()[0] as AgsAppItem).attribute.app.name;
const row1 = (a.get_children()[0] as AgsEventBox) const row2 = (b.get_children()[0] as AgsAppItem).attribute.app.name;
?.attribute.app.name;
const row2 = (b.get_children()[0] as AgsEventBox)
?.attribute.app.name;
if (!row1 || !row2) { if (!row1 || !row2) {
return 0; return 0;
@ -44,12 +39,11 @@ const Applauncher = (window_name = 'applauncher') => {
return fzfResults.indexOf(row1) - return fzfResults.indexOf(row1) -
fzfResults.indexOf(row1) || 0; fzfResults.indexOf(row1) || 0;
}, });
);
}; };
const makeNewChildren = () => { const makeNewChildren = () => {
const rows = list.get_children() as Array<ListBoxRow>; const rows = list.get_children() as ListBoxRow[];
rows.forEach((ch) => { rows.forEach((ch) => {
ch.destroy(); ch.destroy();
@ -94,14 +88,14 @@ const Applauncher = (window_name = 'applauncher') => {
setSort(text); setSort(text);
let visibleApps = 0; let visibleApps = 0;
const rows = list.get_children() as Array<ListBoxRow>; const rows = list.get_children() as ListBoxRow[];
rows.forEach((row) => { rows.forEach((row) => {
row.changed(); row.changed();
const item = (row.get_children()[0] as AgsEventBox); const item = (row.get_children()[0] as AgsAppItem);
if (item?.attribute.app) { if (item.attribute.app) {
const isMatching = fzfResults.find((r) => { const isMatching = fzfResults.find((r) => {
return r.item.name === item.attribute.app.name; return r.item.name === item.attribute.app.name;
}); });
@ -162,5 +156,5 @@ const Applauncher = (window_name = 'applauncher') => {
export default () => PopupWindow({ export default () => PopupWindow({
name: 'applauncher', name: 'applauncher',
keymode: 'on-demand', keymode: 'on-demand',
child: Applauncher(), content: Applauncher(),
}); });

View file

@ -5,8 +5,8 @@ import { Box, EventBox, Revealer, Window } from 'resource:///com/github/Aylur/ag
// Types // Types
import { Variable as Var } from 'types/variable'; import { Variable as Var } from 'types/variable';
import AgsBox from 'types/widgets/box'; import { BoxGeneric, DefaultProps } from 'global-types';
import { RevealerProps } from 'types/widgets/revealer';
const BarCloser = (variable: Var<boolean>) => Window({ const BarCloser = (variable: Var<boolean>) => Window({
name: 'bar-closer', name: 'bar-closer',
@ -30,7 +30,7 @@ const BarCloser = (variable: Var<boolean>) => Window({
}), }),
}); });
export default (props: RevealerProps) => { export default (props?: DefaultProps) => {
const Revealed = Variable(true); const Revealed = Variable(true);
const barCloser = BarCloser(Revealed); const barCloser = BarCloser(Revealed);
@ -50,7 +50,7 @@ export default (props: RevealerProps) => {
} }
}; };
const checkGlobalFsState = (_: AgsBox, fullscreen: boolean) => { const checkGlobalFsState = (_: BoxGeneric, fullscreen: boolean) => {
Revealed.value = !fullscreen; Revealed.value = !fullscreen;
}; };

View file

@ -6,22 +6,11 @@ import HoverRevealer from './hover-revealer.ts';
const DEFAULT_KB = 'at-translated-set-2-keyboard'; const DEFAULT_KB = 'at-translated-set-2-keyboard';
import AgsLabel from 'types/widgets/label.ts'; // Types
type Keyboard = { import { Keyboard, LabelGeneric } from 'global-types';
address: string;
name: string;
rules: string;
model: string;
layout: string;
variant: string;
options: string;
active_keymap: string;
main: boolean;
};
const getKbdLayout = (self: LabelGeneric, _: string, layout: string) => {
const getKbdLayout = (self: AgsLabel, _: string, layout: string) => {
if (layout) { if (layout) {
if (layout === 'error') { if (layout === 'error') {
return; return;
@ -35,7 +24,7 @@ const getKbdLayout = (self: AgsLabel, _: string, layout: string) => {
// At launch, kb layout is undefined // At launch, kb layout is undefined
Hyprland.sendMessage('j/devices').then((obj) => { Hyprland.sendMessage('j/devices').then((obj) => {
const keyboards = Array.from(JSON.parse(obj) const keyboards = Array.from(JSON.parse(obj)
.keyboards) as Array<Keyboard>; .keyboards) as Keyboard[];
const kb = keyboards.find((v) => v.name === DEFAULT_KB); const kb = keyboards.find((v) => v.name === DEFAULT_KB);
if (kb) { if (kb) {

View file

@ -9,15 +9,15 @@ import Separator from '../../misc/separator.ts';
const SPACING = 4; const SPACING = 4;
// Types // Types
import AgsWindow from 'types/widgets/window.ts'; import { PopupWindow } from 'global-types';
export default () => CursorBox({ export default () => CursorBox({
class_name: 'toggle-off', class_name: 'toggle-off',
on_primary_click_release: (self) => { on_primary_click_release: (self) => {
(App.getWindow('notification-center') as AgsWindow) (App.getWindow('notification-center') as PopupWindow)
?.attribute.set_x_pos( .set_x_pos(
self.get_allocation(), self.get_allocation(),
'right', 'right',
); );

View file

@ -14,9 +14,7 @@ import Separator from '../../misc/separator.ts';
const SPACING = 4; const SPACING = 4;
// Types // Types
import AgsRevealer from 'types/widgets/revealer.ts'; import { PopupWindow } from 'global-types';
import AgsBox from 'types/widgets/box.ts';
import AgsWindow from 'types/widgets/window.ts';
export default () => { export default () => {
@ -36,8 +34,8 @@ export default () => {
class_name: 'toggle-off', class_name: 'toggle-off',
on_primary_click_release: (self) => { on_primary_click_release: (self) => {
(App.getWindow('quick-settings') as AgsWindow) (App.getWindow('quick-settings') as PopupWindow)
?.attribute.set_x_pos( .set_x_pos(
self.get_allocation(), self.get_allocation(),
'right', 'right',
); );
@ -55,17 +53,15 @@ export default () => {
attribute: { attribute: {
hoverRevealers: hoverRevealers.map((rev) => { hoverRevealers: hoverRevealers.map((rev) => {
const box = rev.child as AgsBox; const box = rev.child;
return box.children[1]; return box.children[1];
}), }),
}, },
on_hover_lost: (self) => { on_hover_lost: (self) => {
self.attribute.hoverRevealers.forEach( self.attribute.hoverRevealers.forEach((rev) => {
(rev: AgsRevealer) => {
rev.reveal_child = false; rev.reveal_child = false;
}, });
);
}, },
child: Box({ child: Box({

View file

@ -10,7 +10,6 @@ const SPACING = 12;
// Types // Types
import { TrayItem } from 'types/service/systemtray.ts'; import { TrayItem } from 'types/service/systemtray.ts';
import AgsRevealer from 'types/widgets/revealer.ts';
const SysTrayItem = (item: TrayItem) => { const SysTrayItem = (item: TrayItem) => {
@ -54,7 +53,7 @@ const SysTray = () => MenuBar({
self.child = w; self.child = w;
self.show_all(); self.show_all();
(<AgsRevealer> w.child).reveal_child = true; w.child.reveal_child = true;
}, 'added') }, 'added')
.hook(SystemTray, (_, id) => { .hook(SystemTray, (_, id) => {

View file

@ -8,10 +8,13 @@ import CursorBox from '../../misc/cursorbox.ts';
const URGENT_DURATION = 1000; const URGENT_DURATION = 1000;
// Types // Types
import AgsBox from 'types/widgets/box.ts'; import {
import AgsRevealer from 'types/widgets/revealer.ts'; BoxGeneric,
import AgsOverlay from 'types/widgets/overlay.ts'; EventBoxGeneric,
import AgsEventBox from 'types/widgets/eventbox.ts'; OverlayGeneric,
RevealerGeneric,
Workspace,
} from 'global-types';
const Workspace = ({ id }: { id: number }) => { const Workspace = ({ id }: { id: number }) => {
@ -31,7 +34,10 @@ const Workspace = ({ id }: { id: number }) => {
class_name: 'button', class_name: 'button',
setup: (self) => { setup: (self) => {
const update = (_: AgsBox, addr: string | undefined) => { const update = (
_: BoxGeneric,
addr: string | undefined,
) => {
const workspace = Hyprland.getWorkspace(id); const workspace = Hyprland.getWorkspace(id);
const occupied = workspace && workspace.windows > 0; const occupied = workspace && workspace.windows > 0;
@ -79,13 +85,13 @@ export default () => {
const L_PADDING = 16; const L_PADDING = 16;
const WS_WIDTH = 30; const WS_WIDTH = 30;
const updateHighlight = (self: AgsBox) => { const updateHighlight = (self: BoxGeneric) => {
const currentId = Hyprland.active.workspace.id; const currentId = Hyprland.active.workspace.id;
const indicators = (((self.get_parent() as AgsOverlay) const indicators = (((self.get_parent() as OverlayGeneric)
.child as AgsEventBox) .child as EventBoxGeneric)
.child as AgsBox) .child as BoxGeneric)
.children as Array<AgsRevealer>; .children as Workspace[];
const currentIndex = indicators const currentIndex = indicators
.findIndex((w) => w.attribute.id === currentId); .findIndex((w) => w.attribute.id === currentId);
@ -111,25 +117,25 @@ export default () => {
child: Box({ child: Box({
class_name: 'workspaces', class_name: 'workspaces',
attribute: { workspaces: [] }, attribute: { workspaces: [] as Workspace[] },
setup: (self) => { setup: (self) => {
const workspaces = (): Array<AgsRevealer> =>
self.attribute.workspaces;
const refresh = () => { const refresh = () => {
(self.children as Array<AgsRevealer>).forEach((rev) => { (self.children as RevealerGeneric[])
.forEach((rev) => {
rev.reveal_child = false; rev.reveal_child = false;
}); });
workspaces().forEach((ws) => { self.attribute.workspaces
.forEach((ws) => {
ws.reveal_child = true; ws.reveal_child = true;
}); });
}; };
const updateWorkspaces = () => { const updateWorkspaces = () => {
Hyprland.workspaces.forEach((ws) => { Hyprland.workspaces.forEach((ws) => {
const currentWs = (self.children as Array<AgsBox>) const currentWs =
(self.children as Workspace[])
.find((ch) => ch.attribute.id === ws.id); .find((ch) => ch.attribute.id === ws.id);
if (!currentWs && ws.id > 0) { if (!currentWs && ws.id > 0) {
@ -139,21 +145,22 @@ export default () => {
self.show_all(); self.show_all();
// Make sure the order is correct // Make sure the order is correct
workspaces().forEach((workspace, i) => { self.attribute.workspaces.forEach((workspace, i) => {
(<AgsBox> workspace.get_parent()).reorder_child( (workspace.get_parent() as BoxGeneric)
workspace, .reorder_child(workspace, i);
i,
);
}); });
}; };
self.hook(Hyprland, () => { self.hook(Hyprland, () => {
self.attribute.workspaces = self.attribute.workspaces =
(self.children as Array<AgsBox>).filter((ch) => { (self.children as Workspace[])
.filter((ch) => {
return Hyprland.workspaces.find((ws) => { return Hyprland.workspaces.find((ws) => {
return ws.id === ch.attribute.id; return ws.id === ch.attribute.id;
}); });
}).sort((a, b) => a.attribute.id - b.attribute.id); })
.sort((a, b) =>
a.attribute.id - b.attribute.id);
updateWorkspaces(); updateWorkspaces();
refresh(); refresh();

View file

@ -26,10 +26,7 @@ export default (
.get_property('border-radius', Gtk.StateFlags.NORMAL); .get_property('border-radius', Gtk.StateFlags.NORMAL);
widget.set_size_request(r, r); widget.set_size_request(r, r);
widget.connect('draw', (_, context) => { widget.connect('draw', (_, cr) => {
// FIXME: get proper Context type
const cr = context as any;
const c = widget.get_style_context() const c = widget.get_style_context()
.get_property('background-color', Gtk.StateFlags.NORMAL); .get_property('background-color', Gtk.StateFlags.NORMAL);

View file

@ -90,7 +90,7 @@ export default () => PopupWindow({
anchor: ['top'], anchor: ['top'],
margins: [TOP_MARGIN, 0, 0, 0], margins: [TOP_MARGIN, 0, 0, 0],
child: Box({ content: Box({
class_name: 'date', class_name: 'date',
vertical: true, vertical: true,

View file

@ -10,20 +10,15 @@ const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease,
opacity ${ANIM_DURATION}ms ease;`; opacity ${ANIM_DURATION}ms ease;`;
// Types // Types
import AgsOverlay from 'types/widgets/overlay'; import {
import OverlayProps from 'types/widgets/overlay'; CenterBoxGeneric,
import AgsBox from 'types/widgets/box'; Gesture,
import AgsCenterBox from 'types/widgets/centerbox'; OverlayGeneric,
import { Connectable } from 'types/widgets/widget'; PlayerBox,
type Gesture = { } from 'global-types';
attribute?: Object
setup?(self: Connectable<AgsOverlay> & AgsOverlay): void
props?: OverlayProps
};
export default ({ export default ({
attribute = {},
setup = () => {/**/}, setup = () => {/**/},
...props ...props
}: Gesture) => { }: Gesture) => {
@ -39,10 +34,11 @@ export default ({
const content = Overlay({ const content = Overlay({
...props, ...props,
attribute: { attribute: {
...attribute, players: new Map(),
setup: false,
dragging: false, dragging: false,
includesWidget: (playerW: AgsOverlay) => { includesWidget: (playerW: OverlayGeneric) => {
return content.overlays.find((w) => w === playerW); return content.overlays.find((w) => w === playerW);
}, },
@ -50,7 +46,7 @@ export default ({
over.visible = over === content.overlays.at(-1); over.visible = over === content.overlays.at(-1);
}), }),
moveToTop: (player: AgsCenterBox) => { moveToTop: (player: CenterBoxGeneric) => {
player.visible = true; player.visible = true;
content.reorder_overlay(player, -1); content.reorder_overlay(player, -1);
timeout(ANIM_DURATION, () => { timeout(ANIM_DURATION, () => {
@ -82,7 +78,7 @@ export default ({
self.attribute.dragging = true; self.attribute.dragging = true;
let offset = gesture.get_offset()[1]; let offset = gesture.get_offset()[1];
const playerBox = self.overlays.at(-1) as AgsBox; const playerBox = self.overlays.at(-1) as PlayerBox;
if (!offset) { if (!offset) {
return; return;
@ -118,7 +114,7 @@ export default ({
self.attribute.dragging = false; self.attribute.dragging = false;
const offset = gesture.get_offset()[1]; const offset = gesture.get_offset()[1];
const playerBox = self.overlays.at(-1) as AgsBox; const playerBox = self.overlays.at(-1) as PlayerBox;
// If crosses threshold after letting go, slide away // If crosses threshold after letting go, slide away
if (offset && Math.abs(offset) > MAX_OFFSET) { if (offset && Math.abs(offset) > MAX_OFFSET) {

View file

@ -31,17 +31,23 @@ const icons = {
// Types // Types
import { MprisPlayer } from 'types/service/mpris.ts'; import { MprisPlayer } from 'types/service/mpris.ts';
import { Variable as Var } from 'types/variable'; import { Variable as Var } from 'types/variable';
import AgsOverlay from 'types/widgets/overlay.ts';
import AgsCenterBox, { CenterBoxProps } from 'types/widgets/centerbox.ts'; import {
import AgsLabel from 'types/widgets/label.ts'; AgsWidget,
import AgsIcon from 'types/widgets/icon.ts'; CenterBoxPropsGeneric,
import AgsStack from 'types/widgets/stack.ts'; Colors,
PlayerBox,
PlayerButtonType,
PlayerDrag,
PlayerOverlay,
StackGeneric,
} from 'global-types';
export const CoverArt = ( export const CoverArt = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
props: CenterBoxProps, props: CenterBoxPropsGeneric,
) => CenterBox({ ) => CenterBox({
...props, ...props,
vertical: true, vertical: true,
@ -51,7 +57,7 @@ export const CoverArt = (
player, player,
}, },
setup: (self) => { setup: (self: PlayerBox) => {
// Give temp cover art // Give temp cover art
readFileAsync(player.cover_path).catch(() => { readFileAsync(player.cover_path).catch(() => {
if (!colors.value && !player.track_cover_url) { if (!colors.value && !player.track_cover_url) {
@ -93,7 +99,7 @@ export const CoverArt = (
background-position: center; background-position: center;
`; `;
if (!(self.get_parent() as AgsCenterBox) if (!(self.get_parent() as PlayerDrag)
.attribute.dragging) { .attribute.dragging) {
self.setCss(self.attribute.bgStyle); self.setCss(self.attribute.bgStyle);
} }
@ -126,16 +132,18 @@ export const ArtistLabel = (player: MprisPlayer) => Label({
}); });
export const PlayerIcon = (player: MprisPlayer, overlay: AgsOverlay) => { export const PlayerIcon = (player: MprisPlayer, overlay: PlayerOverlay) => {
const playerIcon = ( const playerIcon = (
p: MprisPlayer, p: MprisPlayer,
widget?: AgsOverlay, widget?: PlayerOverlay,
over?: AgsOverlay, playerBox?: PlayerBox,
) => CursorBox({ ) => CursorBox({
tooltip_text: p.identity || '', tooltip_text: p.identity || '',
on_primary_click_release: () => { on_primary_click_release: () => {
widget?.attribute.moveToTop(over); if (widget && playerBox) {
widget.attribute.moveToTop(playerBox);
}
}, },
child: Icon({ child: Icon({
@ -153,7 +161,7 @@ export const PlayerIcon = (player: MprisPlayer, overlay: AgsOverlay) => {
}); });
return Box().hook(Mpris, (self) => { return Box().hook(Mpris, (self) => {
const grandPa = self.get_parent()?.get_parent(); const grandPa = self.get_parent()?.get_parent() as AgsWidget;
if (!grandPa) { if (!grandPa) {
return; return;
@ -162,13 +170,13 @@ export const PlayerIcon = (player: MprisPlayer, overlay: AgsOverlay) => {
const thisIndex = overlay.overlays const thisIndex = overlay.overlays
.indexOf(grandPa); .indexOf(grandPa);
self.children = (overlay.overlays as Array<AgsOverlay>) self.children = (overlay.overlays as PlayerBox[])
.map((over, i) => { .map((playerBox, i) => {
self.children.push(Separator(2)); self.children.push(Separator(2));
return i === thisIndex ? return i === thisIndex ?
playerIcon(player) : playerIcon(player) :
playerIcon(over.attribute.player, overlay, over); playerIcon(playerBox.attribute.player, overlay, playerBox);
}) })
.reverse(); .reverse();
}); });
@ -179,7 +187,7 @@ const display = Gdk.Display.get_default();
export const PositionSlider = ( export const PositionSlider = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
) => Slider({ ) => Slider({
class_name: 'position-slider', class_name: 'position-slider',
vpack: 'center', vpack: 'center',
@ -258,13 +266,6 @@ export const PositionSlider = (
}, },
}); });
type PlayerButtonType = {
player: MprisPlayer
colors: Var<any>
items: Array<[name: string, widget: AgsLabel | AgsIcon]>
onClick: string
prop: string
};
const PlayerButton = ({ const PlayerButton = ({
player, player,
colors, colors,
@ -316,7 +317,7 @@ const PlayerButton = ({
setup: (self) => { setup: (self) => {
self self
.hook(player, () => { .hook(player, () => {
(self.child as AgsStack).shown = `${player[prop]}`; (self.child as StackGeneric).shown = `${player[prop]}`;
}) })
.hook(colors, () => { .hook(colors, () => {
if (!Mpris.players.find((p) => player === p)) { if (!Mpris.players.find((p) => player === p)) {
@ -364,7 +365,7 @@ const PlayerButton = ({
export const ShuffleButton = ( export const ShuffleButton = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
) => PlayerButton({ ) => PlayerButton({
player, player,
colors, colors,
@ -384,7 +385,7 @@ export const ShuffleButton = (
export const LoopButton = ( export const LoopButton = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
) => PlayerButton({ ) => PlayerButton({
player, player,
colors, colors,
@ -408,7 +409,7 @@ export const LoopButton = (
export const PlayPauseButton = ( export const PlayPauseButton = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
) => PlayerButton({ ) => PlayerButton({
player, player,
colors, colors,
@ -432,7 +433,7 @@ export const PlayPauseButton = (
export const PreviousButton = ( export const PreviousButton = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
) => PlayerButton({ ) => PlayerButton({
player, player,
colors, colors,
@ -452,7 +453,7 @@ export const PreviousButton = (
export const NextButton = ( export const NextButton = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
) => PlayerButton({ ) => PlayerButton({
player, player,
colors, colors,

View file

@ -12,14 +12,13 @@ const SPACING = 8;
// Types // Types
import { MprisPlayer } from 'types/service/mpris.ts'; import { MprisPlayer } from 'types/service/mpris.ts';
import AgsOverlay from 'types/widgets/overlay.ts';
import { Variable as Var } from 'types/variable'; import { Variable as Var } from 'types/variable';
import AgsBox from 'types/widgets/box.ts'; import { Colors, PlayerBox, PlayerOverlay } from 'global-types';
const Top = ( const Top = (
player: MprisPlayer, player: MprisPlayer,
overlay: AgsOverlay, overlay: PlayerOverlay,
) => Box({ ) => Box({
class_name: 'top', class_name: 'top',
hpack: 'start', hpack: 'start',
@ -32,7 +31,7 @@ const Top = (
const Center = ( const Center = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
) => Box({ ) => Box({
class_name: 'center', class_name: 'center',
@ -65,7 +64,7 @@ const Center = (
const Bottom = ( const Bottom = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
) => Box({ ) => Box({
class_name: 'bottom', class_name: 'bottom',
@ -88,8 +87,8 @@ const Bottom = (
const PlayerBox = ( const PlayerBox = (
player: MprisPlayer, player: MprisPlayer,
colors: Var<any>, colors: Var<Colors>,
overlay: AgsOverlay, overlay: PlayerOverlay,
) => { ) => {
const widget = mpris.CoverArt(player, colors, { const widget = mpris.CoverArt(player, colors, {
class_name: `player ${player.name}`, class_name: `player ${player.name}`,
@ -107,14 +106,9 @@ const PlayerBox = (
export default () => { export default () => {
const content = PlayerGesture({ const content = PlayerGesture({
attribute: { setup: (self: PlayerOverlay) => {
players: new Map(),
setup: false,
},
setup: (self) => {
self self
.hook(Mpris, (_: AgsOverlay, bus_name: string) => { .hook(Mpris, (_, bus_name) => {
const players = self.attribute.players; const players = self.attribute.players;
if (players.has(bus_name)) { if (players.has(bus_name)) {
@ -136,11 +130,16 @@ export default () => {
} }
// Get the one on top so we can move it up later // Get the one on top so we can move it up later
const previousFirst = self.overlays.at(-1); const previousFirst = self.overlays.at(-1) as PlayerBox;
// Make the new player // Make the new player
const player = Mpris.getPlayer(bus_name); const player = Mpris.getPlayer(bus_name);
const Colors = Variable(null); const colorsVar = Variable({
imageAccent: '#6b4fa2',
buttonAccent: '#ecdcff',
buttonText: '#25005a',
hoverAccent: '#d4baff',
});
if (!player) { if (!player) {
return; return;
@ -148,14 +147,10 @@ export default () => {
players.set( players.set(
bus_name, bus_name,
PlayerBox( PlayerBox(player, colorsVar, self),
player,
Colors,
content.get_children()[0] as AgsOverlay,
),
); );
self.overlays = Array.from(players.values()) self.overlays = Array.from(players.values())
.map((widget) => widget) as Array<AgsBox>; .map((widget) => widget) as PlayerBox[];
const includes = self.attribute const includes = self.attribute
.includesWidget(previousFirst); .includesWidget(previousFirst);
@ -174,7 +169,7 @@ export default () => {
} }
}, 'player-added') }, 'player-added')
.hook(Mpris, (_: AgsOverlay, bus_name: string) => { .hook(Mpris, (_, bus_name) => {
const players = self.attribute.players; const players = self.attribute.players;
if (!bus_name || !players.has(bus_name)) { if (!bus_name || !players.has(bus_name)) {
@ -182,12 +177,12 @@ export default () => {
} }
// Get the one on top so we can move it up later // Get the one on top so we can move it up later
const previousFirst = self.overlays.at(-1); const previousFirst = self.overlays.at(-1) as PlayerBox;
// Remake overlays without deleted one // Remake overlays without deleted one
players.delete(bus_name); players.delete(bus_name);
self.overlays = Array.from(players.values()) self.overlays = Array.from(players.values())
.map((widget) => widget) as Array<AgsBox>; .map((widget) => widget) as PlayerBox[];
// Move previousFirst on top again // Move previousFirst on top again
const includes = self.attribute const includes = self.attribute

View file

@ -23,7 +23,7 @@ Audio.connect('speaker-changed', () => {
return; return;
} }
if (Audio.speaker.stream.is_muted) { if (Audio.speaker.stream?.is_muted) {
SpeakerIcon.value = speakerIcons[0]; SpeakerIcon.value = speakerIcons[0];
} }
else { else {
@ -43,7 +43,7 @@ Audio.connect('microphone-changed', () => {
return; return;
} }
if (Audio.microphone.stream.is_muted) { if (Audio.microphone.stream?.is_muted) {
MicIcon.value = micIcons[0]; MicIcon.value = micIcons[0];
} }
else { else {

View file

@ -1,13 +1,14 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
// Types // Types
import AgsWindow from 'types/widgets/window'; import { PopupWindow } from 'global-types';
export default () => { export default () => {
(Array.from(App.windows) as Array<[string, AgsWindow]>) (Array.from(App.windows) as Array<[string, PopupWindow]>)
.filter((w) => w[1].attribute?.close_on_unfocus && .filter((w) =>
w[1].attribute?.close_on_unfocus !== 'stay') w[1].attribute.close_on_unfocus &&
w[1].attribute.close_on_unfocus !== 'stay')
.forEach((w) => { .forEach((w) => {
App.closeWindow(w[0]); App.closeWindow(w[0]);
}); });

View file

@ -1,80 +1,68 @@
import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import { register } from 'resource:///com/github/Aylur/ags/widget.js';
import Gtk from 'gi://Gtk?version=3.0';
import Gdk from 'gi://Gdk?version=3.0';
import { EventBox } from 'resource:///com/github/Aylur/ags/widget.js'; // Types
import { BaseProps, Widget } from 'types/widgets/widget';
type EventHandler<Self> = (self: Self, event: Gdk.Event) => boolean | unknown;
const { Gtk, Gdk } = imports.gi; export type CursorBoxProps<
const display = Gdk.Display.get_default(); Child extends Gtk.Widget,
Attr = unknown,
Self = CursorBox<Child, Attr>,
> = BaseProps<Self, Gtk.EventBox.ConstructorProperties & {
child?: Child
on_hover?: EventHandler<Self>
on_hover_lost?: EventHandler<Self>
import * as EventBoxTypes from 'types/widgets/eventbox'; on_scroll_up?: EventHandler<Self>
type CursorBox = EventBoxTypes.EventBoxProps & { on_scroll_down?: EventHandler<Self>
on_primary_click_release?(self: EventBoxTypes.default): void;
on_hover?(self: EventBoxTypes.default): void; on_primary_click?: EventHandler<Self>
on_hover_lost?(self: EventBoxTypes.default): void; on_middle_click?: EventHandler<Self>
}; on_secondary_click?: EventHandler<Self>
on_primary_click_release?: EventHandler<Self>
on_middle_click_release?: EventHandler<Self>
on_secondary_click_release?: EventHandler<Self>
}, Attr>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export interface CursorBox<Child, Attr> extends Widget<Attr> { }
export default ({ export class CursorBox<Child extends Gtk.Widget, Attr> extends Gtk.EventBox {
on_primary_click_release = () => {/**/}, static {
on_hover = () => {/**/}, register(this, {
on_hover_lost = () => {/**/}, properties: {
attribute, 'on-clicked': ['jsobject', 'rw'],
...props
}: CursorBox) => {
// Make this variable to know if the function should
// be executed depending on where the click is released
const CanRun = Variable(true);
const Disabled = Variable(false);
const cursorBox = EventBox({ 'on-hover': ['jsobject', 'rw'],
...props, 'on-hover-lost': ['jsobject', 'rw'],
attribute: { 'on-scroll-up': ['jsobject', 'rw'],
...attribute, 'on-scroll-down': ['jsobject', 'rw'],
disabled: Disabled,
'on-primary-click': ['jsobject', 'rw'],
'on-secondary-click': ['jsobject', 'rw'],
'on-middle-click': ['jsobject', 'rw'],
'on-primary-click-release': ['jsobject', 'rw'],
'on-secondary-click-release': ['jsobject', 'rw'],
'on-middle-click-release': ['jsobject', 'rw'],
}, },
});
on_primary_click_release: (self) => {
// Every click, do a one shot connect to
// CanRun to wait for location of click
const id = CanRun.connect('changed', () => {
if (CanRun.value && !Disabled.value) {
on_primary_click_release(self);
} }
CanRun.disconnect(id); constructor(props: CursorBoxProps<Child, Attr> = {}) {
}); super(props as Gtk.EventBox.ConstructorProperties);
}, this.add_events(Gdk.EventMask.SCROLL_MASK);
this.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK);
// OnHover // Gesture stuff
}).on('enter-notify-event', (self) => { const gesture = Gtk.GestureLongPress.new(this);
on_hover(self);
if (!display) { this.hook(gesture, () => {
return;
}
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
Disabled.value ?
'not-allowed' :
'pointer',
));
self.toggleClassName('hover', true);
// OnHoverLost
}).on('leave-notify-event', (self) => {
on_hover_lost(self);
self.window.set_cursor(null);
self.toggleClassName('hover', false);
// Disabled class
}).hook(Disabled, (self) => {
self.toggleClassName('disabled', Disabled.value);
});
const gesture = Gtk.GestureLongPress.new(cursorBox);
cursorBox.hook(gesture, () => {
const pointer = gesture.get_point(null); const pointer = gesture.get_point(null);
const x = pointer[1]; const x = pointer[1];
const y = pointer[2]; const y = pointer[2];
@ -83,11 +71,198 @@ export default ({
return; return;
} }
CanRun.value = !( this.#canRun.value = !(
x > cursorBox.get_allocated_width() || x > this.get_allocated_width() ||
y > cursorBox.get_allocated_height() y > this.get_allocated_height()
); );
}, 'end'); }, 'end');
return cursorBox; this.connect('enter-notify-event', (_, event: Gdk.Event) => {
}; this.set_state_flags(Gtk.StateFlags.PRELIGHT, false);
if (!this.#display) {
return;
}
this.window.set_cursor(Gdk.Cursor.new_from_name(
this.#display,
this.#disabled.value ?
'not-allowed' :
'pointer',
));
return this.on_hover?.(this, event);
});
this.connect('leave-notify-event', (_, event: Gdk.Event) => {
this.unset_state_flags(Gtk.StateFlags.PRELIGHT);
this.window.set_cursor(null);
return this.on_hover_lost?.(this, event);
});
this.connect('button-press-event', (_, event: Gdk.Event) => {
this.set_state_flags(Gtk.StateFlags.ACTIVE, false);
if (this.#disabled.value) {
return;
}
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY) {
return this.on_primary_click?.(this, event);
}
else if (event.get_button()[1] === Gdk.BUTTON_MIDDLE) {
return this.on_middle_click?.(this, event);
}
else if (event.get_button()[1] === Gdk.BUTTON_SECONDARY) {
return this.on_secondary_click?.(this, event);
}
});
this.connect('button-release-event', (_, event: Gdk.Event) => {
this.unset_state_flags(Gtk.StateFlags.ACTIVE);
if (this.#disabled.value) {
return;
}
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY) {
// Every click, do a one shot connect to
// CanRun to wait for location of click
const id = this.#canRun.connect('changed', () => {
if (this.#canRun.value) {
this.on_primary_click_release?.(this, event);
}
this.#canRun.disconnect(id);
});
}
else if (event.get_button()[1] === Gdk.BUTTON_MIDDLE) {
return this.on_middle_click_release?.(this, event);
}
else if (event.get_button()[1] === Gdk.BUTTON_SECONDARY) {
return this.on_secondary_click_release?.(this, event);
}
});
this.connect('scroll-event', (_, event: Gdk.Event) => {
if (event.get_scroll_deltas()[2] < 0) {
return this.on_scroll_up?.(this, event);
}
else if (event.get_scroll_deltas()[2] > 0) {
return this.on_scroll_down?.(this, event);
}
});
}
#display = Gdk.Display.get_default();
// Make this variable to know if the function should
// be executed depending on where the click is released
#canRun = Variable(true);
#disabled = Variable(false);
get disabled() {
return this.#disabled.value;
}
set disabled(value: boolean) {
this.#disabled.value = value;
}
get child() {
return super.child as Child;
}
set child(child: Child) {
super.child = child;
}
get on_hover() {
return this._get('on-hover');
}
set on_hover(callback: EventHandler<this>) {
this._set('on-hover', callback);
}
get on_hover_lost() {
return this._get('on-hover-lost');
}
set on_hover_lost(callback: EventHandler<this>) {
this._set('on-hover-lost', callback);
}
get on_scroll_up() {
return this._get('on-scroll-up');
}
set on_scroll_up(callback: EventHandler<this>) {
this._set('on-scroll-up', callback);
}
get on_scroll_down() {
return this._get('on-scroll-down');
}
set on_scroll_down(callback: EventHandler<this>) {
this._set('on-scroll-down', callback);
}
get on_primary_click() {
return this._get('on-primary-click');
}
set on_primary_click(callback: EventHandler<this>) {
this._set('on-primary-click', callback);
}
get on_middle_click() {
return this._get('on-middle-click');
}
set on_middle_click(callback: EventHandler<this>) {
this._set('on-middle-click', callback);
}
get on_secondary_click() {
return this._get('on-secondary-click');
}
set on_secondary_click(callback: EventHandler<this>) {
this._set('on-secondary-click', callback);
}
get on_primary_click_release() {
return this._get('on-primary-click-release');
}
set on_primary_click_release(callback: EventHandler<this>) {
this._set('on-primary-click-release', callback);
}
get on_middle_click_release() {
return this._get('on-middle-click-release');
}
set on_middle_click_release(callback: EventHandler<this>) {
this._set('on-middle-click-release', callback);
}
get on_secondary_click_release() {
return this._get('on-secondary-click-release');
}
set on_secondary_click_release(callback: EventHandler<this>) {
this._set('on-secondary-click-release', callback);
}
}
export default <Child extends Gtk.Widget, Attr>(
props?: CursorBoxProps<Child, Attr>,
) => new CursorBox(props ?? {});

View file

@ -2,33 +2,61 @@ import App from 'resource:///com/github/Aylur/ags/app.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import { Box, Overlay, Window } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Overlay, register } from 'resource:///com/github/Aylur/ags/widget.js';
import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
// Types // Types
import { Window } from 'resource:///com/github/Aylur/ags/widgets/window.js';
import { Allocation, Widget } from 'types/@girs/gtk-3.0/gtk-3.0.cjs'; import { Allocation, Widget } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
import { RevealerProps } from 'types/widgets/revealer'; import { Variable as Var } from 'types/variable';
import { WindowProps } from 'types/widgets/window';
import AgsWindow from 'types/widgets/window'; import {
import AgsBox from 'types/widgets/box'; CloseType,
import AgsOverlay from 'types/widgets/overlay'; BoxGeneric,
import { Binding } from 'types/service'; OverlayGeneric,
type PopupWindow = WindowProps & { PopupChild,
transition?: RevealerProps['transition'] PopupWindowProps,
transition_duration?: number } from 'global-types';
bezier?: string
on_open?(self: AgsWindow): void
on_close?(self: AgsWindow): void
blur?: boolean
close_on_unfocus?: 'none' | 'stay' | 'released' | 'clicked'
anchor?: Array<string>
name: string
};
// FIXME: deal with overlay children? // FIXME: deal with overlay children?
// TODO: make this a new class to be able to edit props // TODO: make props changes affect the widget
export default ({
export class PopupWindow<
Child extends Widget,
Attr,
> extends Window<Child, Attr> {
static {
register(this, {
properties: {
content: ['widget', 'rw'],
},
});
}
#content: Var<Widget>;
#antiClip: Var<boolean>;
#needsAnticlipping: boolean;
#close_on_unfocus: CloseType;
get content() {
return this.#content.value;
}
set content(value: Widget) {
this.#content.value = value;
this.child.show_all();
}
get close_on_unfocus() {
return this.#close_on_unfocus;
}
set close_on_unfocus(value: 'none' | 'stay' | 'released' | 'clicked') {
this.#close_on_unfocus = value;
}
constructor({
transition = 'slide_down', transition = 'slide_down',
transition_duration = 800, transition_duration = 800,
bezier = 'cubic-bezier(0.68, -0.4, 0.32, 1.4)', bezier = 'cubic-bezier(0.68, -0.4, 0.32, 1.4)',
@ -37,82 +65,31 @@ export default ({
// Window props // Window props
name, name,
child = Box(),
visible = false, visible = false,
anchor = [], anchor = [],
layer = 'overlay', layer = 'overlay',
attribute,
content = Box(),
blur = false, blur = false,
close_on_unfocus = 'released', close_on_unfocus = 'released',
...props ...rest
}: PopupWindow) => { }: PopupWindowProps<Child, Attr>) {
const Child = Variable(child);
const AntiClip = Variable(false);
const needsAnticlipping = bezier.match(/-[0-9]/) !== null && const needsAnticlipping = bezier.match(/-[0-9]/) !== null &&
transition !== 'crossfade'; transition !== 'crossfade';
const contentVar = Variable(Box() as Widget);
const antiClip = Variable(false);
const attribute = { if (content) {
set_x_pos: ( contentVar.value = content;
alloc: Allocation,
side = 'right' as 'left' | 'right',
) => {
const window = App.getWindow(name) as AgsWindow;
if (!window) {
return;
} }
const width = window.get_display() super({
.get_monitor_at_point(alloc.x, alloc.y) ...rest,
.get_geometry().width;
window.margins = [
window.margins[0],
side === 'right' ?
(width - alloc.x - alloc.width) :
window.margins[1],
window.margins[2],
side === 'right' ?
window.margins[3] :
(alloc.x - alloc.width),
];
},
get_child: () => Child.value,
set_child: (new_child: Widget) => {
Child.value = new_child;
App.getWindow(name)?.child.show_all();
},
// This is for my custom pointers.ts
close_on_unfocus,
};
if (transition === 'none') {
return Window({
name, name,
layer, visible,
anchor, anchor,
visible: false,
...props,
attribute,
child: Child.bind(),
});
}
const window = Window({
name,
layer, layer,
anchor,
visible: false,
...props,
attribute, attribute,
setup: () => { setup: () => {
// Add way to make window open on startup // Add way to make window open on startup
const id = App.connect('config-parsed', () => { const id = App.connect('config-parsed', () => {
@ -128,7 +105,6 @@ export default ({
`keyword layerrule blur,${name}`); `keyword layerrule blur,${name}`);
} }
}, },
child: Overlay({ child: Overlay({
overlays: [Box({ overlays: [Box({
css: ` css: `
@ -177,7 +153,7 @@ export default ({
// so we can't change the order // so we can't change the order
if (anchor.length !== 0) { if (anchor.length !== 0) {
for (const ch of self.children) { for (const ch of self.children) {
if (ch !== Child.value) { if (ch !== contentVar.value) {
self.reorder_child(ch, position); self.reorder_child(ch, position);
return; return;
@ -186,7 +162,7 @@ export default ({
} }
}; };
self.hook(AntiClip, () => { self.hook(antiClip, () => {
if (transition === 'slide_down') { if (transition === 'slide_down') {
self.vertical = true; self.vertical = true;
reorder_child(-1); reorder_child(-1);
@ -207,7 +183,7 @@ export default ({
} }
}, },
children: Child.bind().transform((v) => { children: contentVar.bind().transform((v) => {
if (needsAnticlipping) { if (needsAnticlipping) {
return [ return [
// Add an anticlip widget when unanchored // Add an anticlip widget when unanchored
@ -218,7 +194,7 @@ export default ({
min-width: 100px; min-width: 100px;
padding: 2px; padding: 2px;
`, `,
visible: AntiClip.bind(), visible: antiClip.bind(),
}), }),
v, v,
Box({ Box({
@ -227,25 +203,25 @@ export default ({
min-width: 100px; min-width: 100px;
padding: 2px; padding: 2px;
`, `,
visible: AntiClip.bind(), visible: antiClip.bind(),
}), }),
]; ];
} }
else { else {
return [v]; return [v];
} }
}) as Binding<any, any, Widget[]>, }) as PopupChild,
})], })],
setup: (self) => { setup: (self) => {
self.on('get-child-position', (_, ch) => { self.on('get-child-position', (_, ch) => {
const overlay = (Child.value as Widget) const overlay = contentVar.value
.get_parent() as AgsOverlay; .get_parent() as OverlayGeneric;
if (ch === overlay) { if (ch === overlay) {
const alloc = overlay.get_allocation(); const alloc = overlay.get_allocation();
(self.child as AgsBox).css = ` (self.child as BoxGeneric).css = `
min-height: ${alloc.height}px; min-height: ${alloc.height}px;
min-width: ${alloc.width}px; min-width: ${alloc.width}px;
`; `;
@ -265,23 +241,24 @@ export default ({
self.hook(App, (_, currentName, isOpen) => { self.hook(App, (_, currentName, isOpen) => {
if (currentName === name) { if (currentName === name) {
const overlay = (Child.value as Widget) const overlay = contentVar.value
.get_parent() as AgsOverlay; .get_parent() as OverlayGeneric;
const alloc = overlay.get_allocation(); const alloc = overlay.get_allocation();
const height = needsAnticlipping ? const height = antiClip ?
alloc.height + 100 + 10 : alloc.height + 100 + 10 :
alloc.height + 10; alloc.height + 10;
if (needsAnticlipping) { if (needsAnticlipping) {
AntiClip.value = true; antiClip.value = true;
const thisTimeout = timeout( const thisTimeout = timeout(
transition_duration, transition_duration,
() => { () => {
// Only run the timeout if there isn't a newer timeout // Only run the timeout if there isn't a newer timeout
if (thisTimeout === currentTimeout) { if (thisTimeout ===
AntiClip.value = false; currentTimeout) {
antiClip.value = false;
} }
}, },
); );
@ -342,7 +319,7 @@ export default ({
} }
if (isOpen) { if (isOpen) {
on_open(window); on_open(this);
// To get the animation, we need to set the css // To get the animation, we need to set the css
// to hide the widget and then timeout to have // to hide the widget and then timeout to have
@ -351,16 +328,18 @@ export default ({
timeout(10, () => { timeout(10, () => {
overlay.css = ` overlay.css = `
transition: margin transition: margin
${transition_duration}ms ${bezier}, ${transition_duration}ms
${bezier},
opacity opacity
${transition_duration}ms ${bezier}; ${transition_duration}ms
${bezier};
`; `;
}); });
} }
else { else {
timeout(transition_duration, () => { timeout(transition_duration, () => {
on_close(window); on_close(this);
}); });
overlay.css = `${css} overlay.css = `${css}
@ -378,5 +357,36 @@ export default ({
}), }),
}); });
return window; this.#content = contentVar;
}; this.#close_on_unfocus = close_on_unfocus;
this.#needsAnticlipping = needsAnticlipping;
this.#antiClip = antiClip;
}
set_x_pos(
alloc: Allocation,
side = 'right' as 'left' | 'right',
) {
const width = this.get_display()
.get_monitor_at_point(alloc.x, alloc.y)
.get_geometry().width;
this.margins = [
this.margins[0],
side === 'right' ?
(width - alloc.x - alloc.width) :
this.margins[1],
this.margins[2],
side === 'right' ?
this.margins[3] :
(alloc.x - alloc.width),
];
}
}
export default <Child extends Widget, Attr>(
props: PopupWindowProps<Child, Attr>,
) => new PopupWindow(props);

View file

@ -13,13 +13,19 @@ import CursorBox from '../misc/cursorbox.ts';
// Types // Types
import { Notification as NotifObj } from 'types/service/notifications.ts'; import { Notification as NotifObj } from 'types/service/notifications.ts';
import AgsEventBox from 'types/widgets/eventbox.ts'; import AgsEventBox from 'types/widgets/eventbox';
import { Widget } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
import { Client } from 'types/service/hyprland.ts'; import { Client } from 'types/service/hyprland.ts';
type NotificationWidget = { type NotificationWidget = {
notif: NotifObj notif: NotifObj
slideIn?: 'Left' | 'Right' slideIn?: 'Left' | 'Right'
command?(): void command?(): void
}; };
import {
EventBoxGeneric,
CursorBox as CBox,
} from 'global-types';
const setTime = (time: number) => { const setTime = (time: number) => {
return GLib.DateTime return GLib.DateTime
@ -27,13 +33,17 @@ const setTime = (time: number) => {
.format('%H:%M'); .format('%H:%M');
}; };
const getDragState = (box: AgsEventBox) => (box.get_parent()?.get_parent() const getDragState = (box: EventBoxGeneric) => (box
?.get_parent()?.get_parent()?.get_parent() as AgsEventBox) .get_parent()
?.get_parent()
?.get_parent()
?.get_parent()
?.get_parent() as AgsEventBox<Widget, { dragging: boolean }>)
?.attribute.dragging; ?.attribute.dragging;
const NotificationIcon = (notif: NotifObj) => { const NotificationIcon = (notif: NotifObj) => {
let iconCmd = (box: AgsEventBox):void => { let iconCmd = (box: CBox):void => {
console.log(box); console.log(box);
}; };
@ -89,7 +99,9 @@ const NotificationIcon = (notif: NotifObj) => {
if (notif.image) { if (notif.image) {
return CursorBox({ return CursorBox({
on_primary_click_release: iconCmd, on_primary_click_release: (self) => {
iconCmd(self);
},
child: Box({ child: Box({
vpack: 'start', vpack: 'start',
@ -120,7 +132,9 @@ const NotificationIcon = (notif: NotifObj) => {
return CursorBox({ return CursorBox({
on_primary_click_release: iconCmd, on_primary_click_release: (self) => {
iconCmd(self);
},
child: Box({ child: Box({
vpack: 'start', vpack: 'start',
@ -175,7 +189,7 @@ export const Notification = ({
}); });
// Add body to notif // Add body to notif
(notifWidget.child as AgsEventBox).add(Box({ (notifWidget.child as EventBoxGeneric).add(Box({
class_name: `notification ${notif.urgency}`, class_name: `notification ${notif.urgency}`,
vexpand: false, vexpand: false,

View file

@ -21,5 +21,5 @@ export const NotifCenter = () => PopupWindow({
transition: 'slide_up', transition: 'slide_up',
monitor: 1, monitor: 1,
child: NotifCenterWidget(), content: NotifCenterWidget(),
}); });

View file

@ -8,11 +8,11 @@ import { Notification, HasNotifs } from './base.ts';
import CursorBox from '../misc/cursorbox.ts'; import CursorBox from '../misc/cursorbox.ts';
// Types // Types
import AgsBox from 'types/widgets/box.ts';
import { Notification as NotifObj } from 'resource:///com/github/Aylur/ags/service/notifications.js'; import { Notification as NotifObj } from 'resource:///com/github/Aylur/ags/service/notifications.js';
import { BoxGeneric } from 'global-types';
const addNotif = (box: AgsBox, notif: NotifObj) => { const addNotif = (box: BoxGeneric, notif: NotifObj) => {
if (notif) { if (notif) {
const NewNotif = Notification({ const NewNotif = Notification({
notif, notif,
@ -53,7 +53,7 @@ const NotificationList = () => Box({
}, 'notified') }, 'notified')
.hook(Notifications, (box, id) => { .hook(Notifications, (box, id) => {
const notif = (box.children as Array<AgsBox>) const notif = (box.children as BoxGeneric[])
.find((ch) => ch.attribute.id === id); .find((ch) => ch.attribute.id === id);
if (notif?.sensitive) { if (notif?.sensitive) {
@ -73,7 +73,7 @@ const ClearButton = () => CursorBox({
setup: (self) => { setup: (self) => {
self.hook(HasNotifs, () => { self.hook(HasNotifs, () => {
self.attribute.disabled?.setValue(!HasNotifs.value); self.disabled = !HasNotifs.value;
}); });
}, },

View file

@ -18,5 +18,5 @@ export const NotifCenter = () => PopupWindow({
anchor: ['top', 'right'], anchor: ['top', 'right'],
margins: [TOP_MARGIN, 0, 0, 0], margins: [TOP_MARGIN, 0, 0, 0],
child: NotifCenterWidget(), content: NotifCenterWidget(),
}); });

View file

@ -61,5 +61,5 @@ export default () => PopupWindow({
transition: 'slide_up', transition: 'slide_up',
transition_duration, transition_duration,
bezier: 'ease', bezier: 'ease',
child: OSDs(), content: OSDs(),
}); });

View file

@ -44,5 +44,5 @@ const PowermenuWidget = () => CenterBox({
export default () => PopupWindow({ export default () => PopupWindow({
name: 'powermenu', name: 'powermenu',
child: PowermenuWidget(), content: PowermenuWidget(),
}); });

View file

@ -54,5 +54,5 @@ export default () => PopupWindow({
name: 'quick-settings', name: 'quick-settings',
anchor: ['top', 'right'], anchor: ['top', 'right'],
margins: [TOP_MARGIN, 0, 0, 0], margins: [TOP_MARGIN, 0, 0, 0],
child: QuickSettingsWidget(), content: QuickSettingsWidget(),
}); });

View file

@ -10,7 +10,10 @@
"strict": true, "strict": true,
"noImplicitAny": false, "noImplicitAny": false,
"baseUrl": ".", "baseUrl": ".",
"typeRoots": ["./types"], "typeRoots": [
"./types",
"./global-types.d.ts"
],
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
} }