summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/gjs/src/variable.ts19
-rw-r--r--docs/src/content/docs/ags/faq.md79
-rw-r--r--docs/src/content/docs/ags/utilities.md2
-rw-r--r--docs/src/content/docs/ags/variable.md138
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()} />
+}
+```
+
+:::