diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/gjs/.gitignore | 1 | ||||
-rw-r--r-- | core/gjs/package.json | 6 | ||||
-rw-r--r-- | core/gjs/src/astalify.ts | 452 | ||||
-rw-r--r-- | core/gjs/src/gobject.ts | 178 | ||||
-rw-r--r-- | core/gjs/src/jsx/jsx-runtime.ts | 9 | ||||
-rw-r--r-- | core/gjs/src/widgets.ts | 154 | ||||
-rw-r--r-- | core/gjs/tsconfig.json | 10 | ||||
-rw-r--r-- | core/lua/astal/variable.lua | 2 | ||||
-rw-r--r-- | core/lua/astal/widget.lua | 42 | ||||
-rw-r--r-- | core/src/widget/box.vala | 11 | ||||
-rw-r--r-- | core/src/widget/button.vala | 10 | ||||
-rw-r--r-- | core/src/widget/stack.vala | 11 |
12 files changed, 547 insertions, 339 deletions
diff --git a/core/gjs/.gitignore b/core/gjs/.gitignore index 8c2cc59..53f4bc3 100644 --- a/core/gjs/.gitignore +++ b/core/gjs/.gitignore @@ -1,3 +1,4 @@ node_modules/ result/ dist/ +@girs/ diff --git a/core/gjs/package.json b/core/gjs/package.json index e3c86d0..e829409 100644 --- a/core/gjs/package.json +++ b/core/gjs/package.json @@ -16,8 +16,10 @@ }, "exports": { ".": "./index.ts", - "./app": "./src/application.ts", + "./application": "./src/application.ts", + "./binding": "./src/binding.ts", "./file": "./src/file.ts", + "./gobject": "./src/gobject.ts", "./process": "./src/process.ts", "./time": "./src/time.ts", "./variable": "./src/variable.ts", @@ -41,6 +43,6 @@ }, "scripts": { "lint": "eslint . --fix", - "types": "ts-for-gir generate -o node_modules/@girs --package" + "types": "ts-for-gir generate -o @girs" } } diff --git a/core/gjs/src/astalify.ts b/core/gjs/src/astalify.ts index a568a50..c4cbc5c 100644 --- a/core/gjs/src/astalify.ts +++ b/core/gjs/src/astalify.ts @@ -1,58 +1,8 @@ import Binding, { kebabify, snakeify, type Connectable, type Subscribable } from "./binding.js" -import { Astal, Gtk, Gdk } from "./imports.js" +import { Astal, Gtk, Gdk, GObject } from "./imports.js" import { execAsync } from "./process.js" import Variable from "./variable.js" -Object.defineProperty(Astal.Box.prototype, "children", { - get() { return this.get_children() }, - set(v) { this.set_children(v) }, -}) - -function setChildren(parent: Gtk.Widget, children: Gtk.Widget[]) { - children = children.flat(Infinity).map(ch => ch instanceof Gtk.Widget - ? ch - : new Gtk.Label({ visible: true, label: String(ch) })) - - // remove - if (parent instanceof Gtk.Bin) { - const ch = parent.get_child() - if (ch) - parent.remove(ch) - } - else if (parent instanceof Gtk.Container && - !(parent instanceof Astal.Box || - parent instanceof Astal.Stack)) { - for(const ch of parent.get_children()) - parent.remove(ch) - } - - // TODO: add more container types - if (parent instanceof Astal.Box) { - parent.set_children(children) - } - - else if (parent instanceof Astal.Stack) { - parent.set_children(children) - } - - else if (parent instanceof Astal.CenterBox) { - parent.startWidget = children[0] - parent.centerWidget = children[1] - parent.endWidget = children[2] - } - - else if (parent instanceof Astal.Overlay) { - const [child, ...overlays] = children - parent.set_child(child) - parent.set_overlays(overlays) - } - - else if (parent instanceof Gtk.Container) { - for (const ch of children) - parent.add(ch) - } -} - function mergeBindings(array: any[]) { function getValues(...args: any[]) { let i = 0 @@ -75,204 +25,228 @@ function mergeBindings(array: any[]) { function setProp(obj: any, prop: string, value: any) { try { + // the setter method has to be used because + // array like properties are not bound correctly as props const setter = `set_${snakeify(prop)}` if (typeof obj[setter] === "function") return obj[setter](value) - if (Object.hasOwn(obj, prop)) - return (obj[prop] = value) + return (obj[prop] = value) } catch (error) { console.error(`could not set property "${prop}" on ${obj}:`, error) } - - console.error(`could not set property "${prop}" on ${obj}`) -} - -export type Widget<C extends InstanceType<typeof Gtk.Widget>> = C & { - className: string - css: string - cursor: Cursor - clickThrough: boolean - toggleClassName(name: string, on?: boolean): void - hook( - object: Connectable, - signal: string, - callback: (self: Widget<C>, ...args: any[]) => void, - ): Widget<C> - hook( - object: Subscribable, - callback: (self: Widget<C>, ...args: any[]) => void, - ): Widget<C> -} - -function hook( - self: Gtk.Widget, - object: Connectable | Subscribable, - signalOrCallback: string | ((self: Gtk.Widget, ...args: any[]) => void), - callback?: (self: Gtk.Widget, ...args: any[]) => void, -) { - if (typeof object.connect === "function" && callback) { - const id = object.connect(signalOrCallback, (_: any, ...args: unknown[]) => { - callback(self, ...args) - }) - self.connect("destroy", () => { - (object.disconnect as Connectable["disconnect"])(id) - }) - } - - else if (typeof object.subscribe === "function" && typeof signalOrCallback === "function") { - const unsub = object.subscribe((...args: unknown[]) => { - signalOrCallback(self, ...args) - }) - self.connect("destroy", unsub) - } - - return self } -function ctor(self: any, config: any = {}, children: any = []) { - const { setup, ...props } = config - props.visible ??= true - - // collect bindings - const bindings = Object.keys(props).reduce((acc: any, prop) => { - if (props[prop] instanceof Binding) { - const binding = props[prop] - delete props[prop] - return [...acc, [prop, binding]] - } - return acc - }, []) - - // collect signal handlers - const onHandlers = Object.keys(props).reduce((acc: any, key) => { - if (key.startsWith("on")) { - const sig = kebabify(key).split("-").slice(1).join("-") - const handler = props[key] - delete props[key] - return [...acc, [sig, handler]] - } - return acc - }, []) - - // set children - children = mergeBindings(children.flat(Infinity)) - if (children instanceof Binding) { - setChildren(self, children.get()) - self.connect("destroy", children.subscribe((v) => { - setChildren(self, v) - })) - } - else { - if (children.length > 0) { - setChildren(self, children) +export default function astalify< + C extends { new(...args: any[]): Gtk.Widget }, +>(cls: C) { + class Widget extends cls { + get css(): string { return Astal.widget_get_css(this) } + set css(css: string) { Astal.widget_set_css(this, css) } + get_css(): string { return this.css } + set_css(css: string) { this.css = css } + + get className(): string { return Astal.widget_get_class_names(this).join(" ") } + set className(className: string) { Astal.widget_set_class_names(this, className.split(/\s+/)) } + get_class_name(): string { return this.className } + set_class_name(className: string) { this.className = className } + + get cursor(): Cursor { return Astal.widget_get_cursor(this) as Cursor } + set cursor(cursor: Cursor) { Astal.widget_set_cursor(this, cursor) } + get_cursor(): Cursor { return this.cursor } + set_cursor(cursor: Cursor) { this.cursor = cursor } + + get clickThrough(): boolean { return Astal.widget_get_click_through(this) } + set clickThrough(clickThrough: boolean) { Astal.widget_set_click_through(this, clickThrough) } + get_click_through(): boolean { return this.clickThrough } + set_click_through(clickThrough: boolean) { this.clickThrough = clickThrough } + + declare __no_implicit_destroy: boolean + get noImplicitDestroy(): boolean { return this.__no_implicit_destroy } + set noImplicitDestroy(value: boolean) { this.__no_implicit_destroy = value } + + _setChildren(children: Gtk.Widget[]) { + children = children.flat(Infinity).map(ch => ch instanceof Gtk.Widget + ? ch + : new Gtk.Label({ visible: true, label: String(ch) })) + + // remove + if (this instanceof Gtk.Bin) { + const ch = this.get_child() + if (ch) + this.remove(ch) + if (ch && !children.includes(ch) && !this.noImplicitDestroy) + ch?.destroy() + } + else if (this instanceof Gtk.Container) { + for (const ch of this.get_children()) { + this.remove(ch) + if (!children.includes(ch) && !this.noImplicitDestroy) + ch?.destroy() + } + } + + // TODO: add more container types + if (this instanceof Astal.Box) { + this.set_children(children) + } + + else if (this instanceof Astal.Stack) { + this.set_children(children) + } + + else if (this instanceof Astal.CenterBox) { + this.startWidget = children[0] + this.centerWidget = children[1] + this.endWidget = children[2] + } + + else if (this instanceof Astal.Overlay) { + const [child, ...overlays] = children + this.set_child(child) + this.set_overlays(overlays) + } + + else if (this instanceof Gtk.Container) { + for (const ch of children) + this.add(ch) + } } - } - // setup signal handlers - for (const [signal, callback] of onHandlers) { - if (typeof callback === "function") { - self.connect(signal, callback) + toggleClassName(cn: string, cond = true) { + Astal.widget_toggle_class_name(this, cn, cond) } - else { - self.connect(signal, () => execAsync(callback) - .then(print).catch(console.error)) + + hook( + object: Connectable, + signal: string, + callback: (self: this, ...args: any[]) => void, + ): this + hook( + object: Subscribable, + callback: (self: this, ...args: any[]) => void, + ): this + hook( + object: Connectable | Subscribable, + signalOrCallback: string | ((self: this, ...args: any[]) => void), + callback?: (self: this, ...args: any[]) => void, + ) { + if (typeof object.connect === "function" && callback) { + const id = object.connect(signalOrCallback, (_: any, ...args: unknown[]) => { + callback(this, ...args) + }) + this.connect("destroy", () => { + (object.disconnect as Connectable["disconnect"])(id) + }) + } + + else if (typeof object.subscribe === "function" && typeof signalOrCallback === "function") { + const unsub = object.subscribe((...args: unknown[]) => { + signalOrCallback(this, ...args) + }) + this.connect("destroy", unsub) + } + + return this } - } - // setup bindings handlers - for (const [prop, binding] of bindings) { - if (prop === "child" || prop === "children") { - self.connect("destroy", binding.subscribe((v: any) => { - setChildren(self, v) - })) + constructor(...params: any[]) { + super() + const [config] = params + + const { setup, child, children = [], ...props } = config + props.visible ??= true + + if (child) + children.unshift(child) + + // collect bindings + const bindings = Object.keys(props).reduce((acc: any, prop) => { + if (props[prop] instanceof Binding) { + const binding = props[prop] + delete props[prop] + return [...acc, [prop, binding]] + } + return acc + }, []) + + // collect signal handlers + const onHandlers = Object.keys(props).reduce((acc: any, key) => { + if (key.startsWith("on")) { + const sig = kebabify(key).split("-").slice(1).join("-") + const handler = props[key] + delete props[key] + return [...acc, [sig, handler]] + } + return acc + }, []) + + // set children + const mergedChildren = mergeBindings(children.flat(Infinity)) + if (mergedChildren instanceof Binding) { + this._setChildren(mergedChildren.get()) + this.connect("destroy", mergedChildren.subscribe((v) => { + this._setChildren(v) + })) + } + else { + if (mergedChildren.length > 0) { + this._setChildren(mergedChildren) + } + } + + // setup signal handlers + for (const [signal, callback] of onHandlers) { + if (typeof callback === "function") { + this.connect(signal, callback) + } + else { + this.connect(signal, () => execAsync(callback) + .then(print).catch(console.error)) + } + } + + // setup bindings handlers + for (const [prop, binding] of bindings) { + if (prop === "child" || prop === "children") { + this.connect("destroy", binding.subscribe((v: any) => { + this._setChildren(v) + })) + } + this.connect("destroy", binding.subscribe((v: any) => { + setProp(this, prop, v) + })) + setProp(this, prop, binding.get()) + } + + Object.assign(this, props) + setup?.(this) } - self.connect("destroy", binding.subscribe((v: any) => { - setProp(self, prop, v) - })) - setProp(self, prop, binding.get()) } - Object.assign(self, props) - setup?.(self) - return self -} - -function proxify< - C extends typeof Gtk.Widget, ->(klass: C) { - Object.defineProperty(klass.prototype, "className", { - get() { return Astal.widget_get_class_names(this).join(" ") }, - set(v) { Astal.widget_set_class_names(this, v.split(/\s+/)) }, - }) - - Object.defineProperty(klass.prototype, "css", { - get() { return Astal.widget_get_css(this) }, - set(v) { Astal.widget_set_css(this, v) }, - }) - - Object.defineProperty(klass.prototype, "cursor", { - get() { return Astal.widget_get_cursor(this) }, - set(v) { Astal.widget_set_cursor(this, v) }, - }) - - Object.defineProperty(klass.prototype, "clickThrough", { - get() { return Astal.widget_get_click_through(this) }, - set(v) { Astal.widget_set_click_through(this, v) }, - }) - - Object.assign(klass.prototype, { - hook: function (obj: any, sig: any, callback: any) { - return hook(this as InstanceType<C>, obj, sig, callback) - }, - toggleClassName: function name(cn: string, cond = true) { - Astal.widget_toggle_class_name(this as InstanceType<C>, cn, cond) - }, - set_class_name: function (name: string) { - // @ts-expect-error unknown key - this.className = name - }, - set_css: function (css: string) { - // @ts-expect-error unknown key - this.css = css + GObject.registerClass({ + GTypeName: `Astal_${cls.name}`, + Properties: { + "class-name": GObject.ParamSpec.string( + "class-name", "", "", GObject.ParamFlags.READWRITE, "", + ), + "css": GObject.ParamSpec.string( + "css", "", "", GObject.ParamFlags.READWRITE, "", + ), + "cursor": GObject.ParamSpec.string( + "cursor", "", "", GObject.ParamFlags.READWRITE, "default", + ), + "click-through": GObject.ParamSpec.boolean( + "click-through", "", "", GObject.ParamFlags.READWRITE, false, + ), + "no-implicit-destroy": GObject.ParamSpec.boolean( + "no-implicit-destroy", "", "", GObject.ParamFlags.READWRITE, false, + ), }, - set_cursor: function (cursor: string) { - // @ts-expect-error unknown key - this.cursor = cursor - }, - set_click_through: function (clickThrough: boolean) { - // @ts-expect-error unknown key - this.clickThrough = clickThrough - }, - }) - - const proxy = new Proxy(klass, { - construct(_, [conf, ...children]) { - // @ts-expect-error abstract class - return ctor(new klass(), conf, children) - }, - apply(_t, _a, [conf, ...children]) { - // @ts-expect-error abstract class - return ctor(new klass(), conf, children) - }, - }) + }, Widget) - return proxy -} - -export default function astalify< - C extends typeof Gtk.Widget, - P extends Record<string, any>, - N extends string = "Widget", ->(klass: C) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - type Astal<N> = Omit<C, "new"> & { - new(props?: P, ...children: Gtk.Widget[]): Widget<InstanceType<C>> - (props?: P, ...children: Gtk.Widget[]): Widget<InstanceType<C>> - } - - return proxify(klass) as unknown as Astal<N> + return Widget } type BindableProps<T> = { @@ -282,7 +256,7 @@ type BindableProps<T> = { type SigHandler< W extends InstanceType<typeof Gtk.Widget>, Args extends Array<unknown>, -> = ((self: Widget<W>, ...args: Args) => unknown) | string | string[] +> = ((self: W, ...args: Args) => unknown) | string | string[] export type ConstructProps< Self extends InstanceType<typeof Gtk.Widget>, @@ -299,16 +273,18 @@ export type ConstructProps< cursor?: string clickThrough?: boolean }> & { - onDestroy?: (self: Widget<Self>) => unknown - onDraw?: (self: Widget<Self>) => unknown - onKeyPressEvent?: (self: Widget<Self>, event: Gdk.Event) => unknown - onKeyReleaseEvent?: (self: Widget<Self>, event: Gdk.Event) => unknown - onButtonPressEvent?: (self: Widget<Self>, event: Gdk.Event) => unknown - onButtonReleaseEvent?: (self: Widget<Self>, event: Gdk.Event) => unknown - onRealize?: (self: Widget<Self>) => unknown - setup?: (self: Widget<Self>) => void + onDestroy?: (self: Self) => unknown + onDraw?: (self: Self) => unknown + onKeyPressEvent?: (self: Self, event: Gdk.Event) => unknown + onKeyReleaseEvent?: (self: Self, event: Gdk.Event) => unknown + onButtonPressEvent?: (self: Self, event: Gdk.Event) => unknown + onButtonReleaseEvent?: (self: Self, event: Gdk.Event) => unknown + onRealize?: (self: Self) => unknown + setup?: (self: Self) => void } +export type BindableChild = Gtk.Widget | Binding<Gtk.Widget> + type Cursor = | "default" | "help" diff --git a/core/gjs/src/gobject.ts b/core/gjs/src/gobject.ts new file mode 100644 index 0000000..2658555 --- /dev/null +++ b/core/gjs/src/gobject.ts @@ -0,0 +1,178 @@ +import GObject from "gi://GObject" +export default GObject + +const meta = Symbol("meta") + +const { ParamSpec, ParamFlags } = GObject + +const kebabify = (str: string) => str + .replace(/([a-z])([A-Z])/g, "$1-$2") + .replaceAll("_", "-") + .toLowerCase() + +type SignalDeclaration = { + flags?: GObject.SignalFlags + accumulator?: GObject.AccumulatorType + return_type?: GObject.GType + param_types?: Array<GObject.GType> +} + +type PropertyDeclaration = + | InstanceType<typeof GObject.ParamSpec> + | { $gtype: GObject.GType } + | typeof String + | typeof Number + | typeof Boolean + | typeof Object + +type GObjectConstructor = { + [meta]?: { + Properties?: { [key: string]: GObject.ParamSpec } + Signals?: { [key: string]: GObject.SignalDefinition } + } + new(...args: any[]): any +} + +type MetaInfo = GObject.MetaInfo<never, Array<{ $gtype: GObject.GType }>, never> + +export function register(options: MetaInfo = {}) { + return function (cls: GObjectConstructor) { + GObject.registerClass({ + Signals: { ...cls[meta]?.Signals }, + Properties: { ...cls[meta]?.Properties }, + ...options, + }, cls) + } +} + +export function property(declaration: PropertyDeclaration = Object) { + return function (target: any, prop: any, desc?: PropertyDescriptor) { + target.constructor[meta] ??= {} + target.constructor[meta].Properties ??= {} + + const name = kebabify(prop) + + if (!desc) { + let value = defaultValue(declaration) + + Object.defineProperty(target, prop, { + get() { + return value + }, + set(v) { + if (v !== value) { + value = v + this.notify(name) + } + }, + }) + + Object.defineProperty(target, `set_${name.replace("-", "_")}`, { + value: function (v: any) { + this[prop] = v + } + }) + + Object.defineProperty(target, `get_${name.replace("-", "_")}`, { + value: function () { + return this[prop] + } + }) + + target.constructor[meta].Properties[kebabify(prop)] = pspec(name, ParamFlags.READWRITE, declaration) + } + + else { + let flags = 0 + if (desc.get) flags |= ParamFlags.READABLE + if (desc.set) flags |= ParamFlags.WRITABLE + + target.constructor[meta].Properties[kebabify(prop)] = pspec(name, flags, declaration) + } + } +} + +export function signal(...params: Array<{ $gtype: GObject.GType } | typeof Object>): + (target: any, signal: any, desc?: PropertyDescriptor) => void + +export function signal(declaration?: SignalDeclaration): + (target: any, signal: any, desc?: PropertyDescriptor) => void + +export function signal( + declaration?: SignalDeclaration | { $gtype: GObject.GType } | typeof Object, + ...params: Array<{ $gtype: GObject.GType } | typeof Object> +) { + return function (target: any, signal: any, desc?: PropertyDescriptor) { + target.constructor[meta] ??= {} + target.constructor[meta].Signals ??= {} + + const name = kebabify(signal) + + if (declaration || params.length > 0) { + // @ts-expect-error TODO: type assert + const arr = [declaration, ...params].map(v => v.$gtype) + target.constructor[meta].Signals[name] = { + param_types: arr, + } + } + else { + target.constructor[meta].Signals[name] = declaration + } + + if (!desc) { + Object.defineProperty(target, signal, { + value: function (...args: any[]) { + this.emit(name, ...args) + }, + }) + } + else { + const og: ((...args: any[]) => void) = desc.value + desc.value = function (...args: any[]) { + // @ts-expect-error not typed + this.emit(name, ...args) + } + Object.defineProperty(target, `on_${name.replace("-", "_")}`, { + value: function (...args: any[]) { + return og(...args) + }, + }) + } + } +} + +function pspec(name: string, flags: number, declaration: PropertyDeclaration) { + if (declaration instanceof ParamSpec) + return declaration + + switch (declaration) { + case String: + return ParamSpec.string(name, "", "", flags, "") + case Number: + return ParamSpec.double(name, "", "", flags, -Number.MAX_VALUE, Number.MAX_VALUE, 0) + case Boolean: + return ParamSpec.boolean(name, "", "", flags, false) + case Object: + return ParamSpec.jsobject(name, "", "", flags) + default: + // @ts-expect-error misstyped + return ParamSpec.object(name, "", "", flags, declaration.$gtype) + } +} + +function defaultValue(declaration: PropertyDeclaration) { + if (declaration instanceof ParamSpec) + return declaration.get_default_value() + + switch (declaration) { + case String: + return "default-string" + case Number: + return 0 + case Boolean: + return false + case Object: + default: + return null + } +} diff --git a/core/gjs/src/jsx/jsx-runtime.ts b/core/gjs/src/jsx/jsx-runtime.ts index a0aafb6..f40dc05 100644 --- a/core/gjs/src/jsx/jsx-runtime.ts +++ b/core/gjs/src/jsx/jsx-runtime.ts @@ -16,14 +16,15 @@ export function jsx( children = children.filter(Boolean) - if (typeof ctor === "string") - return (ctors as any)[ctor](props, children) - if (children.length === 1) props.child = children[0] else if (children.length > 1) props.children = children + if (typeof ctor === "string") { + return new ctors[ctor](props) + } + if (isArrowFunction(ctor)) return ctor(props) @@ -63,7 +64,7 @@ declare global { box: Widget.BoxProps button: Widget.ButtonProps centerbox: Widget.CenterBoxProps - circularprogress: Widget.CircularProgressProps, + circularprogress: Widget.CircularProgressProps drawingarea: Widget.DrawingAreaProps entry: Widget.EntryProps eventbox: Widget.EventBoxProps diff --git a/core/gjs/src/widgets.ts b/core/gjs/src/widgets.ts index 82d3b8f..e14ca0b 100644 --- a/core/gjs/src/widgets.ts +++ b/core/gjs/src/widgets.ts @@ -1,18 +1,23 @@ /* eslint-disable max-len */ -import { Astal, Gtk } from "./imports.js" -import astalify, { type ConstructProps, type Widget } from "./astalify.js" +import { Astal, GObject, Gtk } from "./imports.js" +import astalify, { type ConstructProps, type BindableChild } from "./astalify.js" export { astalify, ConstructProps } // Box -export type Box = Widget<Astal.Box> -export const Box = astalify<typeof Astal.Box, BoxProps, "Box">(Astal.Box) -export type BoxProps = ConstructProps<Astal.Box, Astal.Box.ConstructorProps> +Object.defineProperty(Astal.Box.prototype, "children", { + get() { return this.get_children() }, + set(v) { this.set_children(v) }, +}) + +export type BoxProps = ConstructProps<Box, Astal.Box.ConstructorProps> +export class Box extends astalify(Astal.Box) { + static { GObject.registerClass({ GTypeName: "Box" }, this) } + constructor(props?: BoxProps, ...children: Array<BindableChild>) { super({ children, ...props } as any) } +} // Button -export type Button = Widget<Astal.Button> -export const Button = astalify<typeof Astal.Button, ButtonProps, "Button">(Astal.Button) -export type ButtonProps = ConstructProps<Astal.Button, Astal.Button.ConstructorProps, { +export type ButtonProps = ConstructProps<Button, Astal.Button.ConstructorProps, { onClicked: [] onClick: [event: Astal.ClickEvent] onClickRelease: [event: Astal.ClickEvent] @@ -20,97 +25,130 @@ export type ButtonProps = ConstructProps<Astal.Button, Astal.Button.ConstructorP onHoverLost: [event: Astal.HoverEvent] onScroll: [event: Astal.ScrollEvent] }> +export class Button extends astalify(Astal.Button) { + static { GObject.registerClass({ GTypeName: "Button" }, this) } + constructor(props?: ButtonProps, child?: BindableChild) { super({ child, ...props } as any) } +} // CenterBox -export type CenterBox = Widget<Astal.CenterBox> -export const CenterBox = astalify<typeof Astal.CenterBox, CenterBoxProps, "CenterBox">(Astal.CenterBox) -export type CenterBoxProps = ConstructProps<Astal.CenterBox, Astal.CenterBox.ConstructorProps> +export type CenterBoxProps = ConstructProps<CenterBox, Astal.CenterBox.ConstructorProps> +export class CenterBox extends astalify(Astal.CenterBox) { + static { GObject.registerClass({ GTypeName: "CenterBox" }, this) } + constructor(props?: CenterBoxProps, ...children: Array<BindableChild>) { super({ children, ...props } as any) } +} // CircularProgress -export type CircularProgress = Widget<Astal.CircularProgress> -export const CircularProgress = astalify<typeof Astal.CircularProgress, CircularProgressProps, "CircularProgress">(Astal.CircularProgress) -export type CircularProgressProps = ConstructProps<Astal.CircularProgress, Astal.CircularProgress.ConstructorProps> - +export type CircularProgressProps = ConstructProps<CircularProgress, Astal.CircularProgress.ConstructorProps> +export class CircularProgress extends astalify(Astal.CircularProgress) { + static { GObject.registerClass({ GTypeName: "CircularProgress" }, this) } + constructor(props?: CircularProgressProps, child?: BindableChild) { super({ child, ...props } as any) } +} // DrawingArea -export type DrawingArea = Widget<Gtk.DrawingArea> -export const DrawingArea = astalify<typeof Gtk.DrawingArea, DrawingAreaProps, "DrawingArea">(Gtk.DrawingArea) -export type DrawingAreaProps = ConstructProps<Gtk.DrawingArea, Gtk.DrawingArea.ConstructorProps, { +export type DrawingAreaProps = ConstructProps<DrawingArea, Gtk.DrawingArea.ConstructorProps, { onDraw: [cr: any] // TODO: cairo types }> +export class DrawingArea extends astalify(Gtk.DrawingArea) { + static { GObject.registerClass({ GTypeName: "DrawingArea" }, this) } + constructor(props?: DrawingAreaProps) { super(props as any) } +} // Entry -export type Entry = Widget<Gtk.Entry> -export const Entry = astalify<typeof Gtk.Entry, EntryProps, "Entry">(Gtk.Entry) -export type EntryProps = ConstructProps<Gtk.Entry, Gtk.Entry.ConstructorProps, { +export type EntryProps = ConstructProps<Entry, Gtk.Entry.ConstructorProps, { onChanged: [] onActivate: [] }> +export class Entry extends astalify(Gtk.Entry) { + static { GObject.registerClass({ GTypeName: "Entry" }, this) } + constructor(props?: EntryProps) { super(props as any) } +} // EventBox -export type EventBox = Widget<Astal.EventBox> -export const EventBox = astalify<typeof Astal.EventBox, EventBoxProps, "EventBox">(Astal.EventBox) -export type EventBoxProps = ConstructProps<Astal.EventBox, Astal.EventBox.ConstructorProps, { +export type EventBoxProps = ConstructProps<EventBox, Astal.EventBox.ConstructorProps, { onClick: [event: Astal.ClickEvent] onClickRelease: [event: Astal.ClickEvent] onHover: [event: Astal.HoverEvent] onHoverLost: [event: Astal.HoverEvent] onScroll: [event: Astal.ScrollEvent] }> - -// TODO: Fixed -// TODO: FlowBox - +export class EventBox extends astalify(Astal.EventBox) { + static { GObject.registerClass({ GTypeName: "EventBox" }, this) } + constructor(props?: EventBoxProps, child?: BindableChild) { super({ child, ...props } as any) } +} + +// // TODO: Fixed +// // TODO: FlowBox +// // Icon -export type Icon = Widget<Astal.Icon> -export const Icon = astalify<typeof Astal.Icon, IconProps, "Icon">(Astal.Icon) -export type IconProps = ConstructProps<Astal.Icon, Astal.Icon.ConstructorProps> +export type IconProps = ConstructProps<Icon, Astal.Icon.ConstructorProps> +export class Icon extends astalify(Astal.Icon) { + static { GObject.registerClass({ GTypeName: "Icon" }, this) } + constructor(props?: IconProps) { super(props as any) } +} // Label -export type Label = Widget<Astal.Label> -export const Label = astalify<typeof Astal.Label, LabelProps, "Label">(Astal.Label) -export type LabelProps = ConstructProps<Astal.Label, Astal.Label.ConstructorProps> +export type LabelProps = ConstructProps<Label, Astal.Label.ConstructorProps> +export class Label extends astalify(Astal.Label) { + static { GObject.registerClass({ GTypeName: "Label" }, this) } + constructor(props?: LabelProps) { super(props as any) } +} // LevelBar -export type LevelBar = Widget<Astal.LevelBar> -export const LevelBar = astalify<typeof Astal.LevelBar, LevelBarProps, "LevelBar">(Astal.LevelBar) -export type LevelBarProps = ConstructProps<Astal.LevelBar, Astal.LevelBar.ConstructorProps> +export type LevelBarProps = ConstructProps<LevelBar, Astal.LevelBar.ConstructorProps> +export class LevelBar extends astalify(Astal.LevelBar) { + static { GObject.registerClass({ GTypeName: "LevelBar" }, this) } + constructor(props?: LevelBarProps) { super(props as any) } +} // TODO: ListBox // Overlay -export type Overlay = Widget<Astal.Overlay> -export const Overlay = astalify<typeof Astal.Overlay, OverlayProps, "Overlay">(Astal.Overlay) -export type OverlayProps = ConstructProps<Astal.Overlay, Astal.Overlay.ConstructorProps> +export type OverlayProps = ConstructProps<Overlay, Astal.Overlay.ConstructorProps> +export class Overlay extends astalify(Astal.Overlay) { + static { GObject.registerClass({ GTypeName: "Overlay" }, this) } + constructor(props?: OverlayProps, ...children: Array<BindableChild>) { super({ children, ...props } as any) } +} // Revealer -export type Revealer = Widget<Gtk.Revealer> -export const Revealer = astalify<typeof Gtk.Revealer, RevealerProps, "Revealer">(Gtk.Revealer) -export type RevealerProps = ConstructProps<Gtk.Revealer, Gtk.Revealer.ConstructorProps> +export type RevealerProps = ConstructProps<Revealer, Gtk.Revealer.ConstructorProps> +export class Revealer extends astalify(Gtk.Revealer) { + static { GObject.registerClass({ GTypeName: "Revealer" }, this) } + constructor(props?: RevealerProps, child?: BindableChild) { super({ child, ...props } as any) } +} // Scrollable -export type Scrollable = Widget<Astal.Scrollable> -export const Scrollable = astalify<typeof Astal.Scrollable, ScrollableProps, "Scrollable">(Astal.Scrollable) -export type ScrollableProps = ConstructProps<Astal.Scrollable, Astal.Scrollable.ConstructorProps> +export type ScrollableProps = ConstructProps<Scrollable, Astal.Scrollable.ConstructorProps> +export class Scrollable extends astalify(Astal.Scrollable) { + static { GObject.registerClass({ GTypeName: "Scrollable" }, this) } + constructor(props?: ScrollableProps, child?: BindableChild) { super({ child, ...props } as any) } +} // Slider -export type Slider = Widget<Astal.Slider> -export const Slider = astalify<typeof Astal.Slider, SliderProps, "Slider">(Astal.Slider) -export type SliderProps = ConstructProps<Astal.Slider, Astal.Slider.ConstructorProps, { +export type SliderProps = ConstructProps<Slider, Astal.Slider.ConstructorProps, { onDragged: [] }> +export class Slider extends astalify(Astal.Slider) { + static { GObject.registerClass({ GTypeName: "Slider" }, this) } + constructor(props?: SliderProps) { super(props as any) } +} // Stack -export type Stack = Widget<Astal.Stack> -export const Stack = astalify<typeof Astal.Stack, StackProps, "Stack">(Astal.Stack) -export type StackProps = ConstructProps<Astal.Stack, Astal.Stack.ConstructorProps> +export type StackProps = ConstructProps<Stack, Astal.Stack.ConstructorProps> +export class Stack extends astalify(Astal.Stack) { + static { GObject.registerClass({ GTypeName: "Stack" }, this) } + constructor(props?: StackProps, ...children: Array<BindableChild>) { super({ children, ...props } as any) } +} // Switch -export type Switch = Widget<Gtk.Switch> -export const Switch = astalify<typeof Gtk.Switch, SwitchProps, "Switch">(Gtk.Switch) -export type SwitchProps = ConstructProps<Gtk.Switch, Gtk.Switch.ConstructorProps> +export type SwitchProps = ConstructProps<Switch, Gtk.Switch.ConstructorProps> +export class Switch extends astalify(Gtk.Switch) { + static { GObject.registerClass({ GTypeName: "Switch" }, this) } + constructor(props?: SwitchProps) { super(props as any) } +} // Window -export type Window = Widget<Astal.Window> -export const Window = astalify<typeof Astal.Window, WindowProps, "Window">(Astal.Window) -export type WindowProps = ConstructProps<Astal.Window, Astal.Window.ConstructorProps> +export type WindowProps = ConstructProps<Window, Astal.Window.ConstructorProps> +export class Window extends astalify(Astal.Window) { + static { GObject.registerClass({ GTypeName: "Window" }, this) } + constructor(props?: WindowProps, child?: BindableChild) { super({ child, ...props } as any) } +} diff --git a/core/gjs/tsconfig.json b/core/gjs/tsconfig.json index b93779f..b535a07 100644 --- a/core/gjs/tsconfig.json +++ b/core/gjs/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "experimentalDecorators": true, "target": "ESNext", "module": "ESNext", "lib": [ @@ -13,11 +14,12 @@ "checkJs": true, "allowJs": true, "jsx": "react-jsx", - "jsxImportSource": "./src/jsx", + "jsxImportSource": "src/jsx", + "baseUrl": ".", }, "include": [ - "./node_modules/@girs", - "./src/**/*", - "./index.ts", + "@girs", + "src/**/*", + "index.ts" ] } diff --git a/core/lua/astal/variable.lua b/core/lua/astal/variable.lua index df83e5f..662eee7 100644 --- a/core/lua/astal/variable.lua +++ b/core/lua/astal/variable.lua @@ -251,7 +251,7 @@ function Variable.derive(deps, transform) for i, binding in ipairs(deps) do params[i] = binding:get() end - return transform(table.unpack(params)) + return transform(table.unpack(params), 1, #deps) end local var = Variable.new(update()) diff --git a/core/lua/astal/widget.lua b/core/lua/astal/widget.lua index 89cc4d5..8f49409 100644 --- a/core/lua/astal/widget.lua +++ b/core/lua/astal/widget.lua @@ -43,6 +43,15 @@ flatten = function(tbl) return copy end +local function includes(tbl, elem) + for _, value in pairs(tbl) do + if value == elem then + return true + end + end + return false +end + local function set_children(parent, children) children = map(flatten(children), function(item) if Gtk.Widget:is_type_of(item) then @@ -56,15 +65,19 @@ local function set_children(parent, children) -- remove if Gtk.Bin:is_type_of(parent) then - local rm = parent:get_child() - if rm ~= nil then - parent:remove(rm) + local ch = parent:get_child() + if ch ~= nil then + parent:remove(ch) + end + if ch ~= nil and not includes(children, ch) and not parent.no_implicit_destroy then + ch:destroy() end - elseif Gtk.Container:is_type_of(parent) and - not (Astal.Box:is_type_of(parent) or - Astal.Stack:is_type_of(parent)) then + elseif Gtk.Container:is_type_of(parent) then for _, ch in ipairs(parent:get_children()) do parent:remove(ch) + if ch ~= nil and not includes(children, ch) and not parent.no_implicit_destroy then + ch:destroy() + end end end @@ -177,7 +190,7 @@ local function astalify(ctor) for prop, value in pairs(props) do if string.sub(prop, 0, 2) == "on" and type(value) ~= "function" then props[prop] = function() - exec_async(value, print, print) + exec_async(value, print) end end end @@ -282,6 +295,21 @@ Gtk.Widget._attribute.click_through = { set = Astal.widget_set_click_through, } +local no_implicit_destroy = {} +Gtk.Widget._attribute.no_implicit_destroy = { + get = function(self) + return no_implicit_destroy[self] or false + end, + set = function(self, v) + if no_implicit_destroy[self] == nil then + self.on_destroy = function() + no_implicit_destroy[self] = nil + end + end + no_implicit_destroy[self] = v + end, +} + Astal.Box._attribute.children = { get = Astal.Box.get_children, set = Astal.Box.set_children, diff --git a/core/src/widget/box.vala b/core/src/widget/box.vala index 0008fd4..943c821 100644 --- a/core/src/widget/box.vala +++ b/core/src/widget/box.vala @@ -6,11 +6,6 @@ public class Box : Gtk.Box { set { orientation = value ? Gtk.Orientation.VERTICAL : Gtk.Orientation.HORIZONTAL; } } - /** - * whether to implicity destroy previous children when setting them - */ - public bool no_implicit_destroy { get; set; default = false; } - public List<weak Gtk.Widget> children { set { _set_children(value); } owned get { return get_children(); } @@ -42,11 +37,7 @@ public class Box : Gtk.Box { private void _set_children(List<weak Gtk.Widget> arr) { foreach(var child in get_children()) { - if (!no_implicit_destroy && arr.find(child).length() == 0) { - child.destroy(); - } else { - remove(child); - } + remove(child); } foreach(var child in arr) diff --git a/core/src/widget/button.vala b/core/src/widget/button.vala index 4d1f467..ad60da1 100644 --- a/core/src/widget/button.vala +++ b/core/src/widget/button.vala @@ -33,11 +33,11 @@ public class Button : Gtk.Button { } public enum MouseButton { - PRIMARY, - MIDDLE, - SECONDARY, - BACK, - FORWARD, + PRIMARY = 1, + MIDDLE = 2, + SECONDARY = 3, + BACK = 4, + FORWARD = 5, } // these structs are here because gjs converts every event diff --git a/core/src/widget/stack.vala b/core/src/widget/stack.vala index 00adf7f..02f9959 100644 --- a/core/src/widget/stack.vala +++ b/core/src/widget/stack.vala @@ -1,9 +1,4 @@ public class Astal.Stack : Gtk.Stack { - /** - * whether to implicity destroy previous children when setting them - */ - public bool no_implicit_destroy { get; set; default = false; } - public string shown { get { return visible_child_name; } set { visible_child_name = value; } @@ -16,11 +11,7 @@ public class Astal.Stack : Gtk.Stack { private void _set_children(List<weak Gtk.Widget> arr) { foreach(var child in get_children()) { - if (!no_implicit_destroy && arr.find(child).length() == 0) { - child.destroy(); - } else { - remove(child); - } + remove(child); } var i = 0; |