diff options
author | Aylur <[email protected]> | 2024-09-01 02:06:50 +0200 |
---|---|---|
committer | Aylur <[email protected]> | 2024-09-01 02:06:50 +0200 |
commit | 0738194780ce52b9047c430b9498487417b2cd07 (patch) | |
tree | e0cdbcbc31ccbe1620a5f228f18fead658487652 /gjs/src | |
parent | 49cdf5d5e010c9abb9b02034999eef2f49f066b9 (diff) |
move libastal to /core
starting point of the monorepo
Diffstat (limited to 'gjs/src')
-rw-r--r-- | gjs/src/application.ts | 105 | ||||
-rw-r--r-- | gjs/src/astalify.ts | 331 | ||||
-rw-r--r-- | gjs/src/binding.ts | 88 | ||||
-rw-r--r-- | gjs/src/file.ts | 44 | ||||
-rw-r--r-- | gjs/src/imports.ts | 10 | ||||
-rw-r--r-- | gjs/src/jsx/jsx-runtime.ts | 87 | ||||
-rw-r--r-- | gjs/src/process.ts | 69 | ||||
-rw-r--r-- | gjs/src/time.ts | 13 | ||||
-rw-r--r-- | gjs/src/variable.ts | 227 | ||||
-rw-r--r-- | gjs/src/widgets.ts | 109 |
10 files changed, 0 insertions, 1083 deletions
diff --git a/gjs/src/application.ts b/gjs/src/application.ts deleted file mode 100644 index 0ba247e..0000000 --- a/gjs/src/application.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Astal, GObject, Gio, GLib } from "./imports.js" - -type RequestHandler = { - (request: string, res: (response: any) => void): void -} - -type Config = Partial<{ - icons: string - instanceName: string - gtkTheme: string - iconTheme: string - cursorTheme: string - css: string - requestHandler: RequestHandler - main(...args: string[]): void - client(message: (msg: string) => string, ...args: string[]): void - hold: boolean -}> - -// @ts-expect-error missing types -// https://github.com/gjsify/ts-for-gir/issues/164 -import { setConsoleLogDomain } from "console" -import { exit, programArgs } from "system" - -class AstalJS extends Astal.Application { - static { GObject.registerClass(this) } - - eval(body: string): Promise<any> { - return new Promise((res, rej) => { - try { - const fn = Function(`return (async function() { - ${body.includes(";") ? body : `return ${body};`} - })`) - fn()() - .then(res) - .catch(rej) - } - catch (error) { - rej(error) - } - }) - } - - requestHandler?: RequestHandler - - vfunc_request(msg: string, conn: Gio.SocketConnection): void { - if (typeof this.requestHandler === "function") { - this.requestHandler(msg, (response) => { - Astal.write_sock(conn, String(response), (_, res) => - Astal.write_sock_finish(res), - ) - }) - } - else { - super.vfunc_request(msg, conn) - } - } - - apply_css(style: string, reset = false) { - super.apply_css(style, reset) - } - - quit(code?: number): void { - super.quit() - exit(code ?? 0) - } - - start({ requestHandler, css, hold, main, client, icons, ...cfg }: Config = {}) { - client ??= () => { - print(`Astal instance "${this.instanceName}" already running`) - exit(1) - } - - Object.assign(this, cfg) - setConsoleLogDomain(this.instanceName) - - this.requestHandler = requestHandler - this.connect("activate", () => { - const path: string[] = import.meta.url.split("/").slice(3) - const file = path.at(-1)!.replace(".js", ".css") - const css = `/${path.slice(0, -1).join("/")}/${file}` - if (file.endsWith(".css") && GLib.file_test(css, GLib.FileTest.EXISTS)) - this.apply_css(css, false) - - main?.(...programArgs) - }) - - if (!this.acquire_socket()) - return client(msg => Astal.Application.send_message(this.instanceName, msg)!, ...programArgs) - - if (css) - this.apply_css(css, false) - - if (icons) - this.add_icons(icons) - - hold ??= true - if (hold) - this.hold() - - this.runAsync([]) - } -} - -export default new AstalJS() diff --git a/gjs/src/astalify.ts b/gjs/src/astalify.ts deleted file mode 100644 index be395ee..0000000 --- a/gjs/src/astalify.ts +++ /dev/null @@ -1,331 +0,0 @@ -import Binding, { kebabify, snakeify, type Connectable, type Subscribable } from "./binding.js" -import { Astal, Gtk, Gdk } 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) - } - - // FIXME: add rest of the edge cases like Stack - if (parent instanceof Astal.Box) { - 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 - return array.map(value => value instanceof Binding - ? args[i++] - : value, - ) - } - - const bindings = array.filter(i => i instanceof Binding) - - if (bindings.length === 0) - return array - - if (bindings.length === 1) - return bindings[0].as(getValues) - - return Variable.derive(bindings, getValues)() -} - -function setProp(obj: any, prop: string, value: any) { - try { - const setter = `set_${snakeify(prop)}` - if (typeof obj[setter] === "function") - return obj[setter](value) - - if (Object.hasOwn(obj, prop)) - 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 - - const bindings = Object.keys(props).reduce((acc: any, prop) => { - if (props[prop] instanceof Binding) { - const binding = props[prop] - setProp(self, prop, binding.get()) - delete props[prop] - return [...acc, [prop, binding]] - } - return acc - }, []) - - 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 - }, []) - - Object.assign(self, props) - - for (const [signal, callback] of onHandlers) { - if (typeof callback === "function") { - self.connect(signal, callback) - } - else { - self.connect(signal, () => execAsync(callback) - .then(print).catch(console.error)) - } - } - - for (const [prop, bind] of bindings) { - if (prop === "child" || prop === "children") { - self.connect("destroy", bind.subscribe((v: any) => { - setChildren(self, v) - })) - } - self.connect("destroy", bind.subscribe((v: any) => { - setProp(self, prop, v) - })) - } - - 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) - } - - 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 - }, - 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) - }, - }) - - 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> -} - -type BindableProps<T> = { - [K in keyof T]: Binding<T[K]> | T[K]; -} - -type SigHandler< - W extends InstanceType<typeof Gtk.Widget>, - Args extends Array<unknown>, -> = ((self: Widget<W>, ...args: Args) => unknown) | string | string[] - -export type ConstructProps< - Self extends InstanceType<typeof Gtk.Widget>, - Props extends Gtk.Widget.ConstructorProps, - Signals extends Record<`on${string}`, Array<unknown>> = Record<`on${string}`, any[]>, -> = Partial<{ - // @ts-expect-error can't assign to unknown, but it works as expected though - [S in keyof Signals]: SigHandler<Self, Signals[S]> -}> & Partial<{ - [Key in `on${string}`]: SigHandler<Self, any[]> -}> & BindableProps<Partial<Props> & { - className?: string - css?: string - 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 -} - -type Cursor = - | "default" - | "help" - | "pointer" - | "context-menu" - | "progress" - | "wait" - | "cell" - | "crosshair" - | "text" - | "vertical-text" - | "alias" - | "copy" - | "no-drop" - | "move" - | "not-allowed" - | "grab" - | "grabbing" - | "all-scroll" - | "col-resize" - | "row-resize" - | "n-resize" - | "e-resize" - | "s-resize" - | "w-resize" - | "ne-resize" - | "nw-resize" - | "sw-resize" - | "se-resize" - | "ew-resize" - | "ns-resize" - | "nesw-resize" - | "nwse-resize" - | "zoom-in" - | "zoom-out" diff --git a/gjs/src/binding.ts b/gjs/src/binding.ts deleted file mode 100644 index feec6fc..0000000 --- a/gjs/src/binding.ts +++ /dev/null @@ -1,88 +0,0 @@ -export const snakeify = (str: string) => str - .replace(/([a-z])([A-Z])/g, "$1_$2") - .replaceAll("-", "_") - .toLowerCase() - -export const kebabify = (str: string) => str - .replace(/([a-z])([A-Z])/g, "$1-$2") - .replaceAll("_", "-") - .toLowerCase() - -export interface Subscribable<T = unknown> { - subscribe(callback: (value: T) => void): () => void - get(): T - [key: string]: any -} - -export interface Connectable { - connect(signal: string, callback: (...args: any[]) => unknown): number - disconnect(id: number): void - [key: string]: any -} - -export default class Binding<Value> { - private emitter: Subscribable<Value> | Connectable - private prop?: string - private transformFn = (v: any) => v - - static bind< - T extends Connectable, - P extends keyof T, - >(object: T, property: P): Binding<T[P]> - - static bind<T>(object: Subscribable<T>): Binding<T> - - static bind(emitter: Connectable | Subscribable, prop?: string) { - return new Binding(emitter, prop) - } - - private constructor(emitter: Connectable | Subscribable<Value>, prop?: string) { - this.emitter = emitter - this.prop = prop && kebabify(prop) - } - - toString() { - return `Binding<${this.emitter}${this.prop ? `, "${this.prop}"` : ""}>` - } - - as<T>(fn: (v: Value) => T): Binding<T> { - const bind = new Binding(this.emitter, this.prop) - bind.transformFn = (v: Value) => fn(this.transformFn(v)) - return bind as unknown as Binding<T> - } - - get(): Value { - if (typeof this.emitter.get === "function") - return this.transformFn(this.emitter.get()) - - if (typeof this.prop === "string") { - const getter = `get_${snakeify(this.prop)}` - if (typeof this.emitter[getter] === "function") - return this.transformFn(this.emitter[getter]()) - - return this.transformFn(this.emitter[this.prop]) - } - - throw Error("can not get value of binding") - } - - subscribe(callback: (value: Value) => void): () => void { - if (typeof this.emitter.subscribe === "function") { - return this.emitter.subscribe(() => { - callback(this.get()) - }) - } - else if (typeof this.emitter.connect === "function") { - const signal = `notify::${this.prop}` - const id = this.emitter.connect(signal, () => { - callback(this.get()) - }) - return () => { - (this.emitter.disconnect as Connectable["disconnect"])(id) - } - } - throw Error(`${this.emitter} is not bindable`) - } -} - -export const { bind } = Binding diff --git a/gjs/src/file.ts b/gjs/src/file.ts deleted file mode 100644 index 90b33a1..0000000 --- a/gjs/src/file.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Astal, Gio } from "./imports.js" - -export function readFile(path: string): string { - return Astal.read_file(path) || "" -} - -export function readFileAsync(path: string): Promise<string> { - return new Promise((resolve, reject) => { - Astal.read_file_async(path, (_, res) => { - try { - resolve(Astal.read_file_finish(res) || "") - } - catch (error) { - reject(error) - } - }) - }) -} - -export function writeFile(path: string, content: string): void { - Astal.write_file(path, content) -} - -export function writeFileAsync(path: string, content: string): Promise<void> { - return new Promise((resolve, reject) => { - Astal.write_file_async(path, content, (_, res) => { - try { - resolve(Astal.write_file_finish(res)) - } - catch (error) { - reject(error) - } - }) - }) -} - -export function monitorFile( - path: string, - callback: (file: string, event: Gio.FileMonitorEvent) => void, -): Gio.FileMonitor { - return Astal.monitor_file(path, (file: string, event: Gio.FileMonitorEvent) => { - callback(file, event) - })! -} diff --git a/gjs/src/imports.ts b/gjs/src/imports.ts deleted file mode 100644 index cbed004..0000000 --- a/gjs/src/imports.ts +++ /dev/null @@ -1,10 +0,0 @@ -// this file's purpose is to have glib versions in one place -// this is only really needed for Gtk/Astal because -// ts-gir might generate gtk4 versions too - -export { default as Astal } from "gi://Astal?version=0.1" -export { default as GObject } from "gi://GObject?version=2.0" -export { default as Gio } from "gi://Gio?version=2.0" -export { default as Gtk } from "gi://Gtk?version=3.0" -export { default as Gdk } from "gi://Gdk?version=3.0" -export { default as GLib } from "gi://GLib?version=2.0" diff --git a/gjs/src/jsx/jsx-runtime.ts b/gjs/src/jsx/jsx-runtime.ts deleted file mode 100644 index 70f098f..0000000 --- a/gjs/src/jsx/jsx-runtime.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Gtk } from "../imports.js" -import * as Widget from "../widgets.js" - -function isArrowFunction(func: any): func is (args: any) => any { - return !Object.hasOwn(func, "prototype") -} - -export function jsx( - ctor: keyof typeof ctors | typeof Gtk.Widget, - { children, ...props }: any, -) { - children ??= [] - - if (!Array.isArray(children)) - children = [children] - - 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 (isArrowFunction(ctor)) - return ctor(props) - - // @ts-expect-error can be class or function - return new ctor(props) -} - -const ctors = { - box: Widget.Box, - button: Widget.Button, - centerbox: Widget.CenterBox, - // TODO: circularprogress - drawingarea: Widget.DrawingArea, - entry: Widget.Entry, - eventbox: Widget.EventBox, - // TODO: fixed - // TODO: flowbox - icon: Widget.Icon, - label: Widget.Label, - levelbar: Widget.LevelBar, - // TODO: listbox - overlay: Widget.Overlay, - revealer: Widget.Revealer, - scrollable: Widget.Scrollable, - slider: Widget.Slider, - // TODO: stack - switch: Widget.Switch, - window: Widget.Window, -} - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace JSX { - type Element = Gtk.Widget - type ElementClass = Gtk.Widget - interface IntrinsicElements { - box: Widget.BoxProps - button: Widget.ButtonProps - centerbox: Widget.CenterBoxProps - // TODO: circularprogress - drawingarea: Widget.DrawingAreaProps - entry: Widget.EntryProps - eventbox: Widget.EventBoxProps - // TODO: fixed - // TODO: flowbox - icon: Widget.IconProps - label: Widget.LabelProps - levelbar: Widget.LevelBarProps - // TODO: listbox - overlay: Widget.OverlayProps - revealer: Widget.RevealerProps - scrollable: Widget.ScrollableProps - slider: Widget.SliderProps - // TODO: stack - switch: Widget.SwitchProps - window: Widget.WindowProps - } - } -} - -export const jsxs = jsx diff --git a/gjs/src/process.ts b/gjs/src/process.ts deleted file mode 100644 index c5329e2..0000000 --- a/gjs/src/process.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Astal } from "./imports.js" - -type Args<Out = void, Err = void> = { - cmd: string | string[] - out?: (stdout: string) => Out - err?: (stderr: string) => Err -} - -function args<O, E>(argsOrCmd: Args | string | string[], onOut: O, onErr: E) { - const params = Array.isArray(argsOrCmd) || typeof argsOrCmd === "string" - return { - cmd: params ? argsOrCmd : argsOrCmd.cmd, - err: params ? onErr : argsOrCmd.err || onErr, - out: params ? onOut : argsOrCmd.out || onOut, - } -} - -export function subprocess(args: Args): Astal.Process -export function subprocess( - cmd: string | string[], - onOut?: (stdout: string) => void, - onErr?: (stderr: string) => void, -): Astal.Process -export function subprocess( - argsOrCmd: Args | string | string[], - onOut: (stdout: string) => void = print, - onErr: (stderr: string) => void = printerr, -) { - const { cmd, err, out } = args(argsOrCmd, onOut, onErr) - const proc = Array.isArray(cmd) - ? Astal.Process.subprocessv(cmd) - : Astal.Process.subprocess(cmd) - - proc.connect("stdout", (_, stdout: string) => out(stdout)) - proc.connect("stderr", (_, stderr: string) => err(stderr)) - return proc -} - -/** @throws {GLib.Error} Throws stderr */ -export function exec(cmd: string | string[]) { - return Array.isArray(cmd) - ? Astal.Process.execv(cmd) - : Astal.Process.exec(cmd) -} - -export function execAsync(cmd: string | string[]): Promise<string> { - return new Promise((resolve, reject) => { - if (Array.isArray(cmd)) { - Astal.Process.exec_asyncv(cmd, (_, res) => { - try { - resolve(Astal.Process.exec_asyncv_finish(res)) - } - catch (error) { - reject(error) - } - }) - } - else { - Astal.Process.exec_async(cmd, (_, res) => { - try { - resolve(Astal.Process.exec_finish(res)) - } - catch (error) { - reject(error) - } - }) - } - }) -} diff --git a/gjs/src/time.ts b/gjs/src/time.ts deleted file mode 100644 index 4e28ad0..0000000 --- a/gjs/src/time.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Astal } from "./imports.js" - -export function interval(interval: number, callback?: () => void) { - return Astal.Time.interval(interval, () => void callback?.()) -} - -export function timeout(timeout: number, callback?: () => void) { - return Astal.Time.timeout(timeout, () => void callback?.()) -} - -export function idle(callback?: () => void) { - return Astal.Time.idle(() => void callback?.()) -} diff --git a/gjs/src/variable.ts b/gjs/src/variable.ts deleted file mode 100644 index d583ab1..0000000 --- a/gjs/src/variable.ts +++ /dev/null @@ -1,227 +0,0 @@ -import Binding, { type Connectable } from "./binding.js" -import { Astal } from "./imports.js" -import { interval } from "./time.js" -import { execAsync, subprocess } from "./process.js" - -class VariableWrapper<T> extends Function { - private variable!: Astal.VariableBase - private errHandler? = console.error - - private _value: T - private _poll?: Astal.Time - private _watch?: Astal.Process - - private pollInterval = 1000 - private pollExec?: string[] | string - private pollTransform?: (stdout: string, prev: T) => T - private pollFn?: (prev: T) => T | Promise<T> - - private watchTransform?: (stdout: string, prev: T) => T - private watchExec?: string[] | string - - constructor(init: T) { - super() - this._value = init - this.variable = new Astal.VariableBase() - this.variable.connect("dropped", () => { - this.stopWatch() - this.stopPoll() - }) - this.variable.connect("error", (_, err) => this.errHandler?.(err)) - return new Proxy(this, { - apply: (target, _, args) => target._call(args[0]), - }) - } - - private _call<R = T>(transform?: (value: T) => R): Binding<R> { - const b = Binding.bind(this) - return transform ? b.as(transform) : b as unknown as Binding<R> - } - - toString() { - return String(`Variable<${this.get()}>`) - } - - get(): T { return this._value } - set(value: T) { - if (value !== this._value) { - this._value = value - this.variable.emit("changed") - } - } - - startPoll() { - if (this._poll) - return - - if (this.pollFn) { - this._poll = interval(this.pollInterval, () => { - const v = this.pollFn!(this.get()) - if (v instanceof Promise) { - v.then(v => this.set(v)) - .catch(err => this.variable.emit("error", err)) - } - else { - this.set(v) - } - }) - } - else if (this.pollExec) { - this._poll = interval(this.pollInterval, () => { - execAsync(this.pollExec!) - .then(v => this.set(this.pollTransform!(v, this.get()))) - .catch(err => this.variable.emit("error", err)) - }) - } - } - - startWatch() { - if (this._watch) - return - - this._watch = subprocess({ - cmd: this.watchExec!, - out: out => this.set(this.watchTransform!(out, this.get())), - err: err => this.variable.emit("error", err), - }) - } - - stopPoll() { - this._poll?.cancel() - delete this._poll - } - - stopWatch() { - this._watch?.kill() - delete this._watch - } - - isPolling() { return !!this._poll } - isWatching() { return !!this._watch } - - drop() { - this.variable.emit("dropped") - this.variable.run_dispose() - } - - onDropped(callback: () => void) { - this.variable.connect("dropped", callback) - return this as unknown as Variable<T> - } - - onError(callback: (err: string) => void) { - delete this.errHandler - this.variable.connect("error", (_, err) => callback(err)) - return this as unknown as Variable<T> - } - - subscribe(callback: (value: T) => void) { - const id = this.variable.connect("changed", () => { - callback(this.get()) - }) - return () => this.variable.disconnect(id) - } - - poll( - interval: number, - exec: string | string[], - transform?: (stdout: string, prev: T) => T - ): Variable<T> - - poll( - interval: number, - callback: (prev: T) => T | Promise<T> - ): Variable<T> - - poll( - interval: number, - exec: string | string[] | ((prev: T) => T | Promise<T>), - transform: (stdout: string, prev: T) => T = out => out as T, - ) { - this.stopPoll() - this.pollInterval = interval - this.pollTransform = transform - if (typeof exec === "function") { - this.pollFn = exec - delete this.pollExec - } - else { - this.pollExec = exec - delete this.pollFn - } - this.startPoll() - return this as unknown as Variable<T> - } - - watch( - exec: string | string[], - transform: (stdout: string, prev: T) => T = out => out as T, - ) { - this.stopWatch() - this.watchExec = exec - this.watchTransform = transform - this.startWatch() - return this as unknown as Variable<T> - } - - observe( - objs: Array<[obj: Connectable, signal: string]>, - callback: (...args: any[]) => T): Variable<T> - - observe( - obj: Connectable, - signal: string, - callback: (...args: any[]) => T): Variable<T> - - observe( - objs: Connectable | Array<[obj: Connectable, signal: string]>, - sigOrFn: string | ((obj: Connectable, ...args: any[]) => T), - callback?: (obj: Connectable, ...args: any[]) => T, - ) { - const f = typeof sigOrFn === "function" ? sigOrFn : callback ?? (() => this.get()) - const set = (obj: Connectable, ...args: any[]) => this.set(f(obj, ...args)) - - if (Array.isArray(objs)) { - for (const obj of objs) { - const [o, s] = obj - o.connect(s, set) - } - } - else { - if (typeof sigOrFn === "string") - objs.connect(sigOrFn, set) - } - - return this as unknown as Variable<T> - } - - static derive< - const Deps extends Array<Variable<any> | Binding<any>>, - Args extends { - [K in keyof Deps]: Deps[K] extends Variable<infer T> - ? T : Deps[K] extends Binding<infer T> ? T : never - }, - V = Args, - >(deps: Deps, fn: (...args: Args) => V = (...args) => args as unknown as V) { - const update = () => fn(...deps.map(d => d.get()) as Args) - const derived = new Variable(update()) - const unsubs = deps.map(dep => dep.subscribe(() => derived.set(update()))) - derived.onDropped(() => unsubs.map(unsub => unsub())) - return derived - } -} - -export interface Variable<T> extends Omit<VariableWrapper<T>, "bind"> { - <R>(transform: (value: T) => R): Binding<R> - (): Binding<T> -} - -export const Variable = new Proxy(VariableWrapper as any, { - apply: (_t, _a, args) => new VariableWrapper(args[0]), -}) as { - derive: typeof VariableWrapper["derive"] - <T>(init: T): Variable<T> - new<T>(init: T): Variable<T> -} - -export default Variable diff --git a/gjs/src/widgets.ts b/gjs/src/widgets.ts deleted file mode 100644 index 82d4708..0000000 --- a/gjs/src/widgets.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* eslint-disable max-len */ -import { Astal, Gtk } from "./imports.js" -import astalify, { type ConstructProps, type Widget } 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> - -// 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, { - onClicked: [] - onClick: [event: Astal.ClickEvent] - onClickRelease: [event: Astal.ClickEvent] - onHover: [event: Astal.HoverEvent] - onHoverLost: [event: Astal.HoverEvent] - onScroll: [event: Astal.ScrollEvent] -}> - -// 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> - -// TODO: CircularProgress - -// 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, { - onDraw: [cr: any] // TODO: cairo types -}> - -// 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, { - onChanged: [] - onActivate: [] -}> - -// 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, { - onClick: [event: Astal.ClickEvent] - onClickRelease: [event: Astal.ClickEvent] - onHover: [event: Astal.HoverEvent] - onHoverLost: [event: Astal.HoverEvent] - onScroll: [event: Astal.ScrollEvent] -}> - -// 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> - -// 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> - -// 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> - -// 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> - -// 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> - -// 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> - -// 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, { - onDragged: [] -}> - -// TODO: Stack - -// 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> - -// 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> |