From 9c64b002432948899a443ae88c901ab7fee942c3 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Mon, 29 Jan 2024 18:54:07 -0500 Subject: [PATCH] refactor(ags): start update types and use classes for cbox and popup --- modules/ags/config/.eslintrc.json | 6 +- modules/ags/config/global-types.d.ts | 129 ++++ .../binto-widgets/notification-center.scss | 2 +- .../scss/wim-widgets/notification-center.scss | 2 +- .../config/scss/wim-widgets/traybuttons.scss | 2 +- modules/ags/config/services/pointers.ts | 24 +- modules/ags/config/ts/applauncher/main.ts | 42 +- modules/ags/config/ts/bar/fullscreen.ts | 8 +- .../config/ts/bar/hovers/keyboard-layout.ts | 19 +- .../ags/config/ts/bar/items/notif-button.ts | 6 +- .../ags/config/ts/bar/items/quick-settings.ts | 18 +- modules/ags/config/ts/bar/items/systray.ts | 3 +- modules/ags/config/ts/bar/items/workspaces.ts | 71 +- .../ags/config/ts/corners/screen-corners.ts | 5 +- modules/ags/config/ts/date.ts | 2 +- modules/ags/config/ts/media-player/gesture.ts | 28 +- modules/ags/config/ts/media-player/mpris.ts | 63 +- modules/ags/config/ts/media-player/player.ts | 55 +- modules/ags/config/ts/misc/audio-icons.ts | 4 +- modules/ags/config/ts/misc/closer.ts | 9 +- modules/ags/config/ts/misc/cursorbox.ts | 319 +++++++-- modules/ags/config/ts/misc/popup.ts | 656 +++++++++--------- modules/ags/config/ts/notifications/base.ts | 28 +- modules/ags/config/ts/notifications/binto.ts | 2 +- modules/ags/config/ts/notifications/center.ts | 8 +- modules/ags/config/ts/notifications/wim.ts | 2 +- modules/ags/config/ts/osd/main.ts | 2 +- modules/ags/config/ts/powermenu.ts | 2 +- modules/ags/config/ts/quick-settings/main.ts | 2 +- modules/ags/config/tsconfig.json | 5 +- 30 files changed, 914 insertions(+), 610 deletions(-) create mode 100644 modules/ags/config/global-types.d.ts diff --git a/modules/ags/config/.eslintrc.json b/modules/ags/config/.eslintrc.json index 4c1d53aa..2871a3ed 100644 --- a/modules/ags/config/.eslintrc.json +++ b/modules/ags/config/.eslintrc.json @@ -2,7 +2,7 @@ "env": { "es2021": true }, - "extends": "eslint:recommended", + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], "parser": "@typescript-eslint/parser", "overrides": [], "parserOptions": { @@ -19,9 +19,6 @@ "no-unreachable-loop": ["error", { "ignore": [ "ForInStatement", "ForOfStatement" ]}], - "no-use-before-define": ["error", { - "functions": false - }], "block-scoped-var": ["error"], @@ -79,6 +76,7 @@ "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-unsafe-declaration-merging": "off", "@stylistic/array-bracket-newline": ["warn", "consistent"], "@stylistic/array-bracket-spacing": ["warn", "never"], diff --git a/modules/ags/config/global-types.d.ts b/modules/ags/config/global-types.d.ts new file mode 100644 index 00000000..b4ad0859 --- /dev/null +++ b/modules/ags/config/global-types.d.ts @@ -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 & Widget; + +// For ./ts/applauncher/main.ts +import { Application } from 'types/service/applications.ts'; +import { CursorBoxProps } from 'ts/misc/cursorbox'; +export type AgsAppItem = AgsEventBox>; + +// 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; + +// For ./ts/bar/fullscreen.ts +export type DefaultProps = RevealerProps; + +// For ./ts/media-player/gesture.ts +export type Gesture = { + attribute?: object + setup?(self: OverlayGeneric): void + props?: OverlayProps +}; + +// 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; +export type PlayerButtonType = { + player: MprisPlayer + colors: Var + 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, +'is_listening' | 'is_polling' | 'value', +Widget[] +>; +export type CloseType = 'none' | 'stay' | 'released' | 'clicked'; +export type PopupWindowProps = +WindowProps & { + transition?: RevealerProps['transition'] + transition_duration?: number + bezier?: string + on_open?(self: AgsWindow): void + on_close?(self: AgsWindow): 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; + +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; + +import AgsLabel from 'types/widgets/label.ts'; +export type LabelGeneric = AgsLabel; + +import AgsOverlay, { OverlayProps } from 'types/widgets/overlay.ts'; +export type OverlayGeneric = AgsOverlay; + +import AgsRevealer, { RevealerProps } from 'types/widgets/revealer.ts'; +export type RevealerGeneric = AgsRevealer; + +import AgsStack from 'types/widgets/stack.ts'; +export type StackGeneric = AgsStack; diff --git a/modules/ags/config/scss/binto-widgets/notification-center.scss b/modules/ags/config/scss/binto-widgets/notification-center.scss index 7515f8ed..1d5ebb6e 100644 --- a/modules/ags/config/scss/binto-widgets/notification-center.scss +++ b/modules/ags/config/scss/binto-widgets/notification-center.scss @@ -27,7 +27,7 @@ padding: 4.5px 9px; } - &.hover box { + &:hover box { box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); background-color: rgba(238, 238, 238, 0.154); color: #f1f1f1; diff --git a/modules/ags/config/scss/wim-widgets/notification-center.scss b/modules/ags/config/scss/wim-widgets/notification-center.scss index 0b51f73d..262c77c4 100644 --- a/modules/ags/config/scss/wim-widgets/notification-center.scss +++ b/modules/ags/config/scss/wim-widgets/notification-center.scss @@ -31,7 +31,7 @@ padding: 4.5px 9px; } - &.hover box { + &:hover box { box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); background-color: rgba(238, 238, 238, 0.154); color: #f1f1f1; diff --git a/modules/ags/config/scss/wim-widgets/traybuttons.scss b/modules/ags/config/scss/wim-widgets/traybuttons.scss index 64a16557..d0847db3 100644 --- a/modules/ags/config/scss/wim-widgets/traybuttons.scss +++ b/modules/ags/config/scss/wim-widgets/traybuttons.scss @@ -48,7 +48,7 @@ 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); transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; diff --git a/modules/ags/config/services/pointers.ts b/modules/ags/config/services/pointers.ts index 8ec96a2b..653e614e 100644 --- a/modules/ags/config/services/pointers.ts +++ b/modules/ags/config/services/pointers.ts @@ -15,7 +15,7 @@ const ON_CLICK_TRIGGERS = [ ]; // Types -import AgsWindow from 'types/widgets/window'; +import { PopupWindow } from 'global-types'; import { Subprocess } from 'types/@girs/gio-2.0/gio-2.0.cjs'; type Layer = { address: string; @@ -54,7 +54,7 @@ class Pointers extends Service { #process = null as Subprocess | null; #lastLine = ''; - #pointers = [] as Array; + #pointers = [] as Array; get process() { return this.#process; @@ -114,11 +114,11 @@ class Pointers extends Service { #initAppConnection() { App.connect('window-toggled', () => { const anyVisibleAndClosable = - (Array.from(App.windows) as Array<[string, AgsWindow]>) + (Array.from(App.windows) as Array<[string, PopupWindow]>) .some((w) => { - const closable = w[1].attribute?.close_on_unfocus && - !(w[1].attribute.close_on_unfocus === 'none' || - w[1].attribute.close_on_unfocus === 'stay'); + const closable = w[1].close_on_unfocus && + !(w[1].close_on_unfocus === 'none' || + w[1].close_on_unfocus === 'stay'); return w[1].visible && closable; }); @@ -134,10 +134,10 @@ class Pointers extends Service { } 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) => { - const closable = (w[1].attribute?.close_on_unfocus && - w[1].attribute.close_on_unfocus === clickStage); + const closable = (w[1].close_on_unfocus && + w[1].close_on_unfocus === clickStage); return w[1].visible && closable; }); @@ -190,11 +190,11 @@ class Pointers extends Service { const widgets = overlayLayer.filter((n) => { const window = - (App.getWindow(n.namespace) as AgsWindow); + (App.getWindow(n.namespace) as PopupWindow); return window && - window.attribute?.close_on_unfocus && - window.attribute?.close_on_unfocus === + window.close_on_unfocus && + window.close_on_unfocus === clickStage; }); diff --git a/modules/ags/config/ts/applauncher/main.ts b/modules/ags/config/ts/applauncher/main.ts index f23759bf..15db7e2a 100644 --- a/modules/ags/config/ts/applauncher/main.ts +++ b/modules/ags/config/ts/applauncher/main.ts @@ -1,8 +1,7 @@ import App from 'resource:///com/github/Aylur/ags/app.js'; import Applications from 'resource:///com/github/Aylur/ags/service/applications.js'; -// FIXME: find cleaner way to import this -// @ts-expect-error -import { Fzf } from 'file:///home/matt/.nix/modules/ags/config/node_modules/fzf/dist/fzf.es.js'; +// @ts-expect-error find cleaner way to import this +import { Fzf, FzfResultItem } 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'; @@ -10,14 +9,13 @@ import PopupWindow from '../misc/popup.ts'; import AppItem from './app-item.ts'; // Types -import { Application } from 'types/service/applications.ts'; 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') => { - let fzfResults: Array; - // @ts-expect-error + let fzfResults: FzfResultItem[]; const list = ListBox(); const setSort = (text: string) => { @@ -31,25 +29,21 @@ const Applauncher = (window_name = 'applauncher') => { }); fzfResults = fzf.find(text); - list.set_sort_func( - (a: ListBoxRow, b: ListBoxRow) => { - const row1 = (a.get_children()[0] as AgsEventBox) - ?.attribute.app.name; - const row2 = (b.get_children()[0] as AgsEventBox) - ?.attribute.app.name; + list.set_sort_func((a, b) => { + const row1 = (a.get_children()[0] as AgsAppItem).attribute.app.name; + const row2 = (b.get_children()[0] as AgsAppItem).attribute.app.name; - if (!row1 || !row2) { - return 0; - } + if (!row1 || !row2) { + return 0; + } - return fzfResults.indexOf(row1) - + return fzfResults.indexOf(row1) - fzfResults.indexOf(row1) || 0; - }, - ); + }); }; const makeNewChildren = () => { - const rows = list.get_children() as Array; + const rows = list.get_children() as ListBoxRow[]; rows.forEach((ch) => { ch.destroy(); @@ -94,14 +88,14 @@ const Applauncher = (window_name = 'applauncher') => { setSort(text); let visibleApps = 0; - const rows = list.get_children() as Array; + const rows = list.get_children() as ListBoxRow[]; rows.forEach((row) => { 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) => { return r.item.name === item.attribute.app.name; }); @@ -162,5 +156,5 @@ const Applauncher = (window_name = 'applauncher') => { export default () => PopupWindow({ name: 'applauncher', keymode: 'on-demand', - child: Applauncher(), + content: Applauncher(), }); diff --git a/modules/ags/config/ts/bar/fullscreen.ts b/modules/ags/config/ts/bar/fullscreen.ts index 84f3e56f..1d4e8435 100644 --- a/modules/ags/config/ts/bar/fullscreen.ts +++ b/modules/ags/config/ts/bar/fullscreen.ts @@ -5,8 +5,8 @@ import { Box, EventBox, Revealer, Window } from 'resource:///com/github/Aylur/ag // Types import { Variable as Var } from 'types/variable'; -import AgsBox from 'types/widgets/box'; -import { RevealerProps } from 'types/widgets/revealer'; +import { BoxGeneric, DefaultProps } from 'global-types'; + const BarCloser = (variable: Var) => Window({ name: 'bar-closer', @@ -30,7 +30,7 @@ const BarCloser = (variable: Var) => Window({ }), }); -export default (props: RevealerProps) => { +export default (props?: DefaultProps) => { const Revealed = Variable(true); 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; }; diff --git a/modules/ags/config/ts/bar/hovers/keyboard-layout.ts b/modules/ags/config/ts/bar/hovers/keyboard-layout.ts index aa7d6415..c950ed87 100644 --- a/modules/ags/config/ts/bar/hovers/keyboard-layout.ts +++ b/modules/ags/config/ts/bar/hovers/keyboard-layout.ts @@ -6,22 +6,11 @@ import HoverRevealer from './hover-revealer.ts'; const DEFAULT_KB = 'at-translated-set-2-keyboard'; -import AgsLabel from 'types/widgets/label.ts'; -type Keyboard = { - address: string; - name: string; - rules: string; - model: string; - layout: string; - variant: string; - options: string; - active_keymap: string; - main: boolean; -}; +// Types +import { Keyboard, LabelGeneric } from 'global-types'; - -const getKbdLayout = (self: AgsLabel, _: string, layout: string) => { +const getKbdLayout = (self: LabelGeneric, _: string, layout: string) => { if (layout) { if (layout === 'error') { return; @@ -35,7 +24,7 @@ const getKbdLayout = (self: AgsLabel, _: string, layout: string) => { // At launch, kb layout is undefined Hyprland.sendMessage('j/devices').then((obj) => { const keyboards = Array.from(JSON.parse(obj) - .keyboards) as Array; + .keyboards) as Keyboard[]; const kb = keyboards.find((v) => v.name === DEFAULT_KB); if (kb) { diff --git a/modules/ags/config/ts/bar/items/notif-button.ts b/modules/ags/config/ts/bar/items/notif-button.ts index 57ab658c..5a438dc9 100644 --- a/modules/ags/config/ts/bar/items/notif-button.ts +++ b/modules/ags/config/ts/bar/items/notif-button.ts @@ -9,15 +9,15 @@ import Separator from '../../misc/separator.ts'; const SPACING = 4; // Types -import AgsWindow from 'types/widgets/window.ts'; +import { PopupWindow } from 'global-types'; export default () => CursorBox({ class_name: 'toggle-off', on_primary_click_release: (self) => { - (App.getWindow('notification-center') as AgsWindow) - ?.attribute.set_x_pos( + (App.getWindow('notification-center') as PopupWindow) + .set_x_pos( self.get_allocation(), 'right', ); diff --git a/modules/ags/config/ts/bar/items/quick-settings.ts b/modules/ags/config/ts/bar/items/quick-settings.ts index d5737a15..74fb9e1d 100644 --- a/modules/ags/config/ts/bar/items/quick-settings.ts +++ b/modules/ags/config/ts/bar/items/quick-settings.ts @@ -14,9 +14,7 @@ import Separator from '../../misc/separator.ts'; const SPACING = 4; // Types -import AgsRevealer from 'types/widgets/revealer.ts'; -import AgsBox from 'types/widgets/box.ts'; -import AgsWindow from 'types/widgets/window.ts'; +import { PopupWindow } from 'global-types'; export default () => { @@ -36,8 +34,8 @@ export default () => { class_name: 'toggle-off', on_primary_click_release: (self) => { - (App.getWindow('quick-settings') as AgsWindow) - ?.attribute.set_x_pos( + (App.getWindow('quick-settings') as PopupWindow) + .set_x_pos( self.get_allocation(), 'right', ); @@ -55,17 +53,15 @@ export default () => { attribute: { hoverRevealers: hoverRevealers.map((rev) => { - const box = rev.child as AgsBox; + const box = rev.child; return box.children[1]; }), }, on_hover_lost: (self) => { - self.attribute.hoverRevealers.forEach( - (rev: AgsRevealer) => { - rev.reveal_child = false; - }, - ); + self.attribute.hoverRevealers.forEach((rev) => { + rev.reveal_child = false; + }); }, child: Box({ diff --git a/modules/ags/config/ts/bar/items/systray.ts b/modules/ags/config/ts/bar/items/systray.ts index d54aae43..8e740e53 100644 --- a/modules/ags/config/ts/bar/items/systray.ts +++ b/modules/ags/config/ts/bar/items/systray.ts @@ -10,7 +10,6 @@ const SPACING = 12; // Types import { TrayItem } from 'types/service/systemtray.ts'; -import AgsRevealer from 'types/widgets/revealer.ts'; const SysTrayItem = (item: TrayItem) => { @@ -54,7 +53,7 @@ const SysTray = () => MenuBar({ self.child = w; self.show_all(); - ( w.child).reveal_child = true; + w.child.reveal_child = true; }, 'added') .hook(SystemTray, (_, id) => { diff --git a/modules/ags/config/ts/bar/items/workspaces.ts b/modules/ags/config/ts/bar/items/workspaces.ts index 1e140137..86861529 100644 --- a/modules/ags/config/ts/bar/items/workspaces.ts +++ b/modules/ags/config/ts/bar/items/workspaces.ts @@ -8,10 +8,13 @@ import CursorBox from '../../misc/cursorbox.ts'; const URGENT_DURATION = 1000; // Types -import AgsBox from 'types/widgets/box.ts'; -import AgsRevealer from 'types/widgets/revealer.ts'; -import AgsOverlay from 'types/widgets/overlay.ts'; -import AgsEventBox from 'types/widgets/eventbox.ts'; +import { + BoxGeneric, + EventBoxGeneric, + OverlayGeneric, + RevealerGeneric, + Workspace, +} from 'global-types'; const Workspace = ({ id }: { id: number }) => { @@ -31,7 +34,10 @@ const Workspace = ({ id }: { id: number }) => { class_name: 'button', setup: (self) => { - const update = (_: AgsBox, addr: string | undefined) => { + const update = ( + _: BoxGeneric, + addr: string | undefined, + ) => { const workspace = Hyprland.getWorkspace(id); const occupied = workspace && workspace.windows > 0; @@ -79,13 +85,13 @@ export default () => { const L_PADDING = 16; const WS_WIDTH = 30; - const updateHighlight = (self: AgsBox) => { + const updateHighlight = (self: BoxGeneric) => { const currentId = Hyprland.active.workspace.id; - const indicators = (((self.get_parent() as AgsOverlay) - .child as AgsEventBox) - .child as AgsBox) - .children as Array; + const indicators = (((self.get_parent() as OverlayGeneric) + .child as EventBoxGeneric) + .child as BoxGeneric) + .children as Workspace[]; const currentIndex = indicators .findIndex((w) => w.attribute.id === currentId); @@ -111,26 +117,26 @@ export default () => { child: Box({ class_name: 'workspaces', - attribute: { workspaces: [] }, + attribute: { workspaces: [] as Workspace[] }, setup: (self) => { - const workspaces = (): Array => - self.attribute.workspaces; - const refresh = () => { - (self.children as Array).forEach((rev) => { - rev.reveal_child = false; - }); + (self.children as RevealerGeneric[]) + .forEach((rev) => { + rev.reveal_child = false; + }); - workspaces().forEach((ws) => { - ws.reveal_child = true; - }); + self.attribute.workspaces + .forEach((ws) => { + ws.reveal_child = true; + }); }; const updateWorkspaces = () => { Hyprland.workspaces.forEach((ws) => { - const currentWs = (self.children as Array) - .find((ch) => ch.attribute.id === ws.id); + const currentWs = + (self.children as Workspace[]) + .find((ch) => ch.attribute.id === ws.id); if (!currentWs && ws.id > 0) { self.add(Workspace({ id: ws.id })); @@ -139,21 +145,22 @@ export default () => { self.show_all(); // Make sure the order is correct - workspaces().forEach((workspace, i) => { - ( workspace.get_parent()).reorder_child( - workspace, - i, - ); + self.attribute.workspaces.forEach((workspace, i) => { + (workspace.get_parent() as BoxGeneric) + .reorder_child(workspace, i); }); }; self.hook(Hyprland, () => { self.attribute.workspaces = - (self.children as Array).filter((ch) => { - return Hyprland.workspaces.find((ws) => { - return ws.id === ch.attribute.id; - }); - }).sort((a, b) => a.attribute.id - b.attribute.id); + (self.children as Workspace[]) + .filter((ch) => { + return Hyprland.workspaces.find((ws) => { + return ws.id === ch.attribute.id; + }); + }) + .sort((a, b) => + a.attribute.id - b.attribute.id); updateWorkspaces(); refresh(); diff --git a/modules/ags/config/ts/corners/screen-corners.ts b/modules/ags/config/ts/corners/screen-corners.ts index 171fb1f6..98b02686 100644 --- a/modules/ags/config/ts/corners/screen-corners.ts +++ b/modules/ags/config/ts/corners/screen-corners.ts @@ -26,10 +26,7 @@ export default ( .get_property('border-radius', Gtk.StateFlags.NORMAL); widget.set_size_request(r, r); - widget.connect('draw', (_, context) => { - // FIXME: get proper Context type - const cr = context as any; - + widget.connect('draw', (_, cr) => { const c = widget.get_style_context() .get_property('background-color', Gtk.StateFlags.NORMAL); diff --git a/modules/ags/config/ts/date.ts b/modules/ags/config/ts/date.ts index 51fc4219..9008f977 100644 --- a/modules/ags/config/ts/date.ts +++ b/modules/ags/config/ts/date.ts @@ -90,7 +90,7 @@ export default () => PopupWindow({ anchor: ['top'], margins: [TOP_MARGIN, 0, 0, 0], - child: Box({ + content: Box({ class_name: 'date', vertical: true, diff --git a/modules/ags/config/ts/media-player/gesture.ts b/modules/ags/config/ts/media-player/gesture.ts index 269fc4e7..38b89514 100644 --- a/modules/ags/config/ts/media-player/gesture.ts +++ b/modules/ags/config/ts/media-player/gesture.ts @@ -10,20 +10,15 @@ const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease, opacity ${ANIM_DURATION}ms ease;`; // Types -import AgsOverlay from 'types/widgets/overlay'; -import OverlayProps from 'types/widgets/overlay'; -import AgsBox from 'types/widgets/box'; -import AgsCenterBox from 'types/widgets/centerbox'; -import { Connectable } from 'types/widgets/widget'; -type Gesture = { - attribute?: Object - setup?(self: Connectable & AgsOverlay): void - props?: OverlayProps -}; +import { + CenterBoxGeneric, + Gesture, + OverlayGeneric, + PlayerBox, +} from 'global-types'; export default ({ - attribute = {}, setup = () => {/**/}, ...props }: Gesture) => { @@ -39,10 +34,11 @@ export default ({ const content = Overlay({ ...props, attribute: { - ...attribute, + players: new Map(), + setup: false, dragging: false, - includesWidget: (playerW: AgsOverlay) => { + includesWidget: (playerW: OverlayGeneric) => { return content.overlays.find((w) => w === playerW); }, @@ -50,7 +46,7 @@ export default ({ over.visible = over === content.overlays.at(-1); }), - moveToTop: (player: AgsCenterBox) => { + moveToTop: (player: CenterBoxGeneric) => { player.visible = true; content.reorder_overlay(player, -1); timeout(ANIM_DURATION, () => { @@ -82,7 +78,7 @@ export default ({ self.attribute.dragging = true; let offset = gesture.get_offset()[1]; - const playerBox = self.overlays.at(-1) as AgsBox; + const playerBox = self.overlays.at(-1) as PlayerBox; if (!offset) { return; @@ -118,7 +114,7 @@ export default ({ self.attribute.dragging = false; 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 (offset && Math.abs(offset) > MAX_OFFSET) { diff --git a/modules/ags/config/ts/media-player/mpris.ts b/modules/ags/config/ts/media-player/mpris.ts index 7993b7b4..512f938a 100644 --- a/modules/ags/config/ts/media-player/mpris.ts +++ b/modules/ags/config/ts/media-player/mpris.ts @@ -31,17 +31,23 @@ const icons = { // Types import { MprisPlayer } from 'types/service/mpris.ts'; import { Variable as Var } from 'types/variable'; -import AgsOverlay from 'types/widgets/overlay.ts'; -import AgsCenterBox, { CenterBoxProps } from 'types/widgets/centerbox.ts'; -import AgsLabel from 'types/widgets/label.ts'; -import AgsIcon from 'types/widgets/icon.ts'; -import AgsStack from 'types/widgets/stack.ts'; + +import { + AgsWidget, + CenterBoxPropsGeneric, + Colors, + PlayerBox, + PlayerButtonType, + PlayerDrag, + PlayerOverlay, + StackGeneric, +} from 'global-types'; export const CoverArt = ( player: MprisPlayer, - colors: Var, - props: CenterBoxProps, + colors: Var, + props: CenterBoxPropsGeneric, ) => CenterBox({ ...props, vertical: true, @@ -51,7 +57,7 @@ export const CoverArt = ( player, }, - setup: (self) => { + setup: (self: PlayerBox) => { // Give temp cover art readFileAsync(player.cover_path).catch(() => { if (!colors.value && !player.track_cover_url) { @@ -93,7 +99,7 @@ export const CoverArt = ( background-position: center; `; - if (!(self.get_parent() as AgsCenterBox) + if (!(self.get_parent() as PlayerDrag) .attribute.dragging) { 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 = ( p: MprisPlayer, - widget?: AgsOverlay, - over?: AgsOverlay, + widget?: PlayerOverlay, + playerBox?: PlayerBox, ) => CursorBox({ tooltip_text: p.identity || '', on_primary_click_release: () => { - widget?.attribute.moveToTop(over); + if (widget && playerBox) { + widget.attribute.moveToTop(playerBox); + } }, child: Icon({ @@ -153,7 +161,7 @@ export const PlayerIcon = (player: MprisPlayer, overlay: AgsOverlay) => { }); return Box().hook(Mpris, (self) => { - const grandPa = self.get_parent()?.get_parent(); + const grandPa = self.get_parent()?.get_parent() as AgsWidget; if (!grandPa) { return; @@ -162,13 +170,13 @@ export const PlayerIcon = (player: MprisPlayer, overlay: AgsOverlay) => { const thisIndex = overlay.overlays .indexOf(grandPa); - self.children = (overlay.overlays as Array) - .map((over, i) => { + self.children = (overlay.overlays as PlayerBox[]) + .map((playerBox, i) => { self.children.push(Separator(2)); return i === thisIndex ? playerIcon(player) : - playerIcon(over.attribute.player, overlay, over); + playerIcon(playerBox.attribute.player, overlay, playerBox); }) .reverse(); }); @@ -179,7 +187,7 @@ const display = Gdk.Display.get_default(); export const PositionSlider = ( player: MprisPlayer, - colors: Var, + colors: Var, ) => Slider({ class_name: 'position-slider', vpack: 'center', @@ -258,13 +266,6 @@ export const PositionSlider = ( }, }); -type PlayerButtonType = { - player: MprisPlayer - colors: Var - items: Array<[name: string, widget: AgsLabel | AgsIcon]> - onClick: string - prop: string -}; const PlayerButton = ({ player, colors, @@ -316,7 +317,7 @@ const PlayerButton = ({ setup: (self) => { self .hook(player, () => { - (self.child as AgsStack).shown = `${player[prop]}`; + (self.child as StackGeneric).shown = `${player[prop]}`; }) .hook(colors, () => { if (!Mpris.players.find((p) => player === p)) { @@ -364,7 +365,7 @@ const PlayerButton = ({ export const ShuffleButton = ( player: MprisPlayer, - colors: Var, + colors: Var, ) => PlayerButton({ player, colors, @@ -384,7 +385,7 @@ export const ShuffleButton = ( export const LoopButton = ( player: MprisPlayer, - colors: Var, + colors: Var, ) => PlayerButton({ player, colors, @@ -408,7 +409,7 @@ export const LoopButton = ( export const PlayPauseButton = ( player: MprisPlayer, - colors: Var, + colors: Var, ) => PlayerButton({ player, colors, @@ -432,7 +433,7 @@ export const PlayPauseButton = ( export const PreviousButton = ( player: MprisPlayer, - colors: Var, + colors: Var, ) => PlayerButton({ player, colors, @@ -452,7 +453,7 @@ export const PreviousButton = ( export const NextButton = ( player: MprisPlayer, - colors: Var, + colors: Var, ) => PlayerButton({ player, colors, diff --git a/modules/ags/config/ts/media-player/player.ts b/modules/ags/config/ts/media-player/player.ts index abb160aa..77424fb4 100644 --- a/modules/ags/config/ts/media-player/player.ts +++ b/modules/ags/config/ts/media-player/player.ts @@ -12,14 +12,13 @@ const SPACING = 8; // Types import { MprisPlayer } from 'types/service/mpris.ts'; -import AgsOverlay from 'types/widgets/overlay.ts'; import { Variable as Var } from 'types/variable'; -import AgsBox from 'types/widgets/box.ts'; +import { Colors, PlayerBox, PlayerOverlay } from 'global-types'; const Top = ( player: MprisPlayer, - overlay: AgsOverlay, + overlay: PlayerOverlay, ) => Box({ class_name: 'top', hpack: 'start', @@ -32,7 +31,7 @@ const Top = ( const Center = ( player: MprisPlayer, - colors: Var, + colors: Var, ) => Box({ class_name: 'center', @@ -65,7 +64,7 @@ const Center = ( const Bottom = ( player: MprisPlayer, - colors: Var, + colors: Var, ) => Box({ class_name: 'bottom', @@ -88,8 +87,8 @@ const Bottom = ( const PlayerBox = ( player: MprisPlayer, - colors: Var, - overlay: AgsOverlay, + colors: Var, + overlay: PlayerOverlay, ) => { const widget = mpris.CoverArt(player, colors, { class_name: `player ${player.name}`, @@ -107,14 +106,9 @@ const PlayerBox = ( export default () => { const content = PlayerGesture({ - attribute: { - players: new Map(), - setup: false, - }, - - setup: (self) => { + setup: (self: PlayerOverlay) => { self - .hook(Mpris, (_: AgsOverlay, bus_name: string) => { + .hook(Mpris, (_, bus_name) => { const players = self.attribute.players; if (players.has(bus_name)) { @@ -136,11 +130,16 @@ export default () => { } // 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 const player = Mpris.getPlayer(bus_name); - const Colors = Variable(null); + const colorsVar = Variable({ + imageAccent: '#6b4fa2', + buttonAccent: '#ecdcff', + buttonText: '#25005a', + hoverAccent: '#d4baff', + }); if (!player) { return; @@ -148,19 +147,15 @@ export default () => { players.set( bus_name, - PlayerBox( - player, - Colors, - content.get_children()[0] as AgsOverlay, - ), + PlayerBox(player, colorsVar, self), ); self.overlays = Array.from(players.values()) - .map((widget) => widget) as Array; + .map((widget) => widget) as PlayerBox[]; const includes = self.attribute .includesWidget(previousFirst); - // Select favorite player at startup + // Select favorite player at startup const attrs = self.attribute; if (!attrs.setup && players.has(FAVE_PLAYER)) { @@ -168,28 +163,28 @@ export default () => { attrs.setup = true; } - // Move previousFirst on top again + // Move previousFirst on top again else if (includes) { attrs.moveToTop(previousFirst); } }, 'player-added') - .hook(Mpris, (_: AgsOverlay, bus_name: string) => { + .hook(Mpris, (_, bus_name) => { const players = self.attribute.players; if (!bus_name || !players.has(bus_name)) { return; } - // Get the one on top so we can move it up later - const previousFirst = self.overlays.at(-1); + // Get the one on top so we can move it up later + const previousFirst = self.overlays.at(-1) as PlayerBox; - // Remake overlays without deleted one + // Remake overlays without deleted one players.delete(bus_name); self.overlays = Array.from(players.values()) - .map((widget) => widget) as Array; + .map((widget) => widget) as PlayerBox[]; - // Move previousFirst on top again + // Move previousFirst on top again const includes = self.attribute .includesWidget(previousFirst); diff --git a/modules/ags/config/ts/misc/audio-icons.ts b/modules/ags/config/ts/misc/audio-icons.ts index 561157f4..52cd774a 100644 --- a/modules/ags/config/ts/misc/audio-icons.ts +++ b/modules/ags/config/ts/misc/audio-icons.ts @@ -23,7 +23,7 @@ Audio.connect('speaker-changed', () => { return; } - if (Audio.speaker.stream.is_muted) { + if (Audio.speaker.stream?.is_muted) { SpeakerIcon.value = speakerIcons[0]; } else { @@ -43,7 +43,7 @@ Audio.connect('microphone-changed', () => { return; } - if (Audio.microphone.stream.is_muted) { + if (Audio.microphone.stream?.is_muted) { MicIcon.value = micIcons[0]; } else { diff --git a/modules/ags/config/ts/misc/closer.ts b/modules/ags/config/ts/misc/closer.ts index d78093f8..6eb3172e 100644 --- a/modules/ags/config/ts/misc/closer.ts +++ b/modules/ags/config/ts/misc/closer.ts @@ -1,13 +1,14 @@ import App from 'resource:///com/github/Aylur/ags/app.js'; // Types -import AgsWindow from 'types/widgets/window'; +import { PopupWindow } from 'global-types'; export default () => { - (Array.from(App.windows) as Array<[string, AgsWindow]>) - .filter((w) => w[1].attribute?.close_on_unfocus && - w[1].attribute?.close_on_unfocus !== 'stay') + (Array.from(App.windows) as Array<[string, PopupWindow]>) + .filter((w) => + w[1].attribute.close_on_unfocus && + w[1].attribute.close_on_unfocus !== 'stay') .forEach((w) => { App.closeWindow(w[0]); }); diff --git a/modules/ags/config/ts/misc/cursorbox.ts b/modules/ags/config/ts/misc/cursorbox.ts index 7b765520..a55c4918 100644 --- a/modules/ags/config/ts/misc/cursorbox.ts +++ b/modules/ags/config/ts/misc/cursorbox.ts @@ -1,93 +1,268 @@ -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, event: Gdk.Event) => boolean | unknown; -const { Gtk, Gdk } = imports.gi; -const display = Gdk.Display.get_default(); +export type CursorBoxProps< + Child extends Gtk.Widget, + Attr = unknown, + Self = CursorBox, +> = BaseProps + on_hover_lost?: EventHandler -import * as EventBoxTypes from 'types/widgets/eventbox'; -type CursorBox = EventBoxTypes.EventBoxProps & { - on_primary_click_release?(self: EventBoxTypes.default): void; - on_hover?(self: EventBoxTypes.default): void; - on_hover_lost?(self: EventBoxTypes.default): void; -}; + on_scroll_up?: EventHandler + on_scroll_down?: EventHandler + + on_primary_click?: EventHandler + on_middle_click?: EventHandler + on_secondary_click?: EventHandler + + on_primary_click_release?: EventHandler + on_middle_click_release?: EventHandler + on_secondary_click_release?: EventHandler +}, Attr>; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export interface CursorBox extends Widget { } -export default ({ - on_primary_click_release = () => {/**/}, - on_hover = () => {/**/}, - on_hover_lost = () => {/**/}, - attribute, - ...props -}: CursorBox) => { +export class CursorBox extends Gtk.EventBox { + static { + register(this, { + properties: { + 'on-clicked': ['jsobject', 'rw'], + + 'on-hover': ['jsobject', 'rw'], + 'on-hover-lost': ['jsobject', 'rw'], + + 'on-scroll-up': ['jsobject', 'rw'], + 'on-scroll-down': ['jsobject', 'rw'], + + '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'], + }, + }); + } + + constructor(props: CursorBoxProps = {}) { + super(props as Gtk.EventBox.ConstructorProperties); + this.add_events(Gdk.EventMask.SCROLL_MASK); + this.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK); + + // Gesture stuff + const gesture = Gtk.GestureLongPress.new(this); + + this.hook(gesture, () => { + const pointer = gesture.get_point(null); + const x = pointer[1]; + const y = pointer[2]; + + if ((!x || !y) || (x === 0 && y === 0)) { + return; + } + + this.#canRun.value = !( + x > this.get_allocated_width() || + y > this.get_allocated_height() + ); + }, 'end'); + + 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 - const CanRun = Variable(true); - const Disabled = Variable(false); + #canRun = Variable(true); + #disabled = Variable(false); - const cursorBox = EventBox({ - ...props, + get disabled() { + return this.#disabled.value; + } - attribute: { - ...attribute, - disabled: Disabled, - }, + set disabled(value: boolean) { + this.#disabled.value = value; + } - 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); - } + get child() { + return super.child as Child; + } - CanRun.disconnect(id); - }); - }, + set child(child: Child) { + super.child = child; + } - // OnHover - }).on('enter-notify-event', (self) => { - on_hover(self); - if (!display) { - return; - } - self.window.set_cursor(Gdk.Cursor.new_from_name( - display, - Disabled.value ? - 'not-allowed' : - 'pointer', - )); - self.toggleClassName('hover', true); + get on_hover() { + return this._get('on-hover'); + } - // OnHoverLost - }).on('leave-notify-event', (self) => { - on_hover_lost(self); + set on_hover(callback: EventHandler) { + this._set('on-hover', callback); + } - self.window.set_cursor(null); - self.toggleClassName('hover', false); + get on_hover_lost() { + return this._get('on-hover-lost'); + } - // Disabled class - }).hook(Disabled, (self) => { - self.toggleClassName('disabled', Disabled.value); - }); + set on_hover_lost(callback: EventHandler) { + this._set('on-hover-lost', callback); + } - const gesture = Gtk.GestureLongPress.new(cursorBox); + get on_scroll_up() { + return this._get('on-scroll-up'); + } - cursorBox.hook(gesture, () => { - const pointer = gesture.get_point(null); - const x = pointer[1]; - const y = pointer[2]; + set on_scroll_up(callback: EventHandler) { + this._set('on-scroll-up', callback); + } - if ((!x || !y) || (x === 0 && y === 0)) { - return; - } + get on_scroll_down() { + return this._get('on-scroll-down'); + } - CanRun.value = !( - x > cursorBox.get_allocated_width() || - y > cursorBox.get_allocated_height() - ); - }, 'end'); + set on_scroll_down(callback: EventHandler) { + this._set('on-scroll-down', callback); + } - return cursorBox; -}; + get on_primary_click() { + return this._get('on-primary-click'); + } + + set on_primary_click(callback: EventHandler) { + this._set('on-primary-click', callback); + } + + get on_middle_click() { + return this._get('on-middle-click'); + } + + set on_middle_click(callback: EventHandler) { + this._set('on-middle-click', callback); + } + + get on_secondary_click() { + return this._get('on-secondary-click'); + } + + set on_secondary_click(callback: EventHandler) { + 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._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._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._set('on-secondary-click-release', callback); + } +} + +export default ( + props?: CursorBoxProps, +) => new CursorBox(props ?? {}); diff --git a/modules/ags/config/ts/misc/popup.ts b/modules/ags/config/ts/misc/popup.ts index c99cab19..a075bc4c 100644 --- a/modules/ags/config/ts/misc/popup.ts +++ b/modules/ags/config/ts/misc/popup.ts @@ -2,381 +2,391 @@ import App from 'resource:///com/github/Aylur/ags/app.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.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'; // 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 { RevealerProps } from 'types/widgets/revealer'; -import { WindowProps } from 'types/widgets/window'; -import AgsWindow from 'types/widgets/window'; -import AgsBox from 'types/widgets/box'; -import AgsOverlay from 'types/widgets/overlay'; -import { Binding } from 'types/service'; -type PopupWindow = WindowProps & { - transition?: RevealerProps['transition'] - transition_duration?: number - bezier?: string - on_open?(self: AgsWindow): void - on_close?(self: AgsWindow): void - blur?: boolean - close_on_unfocus?: 'none' | 'stay' | 'released' | 'clicked' - anchor?: Array - name: string -}; +import { Variable as Var } from 'types/variable'; + +import { + CloseType, + BoxGeneric, + OverlayGeneric, + PopupChild, + PopupWindowProps, +} from 'global-types'; // 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 ({ - transition = 'slide_down', - transition_duration = 800, - bezier = 'cubic-bezier(0.68, -0.4, 0.32, 1.4)', - on_open = () => {/**/}, - on_close = () => {/**/}, - // Window props - name, - child = Box(), - visible = false, - anchor = [], - layer = 'overlay', - blur = false, - close_on_unfocus = 'released', - ...props -}: PopupWindow) => { - const Child = Variable(child); - const AntiClip = Variable(false); - - const needsAnticlipping = bezier.match(/-[0-9]/) !== null && - transition !== 'crossfade'; - - const attribute = { - set_x_pos: ( - alloc: Allocation, - side = 'right' as 'left' | 'right', - ) => { - const window = App.getWindow(name) as AgsWindow; - - if (!window) { - return; - } - - const width = window.get_display() - .get_monitor_at_point(alloc.x, alloc.y) - .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, - layer, - anchor, - visible: false, - ...props, - attribute, - child: Child.bind(), +export class PopupWindow< + Child extends Widget, + Attr, +> extends Window { + static { + register(this, { + properties: { + content: ['widget', 'rw'], + }, }); } - const window = Window({ + #content: Var; + #antiClip: Var; + #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_duration = 800, + bezier = 'cubic-bezier(0.68, -0.4, 0.32, 1.4)', + on_open = () => {/**/}, + on_close = () => {/**/}, + + // Window props name, - layer, - anchor, - visible: false, - ...props, - + visible = false, + anchor = [], + layer = 'overlay', attribute, + content = Box(), + blur = false, + close_on_unfocus = 'released', + ...rest + }: PopupWindowProps) { + const needsAnticlipping = bezier.match(/-[0-9]/) !== null && + transition !== 'crossfade'; + const contentVar = Variable(Box() as Widget); + const antiClip = Variable(false); - setup: () => { - // Add way to make window open on startup - const id = App.connect('config-parsed', () => { - if (visible) { - App.openWindow(`${name}`); + if (content) { + contentVar.value = content; + } + + super({ + ...rest, + name, + visible, + anchor, + layer, + attribute, + setup: () => { + // Add way to make window open on startup + const id = App.connect('config-parsed', () => { + if (visible) { + App.openWindow(`${name}`); + } + App.disconnect(id); + }); + + if (blur) { + Hyprland.sendMessage('[[BATCH]] ' + + `keyword layerrule ignorealpha[0.97],${name}; ` + + `keyword layerrule blur,${name}`); } - App.disconnect(id); - }); + }, + child: Overlay({ + overlays: [Box({ + css: ` + min-height: 1px; + min-width: 1px; + padding: 1px; + `, + setup: (self) => { + // Make sure child doesn't + // get bigger than it should + const MAX_ANCHORS = 4; - if (blur) { - Hyprland.sendMessage('[[BATCH]] ' + - `keyword layerrule ignorealpha[0.97],${name}; ` + - `keyword layerrule blur,${name}`); - } - }, - - child: Overlay({ - overlays: [Box({ - css: ` - min-height: 1px; - min-width: 1px; - padding: 1px; - `, - setup: (self) => { - // Make sure child doesn't - // get bigger than it should - const MAX_ANCHORS = 4; - - self.hpack = 'center'; - self.vpack = 'center'; - - if (anchor.includes('top') && - anchor.includes('bottom')) { - self.vpack = 'center'; - } - else if (anchor.includes('top')) { - self.vpack = 'start'; - } - else if (anchor.includes('bottom')) { - self.vpack = 'end'; - } - - if (anchor.includes('left') && - anchor.includes('right')) { - self.hpack = 'center'; - } - else if (anchor.includes('left')) { - self.hpack = 'start'; - } - else if (anchor.includes('right')) { - self.hpack = 'end'; - } - - if (anchor.length === MAX_ANCHORS) { self.hpack = 'center'; self.vpack = 'center'; - } - if (needsAnticlipping) { - const reorder_child = (position: number) => { - // If unanchored, we have another anticlip widget - // so we can't change the order - if (anchor.length !== 0) { - for (const ch of self.children) { - if (ch !== Child.value) { - self.reorder_child(ch, position); + if (anchor.includes('top') && + anchor.includes('bottom')) { + self.vpack = 'center'; + } + else if (anchor.includes('top')) { + self.vpack = 'start'; + } + else if (anchor.includes('bottom')) { + self.vpack = 'end'; + } - return; + if (anchor.includes('left') && + anchor.includes('right')) { + self.hpack = 'center'; + } + else if (anchor.includes('left')) { + self.hpack = 'start'; + } + else if (anchor.includes('right')) { + self.hpack = 'end'; + } + + if (anchor.length === MAX_ANCHORS) { + self.hpack = 'center'; + self.vpack = 'center'; + } + + if (needsAnticlipping) { + const reorder_child = (position: number) => { + // If unanchored, we have another anticlip widget + // so we can't change the order + if (anchor.length !== 0) { + for (const ch of self.children) { + if (ch !== contentVar.value) { + self.reorder_child(ch, position); + + return; + } } } - } - }; + }; - self.hook(AntiClip, () => { - if (transition === 'slide_down') { - self.vertical = true; - reorder_child(-1); - } - else if (transition === 'slide_up') { - self.vertical = true; - reorder_child(0); - } - else if (transition === 'slide_right') { - self.vertical = false; - reorder_child(-1); - } - else if (transition === 'slide_left') { - self.vertical = false; - reorder_child(0); - } - }); - } - }, + self.hook(antiClip, () => { + if (transition === 'slide_down') { + self.vertical = true; + reorder_child(-1); + } + else if (transition === 'slide_up') { + self.vertical = true; + reorder_child(0); + } + else if (transition === 'slide_right') { + self.vertical = false; + reorder_child(-1); + } + else if (transition === 'slide_left') { + self.vertical = false; + reorder_child(0); + } + }); + } + }, - children: Child.bind().transform((v) => { - if (needsAnticlipping) { - return [ - // Add an anticlip widget when unanchored - // to not have a weird animation - anchor.length === 0 && Box({ - css: ` - min-height: 100px; - min-width: 100px; - padding: 2px; - `, - visible: AntiClip.bind(), - }), - v, - Box({ - css: ` - min-height: 100px; - min-width: 100px; - padding: 2px; - `, - visible: AntiClip.bind(), - }), - ]; - } - else { - return [v]; - } - }) as Binding, - })], + children: contentVar.bind().transform((v) => { + if (needsAnticlipping) { + return [ + // Add an anticlip widget when unanchored + // to not have a weird animation + anchor.length === 0 && Box({ + css: ` + min-height: 100px; + min-width: 100px; + padding: 2px; + `, + visible: antiClip.bind(), + }), + v, + Box({ + css: ` + min-height: 100px; + min-width: 100px; + padding: 2px; + `, + visible: antiClip.bind(), + }), + ]; + } + else { + return [v]; + } + }) as PopupChild, + })], - setup: (self) => { - self.on('get-child-position', (_, ch) => { - const overlay = (Child.value as Widget) - .get_parent() as AgsOverlay; + setup: (self) => { + self.on('get-child-position', (_, ch) => { + const overlay = contentVar.value + .get_parent() as OverlayGeneric; - if (ch === overlay) { - const alloc = overlay.get_allocation(); + if (ch === overlay) { + const alloc = overlay.get_allocation(); - (self.child as AgsBox).css = ` + (self.child as BoxGeneric).css = ` min-height: ${alloc.height}px; min-width: ${alloc.width}px; `; - } - }); - }, + } + }); + }, - child: Box({ - css: ` - min-height: 1px; - min-width: 1px; - padding: 1px; - `, + child: Box({ + css: ` + min-height: 1px; + min-width: 1px; + padding: 1px; + `, - setup: (self) => { - let currentTimeout: number; + setup: (self) => { + let currentTimeout: number; - self.hook(App, (_, currentName, isOpen) => { - if (currentName === name) { - const overlay = (Child.value as Widget) - .get_parent() as AgsOverlay; + self.hook(App, (_, currentName, isOpen) => { + if (currentName === name) { + const overlay = contentVar.value + .get_parent() as OverlayGeneric; - const alloc = overlay.get_allocation(); - const height = needsAnticlipping ? - alloc.height + 100 + 10 : - alloc.height + 10; + const alloc = overlay.get_allocation(); + const height = antiClip ? + alloc.height + 100 + 10 : + alloc.height + 10; - if (needsAnticlipping) { - AntiClip.value = true; + if (needsAnticlipping) { + antiClip.value = true; - const thisTimeout = timeout( - transition_duration, - () => { - // Only run the timeout if there isn't a newer timeout - if (thisTimeout === currentTimeout) { - AntiClip.value = false; - } - }, - ); + const thisTimeout = timeout( + transition_duration, + () => { + // Only run the timeout if there isn't a newer timeout + if (thisTimeout === + currentTimeout) { + antiClip.value = false; + } + }, + ); - currentTimeout = thisTimeout; - } + currentTimeout = thisTimeout; + } - let css = ''; + let css = ''; - /* Margin: top | right | bottom | left */ - switch (transition) { - case 'slide_down': - css = `margin: - -${height}px - 0 - ${height}px - 0 - ;`; - break; + /* Margin: top | right | bottom | left */ + switch (transition) { + case 'slide_down': + css = `margin: + -${height}px + 0 + ${height}px + 0 + ;`; + break; - case 'slide_up': - css = `margin: - ${height}px - 0 - -${height}px - 0 - ;`; - break; + case 'slide_up': + css = `margin: + ${height}px + 0 + -${height}px + 0 + ;`; + break; - case 'slide_left': - css = `margin: - 0 - -${height}px - 0 - ${height}px - ;`; - break; + case 'slide_left': + css = `margin: + 0 + -${height}px + 0 + ${height}px + ;`; + break; - case 'slide_right': - css = `margin: - 0 - ${height}px - 0 - -${height}px - ;`; - break; + case 'slide_right': + css = `margin: + 0 + ${height}px + 0 + -${height}px + ;`; + break; - case 'crossfade': - css = ` - opacity: 0; - min-height: 1px; - min-width: 1px; - `; - break; + case 'crossfade': + css = ` + opacity: 0; + min-height: 1px; + min-width: 1px; + `; + break; - default: - break; - } + default: + break; + } - if (isOpen) { - on_open(window); + if (isOpen) { + on_open(this); - // To get the animation, we need to set the css - // to hide the widget and then timeout to have - // the animation - overlay.css = css; - timeout(10, () => { - overlay.css = ` + // To get the animation, we need to set the css + // to hide the widget and then timeout to have + // the animation + overlay.css = css; + timeout(10, () => { + overlay.css = ` + transition: margin + ${transition_duration}ms + ${bezier}, + + opacity + ${transition_duration}ms + ${bezier}; + `; + }); + } + else { + timeout(transition_duration, () => { + on_close(this); + }); + + overlay.css = `${css} transition: margin ${transition_duration}ms ${bezier}, opacity ${transition_duration}ms ${bezier}; `; - }); + } } - else { - timeout(transition_duration, () => { - on_close(window); - }); - - overlay.css = `${css} - transition: margin - ${transition_duration}ms ${bezier}, - - opacity - ${transition_duration}ms ${bezier}; - `; - } - } - }); - }, + }); + }, + }), }), - }), - }); + }); - 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 ( + props: PopupWindowProps, +) => new PopupWindow(props); diff --git a/modules/ags/config/ts/notifications/base.ts b/modules/ags/config/ts/notifications/base.ts index 4467158a..81f18b9b 100644 --- a/modules/ags/config/ts/notifications/base.ts +++ b/modules/ags/config/ts/notifications/base.ts @@ -13,13 +13,19 @@ import CursorBox from '../misc/cursorbox.ts'; // Types 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'; type NotificationWidget = { notif: NotifObj slideIn?: 'Left' | 'Right' command?(): void }; +import { + EventBoxGeneric, + CursorBox as CBox, +} from 'global-types'; + const setTime = (time: number) => { return GLib.DateTime @@ -27,13 +33,17 @@ const setTime = (time: number) => { .format('%H:%M'); }; -const getDragState = (box: AgsEventBox) => (box.get_parent()?.get_parent() - ?.get_parent()?.get_parent()?.get_parent() as AgsEventBox) +const getDragState = (box: EventBoxGeneric) => (box + .get_parent() + ?.get_parent() + ?.get_parent() + ?.get_parent() + ?.get_parent() as AgsEventBox) ?.attribute.dragging; const NotificationIcon = (notif: NotifObj) => { - let iconCmd = (box: AgsEventBox):void => { + let iconCmd = (box: CBox):void => { console.log(box); }; @@ -89,7 +99,9 @@ const NotificationIcon = (notif: NotifObj) => { if (notif.image) { return CursorBox({ - on_primary_click_release: iconCmd, + on_primary_click_release: (self) => { + iconCmd(self); + }, child: Box({ vpack: 'start', @@ -120,7 +132,9 @@ const NotificationIcon = (notif: NotifObj) => { return CursorBox({ - on_primary_click_release: iconCmd, + on_primary_click_release: (self) => { + iconCmd(self); + }, child: Box({ vpack: 'start', @@ -175,7 +189,7 @@ export const Notification = ({ }); // Add body to notif - (notifWidget.child as AgsEventBox).add(Box({ + (notifWidget.child as EventBoxGeneric).add(Box({ class_name: `notification ${notif.urgency}`, vexpand: false, diff --git a/modules/ags/config/ts/notifications/binto.ts b/modules/ags/config/ts/notifications/binto.ts index 8dd6bf73..d877830b 100644 --- a/modules/ags/config/ts/notifications/binto.ts +++ b/modules/ags/config/ts/notifications/binto.ts @@ -21,5 +21,5 @@ export const NotifCenter = () => PopupWindow({ transition: 'slide_up', monitor: 1, - child: NotifCenterWidget(), + content: NotifCenterWidget(), }); diff --git a/modules/ags/config/ts/notifications/center.ts b/modules/ags/config/ts/notifications/center.ts index 5723fb23..577e1cf5 100644 --- a/modules/ags/config/ts/notifications/center.ts +++ b/modules/ags/config/ts/notifications/center.ts @@ -8,11 +8,11 @@ import { Notification, HasNotifs } from './base.ts'; import CursorBox from '../misc/cursorbox.ts'; // Types -import AgsBox from 'types/widgets/box.ts'; 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) { const NewNotif = Notification({ notif, @@ -53,7 +53,7 @@ const NotificationList = () => Box({ }, 'notified') .hook(Notifications, (box, id) => { - const notif = (box.children as Array) + const notif = (box.children as BoxGeneric[]) .find((ch) => ch.attribute.id === id); if (notif?.sensitive) { @@ -73,7 +73,7 @@ const ClearButton = () => CursorBox({ setup: (self) => { self.hook(HasNotifs, () => { - self.attribute.disabled?.setValue(!HasNotifs.value); + self.disabled = !HasNotifs.value; }); }, diff --git a/modules/ags/config/ts/notifications/wim.ts b/modules/ags/config/ts/notifications/wim.ts index f738b48c..b4f7820e 100644 --- a/modules/ags/config/ts/notifications/wim.ts +++ b/modules/ags/config/ts/notifications/wim.ts @@ -18,5 +18,5 @@ export const NotifCenter = () => PopupWindow({ anchor: ['top', 'right'], margins: [TOP_MARGIN, 0, 0, 0], - child: NotifCenterWidget(), + content: NotifCenterWidget(), }); diff --git a/modules/ags/config/ts/osd/main.ts b/modules/ags/config/ts/osd/main.ts index b0327b8e..4699d5fe 100644 --- a/modules/ags/config/ts/osd/main.ts +++ b/modules/ags/config/ts/osd/main.ts @@ -61,5 +61,5 @@ export default () => PopupWindow({ transition: 'slide_up', transition_duration, bezier: 'ease', - child: OSDs(), + content: OSDs(), }); diff --git a/modules/ags/config/ts/powermenu.ts b/modules/ags/config/ts/powermenu.ts index 1da14588..4285a335 100644 --- a/modules/ags/config/ts/powermenu.ts +++ b/modules/ags/config/ts/powermenu.ts @@ -44,5 +44,5 @@ const PowermenuWidget = () => CenterBox({ export default () => PopupWindow({ name: 'powermenu', - child: PowermenuWidget(), + content: PowermenuWidget(), }); diff --git a/modules/ags/config/ts/quick-settings/main.ts b/modules/ags/config/ts/quick-settings/main.ts index a8134851..72a71b69 100644 --- a/modules/ags/config/ts/quick-settings/main.ts +++ b/modules/ags/config/ts/quick-settings/main.ts @@ -54,5 +54,5 @@ export default () => PopupWindow({ name: 'quick-settings', anchor: ['top', 'right'], margins: [TOP_MARGIN, 0, 0, 0], - child: QuickSettingsWidget(), + content: QuickSettingsWidget(), }); diff --git a/modules/ags/config/tsconfig.json b/modules/ags/config/tsconfig.json index 9943d812..e1b961e8 100644 --- a/modules/ags/config/tsconfig.json +++ b/modules/ags/config/tsconfig.json @@ -10,7 +10,10 @@ "strict": true, "noImplicitAny": false, "baseUrl": ".", - "typeRoots": ["./types"], + "typeRoots": [ + "./types", + "./global-types.d.ts" + ], "skipLibCheck": true, "forceConsistentCasingInFileNames": true }