feat(ags4): only accept widgets as children and some refactor
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2025-01-12 20:12:46 -05:00
parent d854873bec
commit ad69ef38ea
18 changed files with 96 additions and 113 deletions

View file

@ -1,12 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { execAsync, Variable } from 'astal';
import { Gtk } from 'astal/gtk4';
import Binding, { Connectable, kebabify, snakeify, Subscribable } from 'astal/binding';
export const noImplicitDestroy = Symbol('no no implicit destroy');
export const setChildren = Symbol('children setter method');
const mergeBindings = (array: any[]) => {
const getValues = (...args: any[]) => {
const mergeBindings = <Value = unknown>(
array: (Value | Binding<Value>)[],
): Value[] | Binding<Value[]> => {
const getValues = (...args: Value[]) => {
let i = 0;
return array.map((value) => value instanceof Binding ?
@ -16,14 +18,19 @@ const mergeBindings = (array: any[]) => {
const bindings = array.filter((i) => i instanceof Binding);
if (bindings.length === 0) { return array; }
if (bindings.length === 0) {
return array as Value[];
}
if (bindings.length === 1) { return bindings[0].as(getValues); }
if (bindings.length === 1) {
return bindings[0].as(getValues);
}
return Variable.derive(bindings, getValues)();
};
const setProp = (obj: any, prop: string, value: any) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setProp = (obj: any, prop: string, value: unknown) => {
try {
const setter = `set_${snakeify(prop)}`;
@ -39,11 +46,11 @@ const setProp = (obj: any, prop: string, value: any) => {
export const hook = <Widget extends Connectable>(
widget: Widget,
object: Connectable | Subscribable,
signalOrCallback: string | ((self: Widget, ...args: any[]) => void),
callback?: (self: Widget, ...args: any[]) => void,
signalOrCallback: string | ((self: Widget, ...args: unknown[]) => void),
callback?: (self: Widget, ...args: unknown[]) => void,
) => {
if (typeof object.connect === 'function' && callback) {
const id = object.connect(signalOrCallback, (_: any, ...args: unknown[]) => {
const id = object.connect(signalOrCallback, (_: unknown, ...args: unknown[]) => {
callback(widget, ...args);
});
@ -61,7 +68,8 @@ export const hook = <Widget extends Connectable>(
};
export const construct = <Widget extends Connectable & {
[setChildren]: (children: any[]) => void
[setChildren]: (children: Gtk.Widget[]) => void
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}>(widget: Widget, config: any) => {
// eslint-disable-next-line prefer-const
let { setup, child, children = [], ...props } = config;
@ -82,9 +90,9 @@ export const construct = <Widget extends Connectable & {
}
// collect bindings
const bindings: [string, Binding<any>][] = Object
const bindings: [string, Binding<unknown>][] = Object
.keys(props)
.reduce((acc: any, prop) => {
.reduce((acc: [string, Binding<unknown>][], prop) => {
if (props[prop] instanceof Binding) {
const binding = props[prop];
@ -99,7 +107,7 @@ export const construct = <Widget extends Connectable & {
// collect signal handlers
const onHandlers: [string, string | (() => unknown)][] = Object
.keys(props)
.reduce((acc: any, key) => {
.reduce((acc: [string, Binding<unknown>][], key) => {
if (key.startsWith('on')) {
const sig = kebabify(key).split('-').slice(1).join('-');
const handler = props[key];
@ -113,7 +121,7 @@ export const construct = <Widget extends Connectable & {
}, []);
// set children
const mergedChildren = mergeBindings(children.flat(Infinity));
const mergedChildren = mergeBindings<Gtk.Widget>(children.flat(Infinity));
if (mergedChildren instanceof Binding) {
widget[setChildren](mergedChildren.get());
@ -143,11 +151,11 @@ export const construct = <Widget extends Connectable & {
// setup bindings handlers
for (const [prop, binding] of bindings) {
if (prop === 'child' || prop === 'children') {
widget.connect('destroy', binding.subscribe((v: any) => {
widget.connect('destroy', (binding as Binding<Gtk.Widget[]>).subscribe((v: Gtk.Widget[]) => {
widget[setChildren](v);
}));
}
widget.connect('destroy', binding.subscribe((v: any) => {
widget.connect('destroy', binding.subscribe((v: unknown) => {
setProp(widget, prop, v);
}));
setProp(widget, prop, binding.get());

View file

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Gdk from 'gi://Gdk?version=4.0';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
import { property, register } from 'astal';
import Binding, { type Connectable, type Subscribable } from 'astal/binding';
import {
hook,
@ -14,12 +12,6 @@ import {
export type BindableChild = Gtk.Widget | Binding<Gtk.Widget>;
export const filter = (children: any[]) => {
return children.flat(Infinity).map((ch) => ch instanceof Gtk.Widget ?
ch :
new Gtk.Label({ visible: true, label: String(ch) }));
};
export const type = Symbol('child type');
const dummyBuilder = new Gtk.Builder();
@ -131,13 +123,17 @@ const setupControllers = <T>(widget: Gtk.Widget, {
return props;
};
export default <
C extends new(...args: any[]) => Gtk.Widget,
>(cls: C, clsName = cls.name) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default <C extends new (...args: any[]) => Gtk.Widget>(
cls: C,
clsName = cls.name,
) => {
@register({ GTypeName: `RealClass_${clsName}` })
class Widget extends cls {
declare private _css: string | undefined;
declare private _provider: Gtk.CssProvider | undefined;
@property(String)
get css(): string | undefined {
return this._css;
}
@ -156,18 +152,28 @@ export default <
this._provider.load_from_string(value);
}
declare private [type]: string;
@property(String)
get type(): string { return this[type]; }
set type(value: string) { this[type] = value; }
get children(): Gtk.Widget[] { return this.getChildren(this); }
set children(value: Gtk.Widget[]) { this.setChildren(this, value); }
declare private [noImplicitDestroy]: boolean;
@property(String)
get noImplicitDestroy(): boolean { return this[noImplicitDestroy]; }
set noImplicitDestroy(value: boolean) { this[noImplicitDestroy] = value; }
protected getChildren(widget: Gtk.Widget): Gtk.Widget[] {
if ('get_child' in widget && typeof widget.get_child == 'function') {
return widget.get_child() ? [widget.get_child()] : [];
@ -184,11 +190,7 @@ export default <
return children;
}
protected setChildren(widget: Gtk.Widget, children: any[]) {
children = children.flat(Infinity).map((ch) => ch instanceof Gtk.Widget ?
ch :
new Gtk.Label({ visible: true, label: String(ch) }));
protected setChildren(widget: Gtk.Widget, children: Gtk.Widget[]) {
for (const child of children) {
widget.vfunc_add_child(
dummyBuilder,
@ -198,38 +200,42 @@ export default <
}
}
[setChildren](children: any[]) {
const w = this as unknown as Widget;
[setChildren](children: Gtk.Widget[]) {
for (const child of (this.getChildren(this))) {
child?.unparent();
for (const child of (this.getChildren(w))) {
if (child instanceof Gtk.Widget) {
child.unparent();
if (!children.includes(child) && noImplicitDestroy in this) { child.run_dispose(); }
if (!children.includes(child) && noImplicitDestroy in this) {
child.run_dispose();
}
}
this.setChildren(w, children);
this.setChildren(this, children);
}
hook(
object: Connectable,
signal: string,
callback: (self: this, ...args: any[]) => void,
callback: (self: this, ...args: unknown[]) => void,
): this;
hook(
object: Subscribable,
callback: (self: this, ...args: any[]) => void,
callback: (self: this, ...args: unknown[]) => void,
): this;
hook(
object: Connectable | Subscribable,
signalOrCallback: string | ((self: this, ...args: any[]) => void),
callback?: (self: this, ...args: any[]) => void,
signalOrCallback: string | ((self: this, ...args: unknown[]) => void),
callback?: (self: this, ...args: unknown[]) => void,
) {
hook(this, object, signalOrCallback, callback);
return this;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(...params: any[]) {
const props = params[0] || {};
@ -249,24 +255,9 @@ export default <
delete props.type;
}
construct(this as any, setupControllers(this, props));
construct(this, setupControllers(this, props));
}
}
GObject.registerClass({
GTypeName: `RealClass_${clsName}`,
Properties: {
'css': GObject.ParamSpec.string(
'css', '', '', GObject.ParamFlags.READWRITE, '',
),
'type': GObject.ParamSpec.string(
'type', '', '', GObject.ParamFlags.READWRITE, '',
),
'no-implicit-destroy': GObject.ParamSpec.boolean(
'no-implicit-destroy', '', '', GObject.ParamFlags.READWRITE, false,
),
},
}, Widget);
return Widget;
};

View file

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Astal, type ConstructProps } from 'astal/gtk4';
import { Astal, type ConstructProps, Gtk } from 'astal/gtk4';
import astalify, { filter } from './astalify';
import astalify from './astalify';
export type BoxProps = ConstructProps<
@ -13,13 +11,14 @@ export type BoxProps = ConstructProps<
@register({ GTypeName: 'Box' })
export class Box extends astalify(Astal.Box) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: BoxProps) { super(props as any); }
getChildren(self: Box) {
return self.get_children();
}
setChildren(self: Box, children: any[]) {
return self.set_children(filter(children));
setChildren(self: Box, children: Gtk.Widget[]) {
return self.set_children(children);
}
}

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
@ -17,5 +15,6 @@ export type ButtonProps = ConstructProps<
@register({ GTypeName: 'Button' })
export class Button extends astalify(Gtk.Button) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: ButtonProps) { super(props as any); }
}

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
@ -22,5 +20,6 @@ export type CalendarProps = ConstructProps<
@register({ GTypeName: 'Calendar' })
export class Calendar extends astalify(Gtk.Calendar) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: CalendarProps) { super(props as any); }
}

View file

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
import astalify, { filter } from './astalify';
import astalify from './astalify';
export type CenterBoxProps = ConstructProps<
@ -13,17 +11,20 @@ export type CenterBoxProps = ConstructProps<
@register({ GTypeName: 'CenterBox' })
export class CenterBox extends astalify(Gtk.CenterBox) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: CenterBoxProps) { super(props as any); }
getChildren(box: CenterBox) {
return [box.startWidget, box.centerWidget, box.endWidget];
}
setChildren(box: CenterBox, children: any[]) {
const ch = filter(children);
setChildren(box: CenterBox, children: (Gtk.Widget | null)[]) {
if (children.length > 3) {
throw new Error('Cannot have more than 3 children in a CenterBox');
}
box.startWidget = ch[0] || new Gtk.Box();
box.centerWidget = ch[1] || new Gtk.Box();
box.endWidget = ch[2] || new Gtk.Box();
box.startWidget = children[0] || new Gtk.Box();
box.centerWidget = children[1] || new Gtk.Box();
box.endWidget = children[2] || new Gtk.Box();
}
}

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
@ -18,6 +16,7 @@ export type EntryProps = ConstructProps<
@register({ GTypeName: 'Entry' })
export class Entry extends astalify(Gtk.Entry) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: EntryProps) { super(props as any); }
getChildren() { return []; }

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
@ -13,6 +11,7 @@ export type ImageProps = ConstructProps<
@register({ GTypeName: 'Image' })
export class Image extends astalify(Gtk.Image) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: ImageProps) { super(props as any); }
getChildren() { return []; }

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
@ -13,9 +11,8 @@ export type LabelProps = ConstructProps<
@register({ GTypeName: 'Label' })
export class Label extends astalify(Gtk.Label) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: LabelProps) { super(props as any); }
getChildren() { return []; }
setChildren(self: Label, children: any[]) { self.label = String(children); }
}

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
@ -13,6 +11,7 @@ export type LevelBarProps = ConstructProps<
@register({ GTypeName: 'LevelBar' })
export class LevelBar extends astalify(Gtk.LevelBar) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: LevelBarProps) { super(props as any); }
getChildren() { return []; }

View file

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
import astalify, { filter } from './astalify';
import astalify from './astalify';
export type MenuButtonProps = ConstructProps<
@ -13,14 +11,15 @@ export type MenuButtonProps = ConstructProps<
@register({ GTypeName: 'MenuButton' })
export class MenuButton extends astalify(Gtk.MenuButton) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: MenuButtonProps) { super(props as any); }
getChildren(self: MenuButton) {
return [self.popover, self.child];
}
setChildren(self: MenuButton, children: any[]) {
for (const child of filter(children)) {
setChildren(self: MenuButton, children: Gtk.Widget[]) {
for (const child of children) {
if (child instanceof Gtk.Popover) {
self.set_popover(child);
}

View file

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
import astalify, { filter, type } from './astalify';
import astalify, { type } from './astalify';
export type OverlayProps = ConstructProps<
@ -13,6 +11,7 @@ export type OverlayProps = ConstructProps<
@register({ GTypeName: 'Overlay' })
export class Overlay extends astalify(Gtk.Overlay) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: OverlayProps) { super(props as any); }
getChildren(self: Overlay) {
@ -27,8 +26,8 @@ export class Overlay extends astalify(Gtk.Overlay) {
return children.filter((child) => child !== self.child);
}
setChildren(self: Overlay, children: any[]) {
for (const child of filter(children)) {
setChildren(self: Overlay, children: Gtk.Widget[]) {
for (const child of children) {
const types = type in child ?
(child[type] as string).split(/\s+/) :
[];

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
@ -13,5 +11,6 @@ export type PopoverProps = ConstructProps<
@register({ GTypeName: 'Popover' })
export class Popover extends astalify(Gtk.Popover) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: PopoverProps) { super(props as any); }
}

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
@ -13,5 +11,6 @@ export type RevealerProps = ConstructProps<
@register({ GTypeName: 'Revealer' })
export class Revealer extends astalify(Gtk.Revealer) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: RevealerProps) { super(props as any); }
}

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Astal, type ConstructProps } from 'astal/gtk4';
@ -17,6 +15,7 @@ export type SliderProps = ConstructProps<
@register({ GTypeName: 'Slider' })
export class Slider extends astalify(Astal.Slider) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: SliderProps) { super(props as any); }
getChildren() { return []; }

View file

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
import astalify, { filter } from './astalify';
import astalify from './astalify';
export type StackProps = ConstructProps<
@ -13,10 +11,11 @@ export type StackProps = ConstructProps<
@register({ GTypeName: 'Stack' })
export class Stack extends astalify(Gtk.Stack) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: StackProps) { super(props as any); }
setChildren(self: Stack, children: any[]) {
for (const child of filter(children)) {
setChildren(self: Stack, children: Gtk.Widget[]) {
for (const child of children) {
if (child.name !== '' && child.name !== null) {
self.add_named(child, child.name);
}

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Gtk, type ConstructProps } from 'astal/gtk4';
@ -13,6 +11,7 @@ export type SwitchProps = ConstructProps<
@register({ GTypeName: 'Switch' })
export class Switch extends astalify(Gtk.Switch) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: SwitchProps) { super(props as any); }
getChildren() { return []; }

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { register } from 'astal';
import { Astal, type ConstructProps } from 'astal/gtk4';
@ -13,5 +11,6 @@ export type WindowProps = ConstructProps<
@register({ GTypeName: 'Window' })
export class Window extends astalify(Astal.Window) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(props?: WindowProps) { super(props as any); }
}