diff options
-rw-r--r-- | core/gjs/src/variable.ts | 19 | ||||
-rw-r--r-- | docs/src/content/docs/ags/faq.md | 79 | ||||
-rw-r--r-- | docs/src/content/docs/ags/utilities.md | 2 | ||||
-rw-r--r-- | docs/src/content/docs/ags/variable.md | 138 |
4 files changed, 228 insertions, 10 deletions
diff --git a/core/gjs/src/variable.ts b/core/gjs/src/variable.ts index d583ab1..9528ffe 100644 --- a/core/gjs/src/variable.ts +++ b/core/gjs/src/variable.ts @@ -166,12 +166,14 @@ class VariableWrapper<T> extends Function { observe( objs: Array<[obj: Connectable, signal: string]>, - callback: (...args: any[]) => T): Variable<T> + callback: (...args: any[]) => T, + ): Variable<T> observe( obj: Connectable, signal: string, - callback: (...args: any[]) => T): Variable<T> + callback: (...args: any[]) => T, + ): Variable<T> observe( objs: Connectable | Array<[obj: Connectable, signal: string]>, @@ -184,12 +186,15 @@ class VariableWrapper<T> extends Function { if (Array.isArray(objs)) { for (const obj of objs) { const [o, s] = obj - o.connect(s, set) + const id = o.connect(s, set) + this.onDropped(() => o.disconnect(id)) } } else { - if (typeof sigOrFn === "string") - objs.connect(sigOrFn, set) + if (typeof sigOrFn === "string") { + const id = objs.connect(sigOrFn, set) + this.onDropped(() => objs.disconnect(id)) + } } return this as unknown as Variable<T> @@ -199,7 +204,7 @@ class VariableWrapper<T> extends Function { 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 + ? T : Deps[K] extends Binding<infer T> ? T : never }, V = Args, >(deps: Deps, fn: (...args: Args) => V = (...args) => args as unknown as V) { @@ -221,7 +226,7 @@ export const Variable = new Proxy(VariableWrapper as any, { }) as { derive: typeof VariableWrapper["derive"] <T>(init: T): Variable<T> - new<T>(init: T): Variable<T> + new <T>(init: T): Variable<T> } export default Variable diff --git a/docs/src/content/docs/ags/faq.md b/docs/src/content/docs/ags/faq.md index eca31c7..5599fe3 100644 --- a/docs/src/content/docs/ags/faq.md +++ b/docs/src/content/docs/ags/faq.md @@ -78,3 +78,82 @@ App.start({ If there is a name clash with an icon from your current icon pack the icon pack will take precedence ::: + +## Logging + +The `console` API in gjs uses glib logging functions. +If you just want to print some text as is to stdout +use the globally available `print` function or `printerr` for stderr. + +```js +print("print this line to stdout") +printerr("print this line to stderr") +``` + +## Binding custom structures + +The `bind` function can take two types of objects. + +```typescript +interface Subscribable<T = unknown> { + subscribe(callback: (value: T) => void): () => void + get(): T +} + +interface Connectable { + connect(signal: string, callback: (...args: any[]) => unknown): number + disconnect(id: number): void +} +``` + +`Connectable` is for mostly gobjects, while `Subscribable` is for `Variables` +and custom objects. + +For example you can compose `Variables` in using a class. + +```typescript +type MyVariableValue = { + number: number + string: string +} + +class MyVariable { + number = Variable(0) + string = Variable("") + + get(): MyVariableValue { + return { + number: this.number.get(), + string: this.string.get(), + } + } + + subscribe(callback: (v: MyVariableValue) => void) { + const unsub1 = this.number.subscribe((value) => { + callback({ string: value, number: this.number.get() }) + }) + + const unsub2 = this.string.subscribe((value) => { + callback({ number: value, string: this.string.get() }) + }) + + return () => { + unsub1() + unsub2() + } + } +} +``` + +Then it can be used with `bind`. + +```tsx +function MyWidget() { + const myvar = new MyVariableValue() + const label = bind(myvar).as(({ string, number }) => { + return `${string} ${number}` + }) + + return <label label={label} /> +} +``` diff --git a/docs/src/content/docs/ags/utilities.md b/docs/src/content/docs/ags/utilities.md index df5e490..a26bb52 100644 --- a/docs/src/content/docs/ags/utilities.md +++ b/docs/src/content/docs/ags/utilities.md @@ -169,7 +169,7 @@ They are **not** executed in a shell environment, they do **not** expand env variables like `$HOME`, and they do **not** handle logical operators like `&&` and `||`. -If you want to run bash, run them with bash. +If you want bash, run them with bash. ```js exec(["bash", "-c", "command $VAR && command"]) diff --git a/docs/src/content/docs/ags/variable.md b/docs/src/content/docs/ags/variable.md index e172b7d..82ea856 100644 --- a/docs/src/content/docs/ags/variable.md +++ b/docs/src/content/docs/ags/variable.md @@ -2,7 +2,141 @@ title: Variable description: Reference of the builtin Variable type sidebar: - order: 6 + order: 6 --- -## TODO: +```js +import { Variable } from "astal" +``` + +Variable is just a simple `GObject` that holds a value. +And has shortcuts for hooking up subprocesses. + +## Variable as state + +```typescript +const myvar = Variable<string>("initial-value") + +// whenever its value changes, callback will be executed +myvar.subscribe((value: string) => { + console.log(value) +}) + +// settings its value +myvar.set("new value") + +// getting its value +const value = myvar.get() + +// binding them to widgets +Widget.Label({ + label: bind(myvar).as((value) => `transformed ${value}`), + label: myvar((value) => `transformed ${value}`), // shorthand for the above +}) +``` + +:::caution +Make sure to make the transform functions pure. The `.get()` function can be called +anytime by `astal` especially when `deriving`. +::: + +## Composing variables + +Using `Variable.derive` we can compose both Variables and Bindings. + +```typescript +const v1: Variable<number> = Variable(2) +const v2: Variable<number> = Variable(3) + +// first argument is a list of dependencies +// second argument is a transform function, +// where the parameters are the values of the dependencies in the order they were passed +const v3: Variable<number> = Variable.derive([v1, v2], (v1, v2) => { + return v1 * v2 +}) + +const b1: Binding<string> = bind(obj, "prop") +const b2: Binding<string> = bind(obj, "prop") + +const b3: Variable<string> = Variable.derive([b1, b2], (b1, b2) => { + return `${b1}-${b2}` +}) +``` + +## Subprocess shorthands + +Using `.poll` and `.watch` we can start subprocess and capture their +output in `Variables`. They can poll and watch at the same time, but they +can only poll/watch one subprocess. + +:::caution +The command parameter is passed to [execAsync](/astal/ags/utilities/#executing-external-commands-and-scripts) +which means they are **not** executed in a shell environment, +they do **not** expand env variables like `$HOME`, +and they do **not** handle logical operators like `&&` and `||`. + +If you want bash, run them with bash. + +```js +Variable("").poll(1000, ["bash", "-c", "command $VAR && command"]) +``` + +::: + +```typescript +const myVar = Variable<number>(0) + .poll(1000, "command", (out: string, prev: number) => parseInt(out)) + .poll(1000, ["bash", "-c", "command"], (out, prev) => parseInt(out)) + .poll(1000, (prev) => prev + 1) +``` + +```typescript +const myVar = Variable<number>(0) + .watch("command", (out: string, prev: number) => parseInt(out)) + .watch(["bash", "-c", "command"], (out, prev) => parseInt(out)) +``` + +You can temporarily stop them and restart them whenever. + +```js +myvar.stopWatch() // this kills the subprocess +myvar.stopPoll() + +myvar.startListen() // launches the subprocess again +myvar.startPoll() + +console.log(myvar.isListening()) +console.log(myvar.isPolling()) +``` + +## Gobject connection shorthands + +Using `.observe` you can connect gobject signals and capture their value. + +```typescript +const myvar = Variable("") + .observe(obj1, "signal", () => "") + .observe(obj2, "signal", () => "") +``` + +## Dispose if no longer needed + +This will stop the interval and force exit the subprocess and disconnect gobjects. + +```js +myVar.drop() +``` + +:::caution +Don't forget to drop them when they are defined inside widgets +with either `.poll`, `.watch` or `.observe` + +```tsx +function MyWidget() { + const myvar = Variable().poll() + + return <box onDestroy={() => myvar.drop()} /> +} +``` + +::: |