diff --git a/src/service/systemtray.ts b/src/service/systemtray.ts index ec60210..937ff64 100644 --- a/src/service/systemtray.ts +++ b/src/service/systemtray.ts @@ -14,7 +14,10 @@ const StatusNotifierItemIFace = loadInterfaceXML('org.kde.StatusNotifierItem')!; const StatusNotifierItemProxy = Gio.DBusProxy.makeProxyWrapper(StatusNotifierItemIFace) as unknown as StatusNotifierItemProxy; -const DbusMenu = mkCtor < DbusmenuGtk3.Menu.ConstructorProperties > (DbusmenuGtk3.Menu, 'DbusMenu'); +const DbusMenu = mkCtor< + typeof DbusmenuGtk3.Menu, + DbusmenuGtk3.Menu.ConstructorProperties +>(DbusmenuGtk3.Menu, 'DbusMenu'); export class TrayItem extends Service { static { @@ -122,7 +125,7 @@ export class TrayItem extends Service { dbus_name: proxy.g_name_owner, dbus_object: proxy.Menu, }); - this.menu = menu as unknown as DbusmenuGtk3.Menu; + this.menu = (menu as unknown) as DbusmenuGtk3.Menu; } if (this._proxy.IconThemePath) { @@ -169,7 +172,7 @@ export class TrayItem extends Service { const variant = proxy?.call_finish(result) as GLib.Variant; if (!variant) return; - const [properties] = variant.deepUnpack < Record < string, GLib.Variant> [] > (); + const [properties] = variant.deepUnpack < Record < string, GLib.Variant> [] >(); Object.entries(properties).map(([propertyName, value]) => { this._proxy.set_cached_property(propertyName, value); }); diff --git a/src/widget.ts b/src/widget.ts index 51cb665..17441cb 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -16,41 +16,43 @@ import AgsEntry, { type EntryProps } from './widgets/entry.js'; import { AgsMenu, AgsMenuItem, type MenuProps, type MenuItemProps } from './widgets/menu.js'; import AgsWindow, { type WindowProps } from './widgets/window.js'; import AgsCircularProgress, { type CircularProgressProps } from './widgets/circularprogress.js'; +import Gtk from 'gi://Gtk?version=3.0'; -export function mkCtor(w: new (...args: Props[]) => any, name?: string) { - const Ctor = AgsWidget(w, name); - return (props: Props & BaseProps) => new Ctor(props); +export function mkCtor(w: W, name?: string) { + const Ctor = AgsWidget(w, name); + return (props: Props & BaseProps) => new Ctor(props); } -export const Box = mkCtor(AgsBox, 'AgsBox'); -export const Button = mkCtor(AgsButton, 'AgsButton'); -export const CenterBox = mkCtor(AgsCenterBox, 'AgsCenterBox'); -export const CircularProgress = mkCtor(AgsCircularProgress, 'AgsCircularProgress'); -export const Entry = mkCtor(AgsEntry, 'AgsEntry'); -export const EventBox = mkCtor(AgsEventBox, 'AgsEventBox'); -export const Icon = mkCtor(AgsIcon, 'AgsIcon'); -export const Label = mkCtor(AgsLabel, 'AgsLabel'); -export const Menu = mkCtor(AgsMenu, 'AgsMenu'); -export const MenuItem = mkCtor(AgsMenuItem, 'AgsMenuItem'); -export const Overlay = mkCtor(AgsOverlay, 'AgsOverlay'); -export const ProgressBar = mkCtor(AgsProgressBar, 'AgsProgressBar'); -export const Revealer = mkCtor(AgsRevealer, 'AgsRevealer'); -export const Scrollable = mkCtor(AgsScrollable, 'AgsScrollable'); -export const Slider = mkCtor(AgsSlider, 'AgsSlider'); -export const Stack = mkCtor(AgsStack, 'AgsStack'); -export const Window = mkCtor(AgsWindow, 'AgsWindow'); +// @ts-ignore +export const Window = mkCtor(AgsWindow, 'AgsWindow'); +export const Box = mkCtor(AgsBox, 'AgsBox'); +export const Button = mkCtor(AgsButton, 'AgsButton'); +export const CenterBox = mkCtor(AgsCenterBox, 'AgsCenterBox'); +export const CircularProgress = mkCtor(AgsCircularProgress, 'AgsCircularProgress'); +export const Entry = mkCtor(AgsEntry, 'AgsEntry'); +export const EventBox = mkCtor(AgsEventBox, 'AgsEventBox'); +export const Icon = mkCtor(AgsIcon, 'AgsIcon'); +export const Label = mkCtor(AgsLabel, 'AgsLabel'); +export const Menu = mkCtor(AgsMenu, 'AgsMenu'); +export const MenuItem = mkCtor(AgsMenuItem, 'AgsMenuItem'); +export const Overlay = mkCtor(AgsOverlay, 'AgsOverlay'); +export const ProgressBar = mkCtor(AgsProgressBar, 'AgsProgressBar'); +export const Revealer = mkCtor(AgsRevealer, 'AgsRevealer'); +export const Scrollable = mkCtor(AgsScrollable, 'AgsScrollable'); +export const Slider = mkCtor(AgsSlider, 'AgsSlider'); +export const Stack = mkCtor(AgsStack, 'AgsStack'); const ctors = new Map(); // TODO: figure out how to type this export function Widget({ type, ...props }: { - type: new (props: Props) => any, + type: any }) { let Ctor; if (ctors.has(type)) { Ctor = ctors.get(type); } else { - Ctor = mkCtor(type); + Ctor = mkCtor(type); ctors.set(type, Ctor); } diff --git a/src/widgets/icon.ts b/src/widgets/icon.ts index 84399d9..84731fb 100644 --- a/src/widgets/icon.ts +++ b/src/widgets/icon.ts @@ -6,11 +6,13 @@ import Gdk from 'gi://Gdk?version=3.0'; import Service from '../service.js'; import cairo from '@girs/cairo-1.0'; -export interface IconProps extends Gtk.Image.ConstructorProperties { +interface Props extends Gtk.Image.ConstructorProperties { icon?: string | GdkPixbuf.Pixbuf size?: number } +export type IconProps = Props | string | GdkPixbuf.Pixbuf | undefined + export default class AgsIcon extends Gtk.Image { static { GObject.registerClass({ diff --git a/src/widgets/label.ts b/src/widgets/label.ts index a52b72f..416c865 100644 --- a/src/widgets/label.ts +++ b/src/widgets/label.ts @@ -7,11 +7,13 @@ import Service from '../service.js'; const justifications = ['left', 'center', 'right', 'fill']; const truncates = ['none', 'start', 'middle', 'end']; -export interface LabelProps extends Gtk.Label.ConstructorProperties { +interface Props extends Gtk.Label.ConstructorProperties { justification?: string truncate?: string } +export type LabelProps = Props | string | undefined + export default class AgsLabel extends Gtk.Label { static { GObject.registerClass({ @@ -22,7 +24,7 @@ export default class AgsLabel extends Gtk.Label { }, this); } - constructor(params: LabelProps | string) { + constructor(params: LabelProps) { super(typeof params === 'string' ? { label: params } : params); } diff --git a/src/widgets/widget.ts b/src/widgets/widget.ts index fec390a..3cb188b 100644 --- a/src/widgets/widget.ts +++ b/src/widgets/widget.ts @@ -31,184 +31,208 @@ export interface BaseProps { setup?: (self: Self) => void } -export default (W: new (...args: any[]) => any, GTypeName?: string) => class extends W { - static { - GObject.registerClass({ - ...(GTypeName ? { GTypeName } : {}), - Properties: { - 'class-name': Service.pspec('class-name', 'string', 'rw'), - 'class-names': Service.pspec('class-names', 'jsobject', 'rw'), - 'css': Service.pspec('css', 'string', 'rw'), - }, - }, this); - } +export default function (W: T, GTypeName?: string) { + // @ts-expect-error mixin constructor + return class AgsWidget extends W { + static { + GObject.registerClass({ + GTypeName: GTypeName || `${W.name}`, + Properties: { + 'class-name': Service.pspec('class-name', 'string', 'rw'), + 'class-names': Service.pspec('class-names', 'jsobject', 'rw'), + 'css': Service.pspec('css', 'string', 'rw'), + }, + }, this); + } - constructor(params: BaseProps> | unknown) { - const { - connections = [], - properties = [], - binds = [], - style, - halign, - valign, - setup, - ...rest - } = params as BaseProps>; - super(typeof params === 'string' ? params : rest); + constructor(params: BaseProps & Props | any) { + const { + connections = [], + properties = [], + binds = [], + style, + halign, + valign, + setup, + ...rest + } = params as BaseProps & Props; + super(typeof params === 'object' ? rest : params); - this.style = style!; - this.halign = halign!; - this.valign = valign!; + this.style = style!; + this.halign = halign!; + this.valign = valign!; - properties.forEach(([key, value]) => { - this[`_${key}`] = value; - }); + properties.forEach(([key, value]) => { + (this as unknown as { [key: string]: unknown })[`_${key}`] = value; + }); - binds.forEach(([prop, obj, objProp = 'value', transform = out => out]) => { - if (!prop || !obj) { - console.error(Error('missing arguments to binds')); + binds.forEach(([prop, obj, objProp = 'value', transform = out => out]) => { + if (!prop || !obj) { + console.error(Error('missing arguments to binds')); + return; + } + + // @ts-expect-error + const callback = () => this[prop] = transform(obj[objProp]); + connections.push([obj, callback, `notify::${objProp}`]); + }); + + connections.forEach(([s, callback, event]) => { + if (!s || !callback) { + console.error(Error('missing arguments to connections')); + return; + } + + if (typeof s === 'string') + this.connect(s, callback); + + else if (typeof s === 'number') + interval(s, () => callback(this as any), this as unknown as Gtk.Widget); + + else if (s instanceof GObject.Object) + connect(s, this as unknown as Gtk.Widget, callback as any, event); + + else + console.error(Error(`${s} is not a GObject nor a string nor a number`)); + }); + + if (typeof setup === 'function') + setup(this as any); + } + + toggleClassName(className: string, condition = true) { + condition + ? this.get_style_context().add_class(className) + : this.get_style_context().remove_class(className); + } + + // @ts-expect-error prop override + get halign() { return aligns[super.halign]; } + + // @ts-expect-error prop override + set halign(align: string) { + if (!align) + return; + + if (!aligns.includes(align)) { + console.error(new Error(`halign has to be on of ${aligns}`)); return; } + super.halign = aligns.findIndex(a => a === align); + } + + // @ts-expect-error prop override + get valign() { return aligns[super.valign]; } + + // @ts-expect-error prop override + set valign(align: string) { + if (!align) + return; + + if (!aligns.includes(align)) { + console.error(new Error(`valign has to be on of ${aligns}`)); + return; + } + + super.valign = aligns.findIndex(a => a === align); + } + + get class_name() { + // @ts-expect-error + return this._className || ''; + } + + set class_name(names: string) { + // @ts-expect-error + this._className = names; + this.class_names = names.split(/\s+/); + this.notify('class-name'); + } + + get class_names() { + // @ts-expect-error + return this._classNames || []; + } + + set class_names(names: string[]) { + this.class_names.forEach((cn: string) => this.toggleClassName(cn, false)); + // @ts-expect-error - const callback = () => this[prop] = transform(obj[objProp]); - connections.push([obj, callback, `notify::${objProp}`]); - }); + this._classNames = names.map(cn => { + this.toggleClassName(cn); + return cn; + }); - connections.forEach(([s, callback, event]) => { - if (!s || !callback) { - console.error(Error('missing arguments to connections')); + this.notify('class-names'); + } + + setCss(css: string) { + const previous = widgetProviders.get(this); + if (previous) + this.get_style_context().remove_provider(previous); + + const provider = new Gtk.CssProvider(); + widgetProviders.set(this, provider); + provider.load_from_data(new TextEncoder().encode(css)); + this.get_style_context() + .add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_USER); + + this.notify('css'); + } + + setStyle(css: string) { + this.setCss(`* { ${css} }`); + this.notify('style'); + } + + // @ts-expect-error prop override + get style() { return this._style || ''; } + + // @ts-expect-error prop override + set style(css: string) { + if (!css) return; - } - if (typeof s === 'string') - this.connect(s, callback); + // @ts-expect-error + this._style = css; + this.setCss(`* { ${css} }`); + this.notify('style'); + } - else if (typeof s === 'number') - interval(s, () => callback(this), this as unknown as Gtk.Widget); + // @ts-expect-error + get css() { return this._css || ''; } + set css(css: string) { + if (!css) + return; - else if (s instanceof GObject.Object) - connect(s, this as unknown as Gtk.Widget, callback, event); + // @ts-expect-error + this._css = css; + this.setCss(css); + this.notify('css'); + } + get child(): Gtk.Widget | null { + // @ts-expect-error + if (typeof this.get_child === 'function') + // @ts-expect-error + return this.get_child(); + + return null; + } + + set child(child: Gtk.Widget) { + // @ts-expect-error + if (typeof this.set_child === 'function') + // @ts-expect-error + this.set_child(child); else - console.error(Error(`${s} is not a GObject nor a string nor a number`)); - }); - - if (typeof setup === 'function') - setup(this); - } - - toggleClassName(className: string, condition = true) { - condition - ? this.get_style_context().add_class(className) - : this.get_style_context().remove_class(className); - } - - get halign() { return aligns[super.halign]; } - set halign(align: string) { - if (!align) - return; - - if (!aligns.includes(align)) { - console.error(new Error(`halign has to be on of ${aligns}`)); - return; + console.error(new Error(`can't set child on ${this}`)); } - super.halign = aligns.findIndex(a => a === align); - } - - get valign() { return aligns[super.valign]; } - set valign(align: string) { - if (!align) - return; - - if (!aligns.includes(align)) { - console.error(new Error(`valign has to be on of ${aligns}`)); - return; + // @ts-expect-error prop override + get parent(): Gtk.Container | null { + return this.get_parent() as Gtk.Container || null; } - - super.valign = aligns.findIndex(a => a === align); - } - - get class_name() { - return this._className || ''; - } - - set class_name(names: string) { - this._className = names; - this.class_names = names.split(/\s+/); - this.notify('class-name'); - } - - get class_names() { - return this._classNames || []; - } - - set class_names(names: string[]) { - this.class_names.forEach((cn: string) => this.toggleClassName(cn, false)); - - this._classNames = names.map(cn => { - this.toggleClassName(cn); - return cn; - }); - - this.notify('class-names'); - } - - setCss(css: string) { - const previous = widgetProviders.get(this); - if (previous) - this.get_style_context().remove_provider(previous); - - const provider = new Gtk.CssProvider(); - widgetProviders.set(this, provider); - provider.load_from_data(new TextEncoder().encode(css)); - this.get_style_context() - .add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_USER); - - this.notify('css'); - } - - setStyle(css: string) { - this.setCss(`* { ${css} }`); - this.notify('style'); - } - - get style() { return this._style || ''; } - set style(css: string) { - if (!css) - return; - - this.setCss(`* { ${css} }`); - this._style = css; - this.notify('style'); - } - - get css() { return this._css || ''; } - set css(css: string) { - if (!css) - return; - - this.setCss(css); - this._css = css; - this.notify('css'); - } - - get child(): Gtk.Widget | null { - if (typeof this.get_child === 'function') - return this.get_child(); - - return null; - } - - set child(child: Gtk.Widget) { - if (typeof this.set_child === 'function') - this.set_child(child); - else - console.error(new Error(`can't set child on ${this}`)); - } - - get parent() { - return this.get_parent(); - } -}; + }; +}