summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/guide/ags/binding.md234
-rw-r--r--docs/guide/ags/faq.md189
-rw-r--r--docs/guide/ags/first-widgets.md41
-rw-r--r--docs/guide/ags/gobject.md166
-rw-r--r--docs/guide/ags/utilities.md2
-rw-r--r--docs/guide/ags/variable.md72
-rw-r--r--docs/guide/ags/widget.md77
-rw-r--r--docs/guide/getting-started/nix.md2
-rw-r--r--docs/vitepress.config.ts5
9 files changed, 536 insertions, 252 deletions
diff --git a/docs/guide/ags/binding.md b/docs/guide/ags/binding.md
new file mode 100644
index 0000000..f1592a0
--- /dev/null
+++ b/docs/guide/ags/binding.md
@@ -0,0 +1,234 @@
+# Binding
+
+As mentioned before binding an object's state to another -
+so in most cases a `Variable` or a `GObject.Object` property to a widget's property -
+is done through the `bind` function which returns a `Binding` object.
+
+`Binding` objects simply hold information about the source and how it should be transformed
+which Widget constructors can use to setup a connection between themselves and the source.
+
+```ts
+class Binding<Value> {
+ private transformFn: (v: any) => unknown
+ private emitter: Subscribable<Value> | Connectable
+ private prop?: string
+
+ as<T>(fn: (v: Value) => T): Binding<T>
+ get(): Value
+ subscribe(callback: (value: Value) => void): () => void
+}
+```
+
+A `Binding` can be constructed from an object implementing
+the `Subscribable` interface (usually a `Variable`)
+or an object implementing the `Connectable` interface and one of its properties
+(usually a `GObject.Object` instance).
+
+```ts
+function bind<T>(obj: Subscribable): Binding<T>
+
+function bind<
+ Obj extends Connectable,
+ Prop extends keyof Obj,
+>(obj: Obj, prop: Prop): Binding<Obj[Prop]>
+```
+
+## Subscribable and Connectable interface
+
+Any object implementing one of these interfaces can be used with `bind`.
+
+```ts
+interface Subscribable<T> {
+ subscribe(callback: (value: T) => void): () => void
+ get(): T
+}
+
+interface Connectable {
+ connect(signal: string, callback: (...args: any[]) => unknown): number
+ disconnect(id: number): void
+}
+```
+
+## Example Custom Subscribable
+
+When binding the children of a box from an array, usually not all elements
+of the array changes each time, so it would make sense to not destroy
+the widget which represents the element.
+
+::: code-group
+
+```ts :line-numbers [varmap.ts]
+import { type Subscribable } from "astal/binding"
+import { Gtk } from "astal"
+
+export class VarMap<K, T = Gtk.Widget> implements Subscribable {
+ #subs = new Set<(v: Array<[K, T]>) => void>()
+ #map: Map<K, T>
+
+ #notifiy() {
+ const value = this.get()
+ for (const sub of this.#subs) {
+ sub(value)
+ }
+ }
+
+ #delete(key: K) {
+ const v = this.#map.get(key)
+
+ if (v instanceof Gtk.Widget) {
+ v.destroy()
+ }
+
+ this.#map.delete(key)
+ }
+
+ constructor(initial?: Iterable<[K, T]>) {
+ this.#map = new Map(initial)
+ }
+
+ set(key: K, value: T) {
+ this.#delete(key)
+ this.#map.set(key, value)
+ this.#notifiy()
+ }
+
+ delete(key: K) {
+ this.#delete(key)
+ this.#notifiy()
+ }
+
+ get() {
+ return [...this.#map.entries()]
+ }
+
+ subscribe(callback: (v: Array<[K, T]>) => void) {
+ this.#subs.add(callback)
+ return () => this.#subs.delete(callback)
+ }
+}
+```
+
+:::
+
+And this `VarMap<key, Widget>` can be used as an alternative to `Variable<Array<Widget>>`.
+
+```tsx
+function MappedBox() {
+ const map = new VarMap([
+ [1, <MyWidget id={id} />]
+ [2, <MyWidget id={id} />]
+ ])
+
+ const conns = [
+ gobject.connect("added", (_, id) => map.set(id, MyWidget({ id }))),
+ gobject.connect("removed", (_, id) => map.delete(id, MyWidget({ id }))),
+ ]
+
+ return <box onDestroy={() => conns.map(id => gobject.disconnect(id))}>
+ {bind(map).as(arr => arr.sort(([a], [b]) => a - b).map(([,w]) => w))}
+ </box>
+}
+```
+
+## Example Custom Connectable
+
+This was formerly known as a "Service" in AGS.
+Astal provides [decorator functions](./gobject#example-usage) that make it easy to subclass gobjects, however
+you can read more about GObjects and subclassing on [gjs.guide](https://gjs.guide/guides/gobject/subclassing.html#gobject-subclassing).
+
+Objects coming from [libraries](../libraries/references#astal-libraries)
+usually have a singleton gobject you can access with `.get_default()`.
+
+Here is an example of a Brightness library by wrapping the `brightnessctl` cli utility
+and by monitoring `/sys/class/backlight`
+
+::: code-group
+
+```ts :line-numbers [brightness.ts]
+import GObject, { register, property } from "astal/gobject"
+import { monitorFile, readFileAsync } from "astal/file"
+import { exec, execAsync } from "astal/process"
+
+const get = (args: string) => Number(exec(`brightnessctl ${args}`))
+const screen = exec(`bash -c "ls -w1 /sys/class/backlight | head -1"`)
+const kbd = exec(`bash -c "ls -w1 /sys/class/leds | head -1"`)
+
+@register({ GTypeName: "Brightness" })
+export default class Brightness extends GObject.Object {
+ static instance: Brightness
+ static get_default() {
+ if (!this.instance)
+ this.instance = new Brightness()
+
+ return this.instance
+ }
+
+ #kbdMax = get(`--device ${kbd} max`)
+ #kbd = get(`--device ${kbd} get`)
+ #screenMax = get("max")
+ #screen = get("get") / (get("max") || 1)
+
+ @property(Number)
+ get kbd() { return this.#kbd }
+
+ set kbd(value) {
+ if (value < 0 || value > this.#kbdMax)
+ return
+
+ execAsync(`brightnessctl -d ${kbd} s ${value} -q`).then(() => {
+ this.#kbd = value
+ this.notify("kbd")
+ })
+ }
+
+ @property(Number)
+ get screen() { return this.#screen }
+
+ set screen(percent) {
+ if (percent < 0)
+ percent = 0
+
+ if (percent > 1)
+ percent = 1
+
+ execAsync(`brightnessctl set ${Math.floor(percent * 100)}% -q`).then(() => {
+ this.#screen = percent
+ this.notify("screen")
+ })
+ }
+
+ constructor() {
+ super()
+
+ const screenPath = `/sys/class/backlight/${screen}/brightness`
+ const kbdPath = `/sys/class/leds/${kbd}/brightness`
+
+ monitorFile(screenPath, async f => {
+ const v = await readFileAsync(f)
+ this.#screen = Number(v) / this.#screenMax
+ this.notify("screen")
+ })
+
+ monitorFile(kbdPath, async f => {
+ const v = await readFileAsync(f)
+ this.#kbd = Number(v) / this.#kbdMax
+ this.notify("kbd")
+ })
+ }
+}
+```
+
+:::
+
+And it can be used like any other library object.
+
+```tsx
+function BrightnessSlider() {
+ const brightness = Brightness.get_default()
+
+ return <slider
+ value={bind(brightness, "screen")}
+ onDragged={({ value }) => brightness.screen = value}
+ />
+}
+```
diff --git a/docs/guide/ags/faq.md b/docs/guide/ags/faq.md
index cb5d609..76d8e72 100644
--- a/docs/guide/ags/faq.md
+++ b/docs/guide/ags/faq.md
@@ -2,22 +2,22 @@
## Monitor id does not match compositor
-The monitor property that windows expect is mapped by Gdk, which is not always
+The monitor id property that windows expect is mapped by Gdk, which is not always
the same as the compositor. Instead use the `gdkmonitor` property which expects
-a `Gdk.Monitor` object which you can get from compositor libraries.
-
-Example with Hyprland
+a `Gdk.Monitor` object.
```tsx
-import Hyprland from "gi://AstalHyprland"
+import { App } from "astal"
function Bar(gdkmonitor) {
return <window gdkmonitor={gdkmonitor} />
}
function main() {
- for (const m of Hyprland.get_default().get_monitors()) {
- Bar(m.gdk_monitor)
+ for (const monitor of App.get_monitors()) {
+ if (monitor.model == "your-desired-model") {
+ Bar(monitor)
+ }
}
}
@@ -53,7 +53,7 @@ import GLib from "gi://GLib"
const HOME = GLib.getenv("HOME")
```
-## Custom svg symbolic icons
+## Custom SVG symbolic icons
Put the svgs in a directory, named `<icon-name>-symbolic.svg`
and use `App.add_icons` or `icons` parameter in `App.start`
@@ -90,74 +90,6 @@ print("print this line to stdout")
printerr("print this line to stderr")
```
-## Binding custom structures
-
-The `bind` function can take two types of objects.
-
-```ts
-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.
-
-```ts
-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 MyVariable()
- const label = bind(myvar).as(({ string, number }) => {
- return `${string} ${number}`
- })
-
- return <label label={label} />
-}
-```
-
## Populate the global scope with frequently accessed variables
It might be annoying to always import Gtk only for `Gtk.Align` enums.
@@ -289,91 +221,46 @@ return <RegularWindow
</RegularWindow>
```
-## Avoiding unnecessary re-rendering
-
-As mentioned before, any object can be bound that implements the `Subscribable` interface.
-
-```ts
-interface Subscribable<T = unknown> {
- subscribe(callback: (value: T) => void): () => void
- get(): T
-}
-```
-
-This can be used to our advantage to create a reactive `Map` object.
-
-```ts
-import { type Subscribable } from "astal/binding"
-import { Gtk } from "astal"
-
-export class VarMap<K, T = Gtk.Widget> implements Subscribable {
- #subs = new Set<(v: Array<[K, T]>) => void>()
- #map: Map<K, T>
-
- #notifiy() {
- const value = this.get()
- for (const sub of this.#subs) {
- sub(value)
- }
- }
-
- #delete(key: K) {
- const v = this.#map.get(key)
-
- if (v instanceof Gtk.Widget) {
- v.destroy()
- }
-
- this.#map.delete(key)
- }
+## Is there a way to limit the width/height of a widget?
- constructor(initial?: Iterable<[K, T]>) {
- this.#map = new Map(initial)
- }
+Unfortunately not. You can set a minimum size with `min-width` and `min-heigth` css attributes,
+but you can not set max size.
- add(key: K, value: T) {
- this.#delete(key)
- this.#map.set(key, value)
- this.#notifiy()
- }
+## Custom widgets with bindable properties
- delete(key: K) {
- this.#delete(key)
- this.#notifiy()
- }
+In function components you can wrap any primitive to handle both
+binding and value cases as one.
- get() {
- return [...this.#map.entries()]
+```tsx
+function MyWidget(props: { prop: string | Binding<string> }) {
+ const prop = props.prop instanceof Binding
+ ? props.prop
+ : bind({ get: () => props.prop, subscribe: () => () => {} })
+
+ function setup(self: Widget.Box) {
+ self.hook(prop, () => {
+ const value = prop.get()
+ // handler
+ })
}
- subscribe(callback: (v: Array<[K, T]>) => void) {
- this.#subs.add(callback)
- return () => this.#subs.delete(callback)
- }
+ return <box setup={setup}>
+ </box>
}
```
-And this `VarMap<key, Widget>` can be used as an alternative to `Variable<Array<Widget>>`.
+You can pass the prop the super constructor in subclasses
```tsx
-function MappedBox() {
- const map = new VarMap([
- [1, <MyWidget id={id} />]
- [2, <MyWidget id={id} />]
- ])
-
- const conns = [
- gobject.connect("added", (_, id) => map.set(id, MyWidget({ id }))),
- gobject.connect("removed", (_, id) => map.delete(id, MyWidget({ id }))),
- ]
-
- return <box onDestroy={() => conns.map(id => gobject.disconnect(id))}>
- {bind(map).as(arr => arr.sort(([a], [b]) => a - b).map(([,w]) => w))}
- </box>
+@register()
+class MyWidget extends Widget.Box {
+ @property(String)
+ set prop(v: string) {
+ // handler
+ }
+
+ constructor(props: { prop: string | Binding<string> }) {
+ super(props)
+ }
}
```
-
-## Is there a way to limit the width/height of a widget?
-
-Unfortunately not. You can set a minimum size with `min-width` and `min-heigth` css attributes,
-but you can not set max size.
diff --git a/docs/guide/ags/first-widgets.md b/docs/guide/ags/first-widgets.md
index c4c4436..6dba7b3 100644
--- a/docs/guide/ags/first-widgets.md
+++ b/docs/guide/ags/first-widgets.md
@@ -75,7 +75,7 @@ either by using JSX or using a widget constructor.
```tsx [MyButton.tsx]
function MyButton(): JSX.Element {
return <button onClicked="echo hello">
- Clicke Me!
+ <label label="Click me!" />
</button>
}
```
@@ -84,10 +84,10 @@ function MyButton(): JSX.Element {
import { Widget } from "astal"
function MyButton(): Widget.Button {
- return Widget.Button({
- onClicked: "echo hello",
- label: "Click Me!",
- })
+ return new Widget.Button(
+ { onClicked: "echo hello" },
+ new Widget.Label({ label: "Click me!" }),
+ )
}
```
@@ -96,7 +96,7 @@ function MyButton(): Widget.Button {
:::info
The only difference between the two is the return type.
Using markup the return type is always `Gtk.Widget` (globally available as `JSX.Element`),
-while using constructors the return type is the type of the widget.
+while using constructors the return type is the actual type of the widget.
It is rare to need the actual return type, so most if not all of the time, you can use markup.
:::
@@ -261,13 +261,12 @@ return <MyWidget myprop="hello">
## State management
-The state of widgets are handled with Bindings. A `Binding` lets you
-connect the state of one [GObject](https://docs.gtk.org/gobject/class.Object.html) to another, in our case it is used to
-rerender part of a widget based on the state of a `GObject`.
-A `GObject` can be a [Variable](./variable) or it can be from a [Library](../libraries/references).
+The state of widgets are handled with Bindings. A [Binding](./binding) lets you
+connect the state of an [object](./binding#subscribable-and-connectable-interface)
+to a widget so it re-renders when that state changes.
-We use the `bind` function to create a `Binding` object from a `Variable` or
-a regular GObject and one of its properties.
+Use the `bind` function to create a `Binding` object from a `Variable` or
+a regular `GObject` and one of its properties.
Here is an example of a Counter widget that uses a `Variable` as its state:
@@ -349,23 +348,25 @@ return <box>
<box>
```
-:::warning
-Only bind children of the `box` or the `stack` widget. Gtk does not cleanup widgets by default,
-they have to be explicitly destroyed. The box widget is a special container that
-will implicitly call `.destroy()` on its removed child widgets.
-You can disable this behavior by setting the `noImplicityDestroy` property.
+:::tip
+Binding children of widgets will implicitly call `.destroy()` on widgets
+that would be left without a parent. You can opt out of this behavior
+by setting `noImplicityDestroy` property on the container widget.
:::
:::info
-The above example destroys and recreates every widget in the list everytime
+The above example destroys and recreates every widget in the list **every time**
the value of the `Variable` changes. There might be cases where you would
want to [handle child creation and deletion](/guide/ags/faq#avoiding-unnecessary-re-rendering)
yourself, because you don't want to lose the
-inner state of widgets that does not need to be recreated.
+inner state of widgets that does not need to be recreated. In this case
+you can create a [custom reactive structure](./binding#example-custom-subscribable)
:::
When there is at least one `Binding` passed as a child, the `children`
-parameter will always be a flattened `Binding<Array<JSX.Element>>`
+parameter will always be a flattened `Binding<Array<JSX.Element>>`.
+When there is a single `Binding` passed as a child, the `child` parameter will
+be a `Binding<JSX.Element>` or a flattened `Binding<Array<JSX.Element>>`.
```tsx
function MyContainer({ children }) {
diff --git a/docs/guide/ags/gobject.md b/docs/guide/ags/gobject.md
new file mode 100644
index 0000000..83b7c45
--- /dev/null
+++ b/docs/guide/ags/gobject.md
@@ -0,0 +1,166 @@
+# Subclassing GObject.Object
+
+These were formerly known as custom services in AGS.
+Astal provides decorator functions that make it easy to subclass gobjects.
+
+## Example Usage
+
+```ts
+import GObject, { register, property } from "astal/gobject"
+
+@register()
+class MyObj extends GObject.Object {
+ @property(String)
+ declare myProp: string
+
+ @signal(String, Number)
+ declare mySignal: (a: string, b: number) => void
+}
+```
+
+## Property decorator
+
+```ts
+type PropertyDeclaration =
+ | GObject.ParamSpec
+ | { $gtype: GObject.GType }
+
+function property(declaration: PropertyDeclaration)
+```
+
+The `property` decorator can take any class that has a registered GType.
+This includes the globally available `String`, `Number`, `Boolean` and `Object`
+javascript constructors. They are mapped to their relative `GObject.ParamSpec`.
+
+The property decorator can be applied in the following ways:
+
+1. On a property declaration
+
+```ts {3,4}
+@register()
+class MyObj extends GObject.Object {
+ @property(String)
+ declare myProp: string
+}
+```
+
+This will create a getter and setter for the property and will also
+emit the notify signal when the value is set to a new value.
+
+:::info
+The `declare` keyword is required so that the property declaration
+is not transpiled into JavaScript, otherwise the initial value of the
+property would be `undefined`.
+:::
+
+:::warning
+The value is checked by reference, this is important if your
+property is an object type.
+
+```ts
+const dict = obj.prop
+dict["key"] = 0
+obj.prop = dict // This will not emit notify::prop // [!code error]
+obj.prop = { ...dict } // This will emit notify::prop
+```
+
+:::
+
+If you want to set a custom default value, do so in the constructor of your class.
+
+```ts {7}
+@register()
+class MyObj extends GObject.Object {
+ @property(String)
+ declare myProp: string
+
+ constructor() {
+ super({ myProp: "default-value" })
+ }
+}
+```
+
+2. On a getter
+
+```ts {3,4}
+@register()
+class MyObj extends GObject.Object {
+ @property(String)
+ get myProp () {
+ return "value"
+ }
+}
+```
+
+This will create a read-only property.
+
+3. On a getter and setter
+
+```ts {5,6,10}
+@register()
+class MyObj extends GObject.Object {
+ declare private _prop: string
+
+ @property(String)
+ get myProp () {
+ return "value"
+ }
+
+ set myProp (v: string) {
+ if (v !== this._prop) {
+ this._prop = v
+ this.notify("my-prop")
+ }
+ }
+}
+```
+
+This will create a read-write property.
+
+:::info
+When defining getter/setters for the property, notify signal emission has to be done explicitly.
+:::
+
+## Signal decorator
+
+```ts
+function signal(...params: Array<{ $gtype: GObject.GType })
+
+function signal(declaration?: SignalDeclaration) // Object you would pass to GObject.registerClass
+```
+
+You can apply the signal decorator to either a property declaration or a method.
+
+```ts {3,4,6,7}
+@register()
+class MyObj extends GObject.Object {
+ @signal(String, String)
+ declare mySig: (a: String, b: String) => void
+
+ @signal(String, String)
+ mySig(a: string, b: string) {
+ // default signal handler
+ }
+}
+```
+
+You can emit the signal by calling the signal method or using `emit`.
+
+```ts
+const obj = new MyObj()
+obj.connect("my-sig", (obj, a: string, b: string) => {})
+
+obj.mySig("a", "b")
+obj.emit("my-sig", "a", "b")
+```
+
+## Register decorator
+
+Every GObject subclass has to be registered. You can pass the same options
+to this decorator as you would to `GObject.registerClass`
+
+```ts
+@register({ GTypeName: "MyObj" })
+class MyObj extends GObject.Object {
+}
+```
diff --git a/docs/guide/ags/utilities.md b/docs/guide/ags/utilities.md
index 42589d3..aae255c 100644
--- a/docs/guide/ags/utilities.md
+++ b/docs/guide/ags/utilities.md
@@ -161,7 +161,7 @@ execAsync(["bash", "-c", "/path/to/script.sh"])
:::warning
`subprocess`, `exec`, and `execAsync` executes the passed executable as is.
They are **not** executed in a shell environment,
-they do **not** expand env variables like `$HOME`,
+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.
diff --git a/docs/guide/ags/variable.md b/docs/guide/ags/variable.md
index 96e8d38..4207f61 100644
--- a/docs/guide/ags/variable.md
+++ b/docs/guide/ags/variable.md
@@ -4,17 +4,17 @@
import { Variable } from "astal"
```
-Variable is just a simple `GObject` that holds a value.
-And has shortcuts for hooking up subprocesses.
+Variable is just a simple object which holds a single value.
+It also has some shortcuts for hooking up subprocesses, intervals and other gobjects.
:::info
The `Variable` object imported from the `"astal"` package is **not** [Astal.Variable](https://aylur.github.io/libastal/class.Variable.html).
:::
-## Variable as state
+## Example Usage
```typescript
-const myvar = Variable<string>("initial-value")
+const myvar = Variable("initial-value")
// whenever its value changes, callback will be executed
myvar.subscribe((value: string) => {
@@ -35,43 +35,49 @@ Widget.Label({
```
:::warning
-Make sure to make the transform functions pure. The `.get()` function can be called
-anytime by `astal` especially when `deriving`, so make sure there are no sideeffects.
+Make sure to make the transform functions passed to `.as()` are pure.
+The `.get()` function can be called anytime by `astal` especially when `deriving`,
+so make sure there are no sideeffects.
:::
-## Composing variables
+## Variable Composition
-Using `Variable.derive` we can compose both Variables and Bindings.
+Using `Variable.derive` any `Subscribable` object can be composed.
```typescript
-const v1: Variable<number> = Variable(2)
-const v2: Variable<number> = Variable(3)
+const v1: Variable<number> = Variable(1)
+const v2: Binding<number> = bind(obj, "prop")
+const v3: Subscribable<number> = {
+ get: () => 3,
+ subscribe: () => () => {},
+}
// 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}`
-})
+const v4: Variable<number> = Variable.derive(
+ [v1, v2, v3],
+ (v1: number, v2: number, v3: number) => {
+ return v1 * v2 * v3
+ }
+)
```
+:::info
+The types are only for demonstration purposes, you do not have to declare
+the type of a Variable, they will be inferred from their initial value.
+:::
+
## 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.
+Using `.poll` and `.watch` we can start subprocesses and capture their
+output. They can poll and watch at the same time, but they
+can only poll/watch once.
:::warning
The command parameter is passed to [execAsync](/guide/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`,
+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.
@@ -83,14 +89,14 @@ Variable("").poll(1000, ["bash", "-c", "command $VAR && command"])
:::
```typescript
-const myVar = Variable<number>(0)
+const myVar = Variable(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)
+const myVar = Variable(0)
.watch("command", (out: string, prev: number) => parseInt(out))
.watch(["bash", "-c", "command"], (out, prev) => parseInt(out))
```
@@ -127,14 +133,20 @@ myVar.drop()
```
:::warning
-Don't forget to drop them when they are defined inside widgets
-with either `.poll`, `.watch` or `.observe`
+Don't forget to drop derived variables or variables with
+either `.poll`, `.watch` or `.observe` when they are defined inside closures.
```tsx
function MyWidget() {
const myvar = Variable().poll()
-
- return <box onDestroy={() => myvar.drop()} />
+ const derived = Variable.derive()
+
+ return <box
+ onDestroy={() => {
+ myvar.drop()
+ derived.drop()
+ }}
+ />
}
```
diff --git a/docs/guide/ags/widget.md b/docs/guide/ags/widget.md
index 1fe755f..01cff81 100644
--- a/docs/guide/ags/widget.md
+++ b/docs/guide/ags/widget.md
@@ -1,8 +1,8 @@
# Widget
-## AGS widget properties
+## Additional widget properties
-These are properties that Astal.js additionally adds to Gtk.Widgets
+These are properties that Astal additionally adds to Gtk.Widgets
- className: `string` - List of class CSS selectors separated by white space.
- css: `string` - Inline CSS. e.g `label { color: white; }`. If no selector is specified `*` will be assumed. e.g `color: white;` will be inferred as `* { color: white; }`.
@@ -14,13 +14,12 @@ To have a full list of available properties, reference the documentation of the
- [Astal widgets](https://aylur.github.io/libastal/index.html#classes)
- [Gtk widgets](https://docs.gtk.org/gtk3/#classes)
-## AGS widget methods
-
-Additional methods that Astal.js adds to Gtk.Widget instances
+## Additional widget methods
### setup
-`setup` is a convenience prop to not have predefine widgets before returning them
+`setup` is a convenience prop to remove the need to predefine widgets
+before returning them in cases where a reference is needed.
without `setup`
@@ -46,17 +45,19 @@ function MyWidget() {
### hook
-Shorthand for connection and disconnecting to gobjects.
+Shorthand for connection and disconnecting to [Subscribable and Connectable](./binding#subscribable-and-connectable-interface) objects.
without `hook`
```tsx
function MyWidget() {
const id = gobject.connect("signal", callback)
+ const unsub = variable.subscribe(callback)
return <box
onDestroy={() => {
gobject.disconnect(id)
+ unsub()
}}
/>
}
@@ -69,6 +70,7 @@ function MyWidget() {
return <box
setup={(self) => {
self.hook(gobject, "signal", callback)
+ self.hook(variable, callback)
}}
/>
}
@@ -90,33 +92,35 @@ function MyWidget() {
## How to use non builtin Gtk widgets
-Using `Widget.astalify` you can setup widget constructors to behave like builtin widgets.
-The `astalify` function will apply the following:
+Using the `Widget.astalify` mixin you can subclass widgets
+to behave like builtin widgets.
+The `astalify` mixin will apply the following:
- set `visible` to true by default (Gtk3 widgets are invisible by default)
- make gobject properties accept and consume `Binding` objects
- add properties and methods listed above
-- proxify the constructor so the `new` keyword is not needed
- sets up signal handlers that are passed as props prefixed with `on`
```tsx
-import { Widget, Gtk } from "astal"
-
-// define its props, constructor and type
-export type ColorButtonProps = Widget.ConstructProps<
- Gtk.ColorButton,
- Gtk.ColorButton.ConstructorProps,
- { onColorSet: [] }
->
-export const ColorButton = Widget.astalify<
- typeof Gtk.ColorButton,
- ColorButtonProps,
- "ColorButton"
->(Gtk.ColorButton)
-export type ColorButton = ReturnType<typeof ColorButton>
+import { Widget, Gtk, GObject, Gdk } from "astal"
+
+// subclass, register, define constructor props
+class ColorButton extends Widget.astalify(Gtk.ColorButton) {
+ static { GObject.registerClass(this) }
+
+ constructor(props: Widget.ConstructProps<
+ ColorButton,
+ Gtk.ColorButton.ConstructorProps,
+ { onColorSet: [] } // signals of Gtk.ColorButton have to be manually typed
+ >) {
+ super(props as any)
+ }
+}
function MyWidget() {
- function setup(button: ColorButton) {}
+ function setup(button: ColorButton) {
+
+ }
return <ColorButton
setup={setup}
@@ -140,29 +144,6 @@ You can reference [Gtk3](https://gjs-docs.gnome.org/gtk30~3.0/)
and [Astal](https://aylur.github.io/libastal/index.html#classes) for available signals.
:::
-:::tip
-
-As stated before children are passed as either `child` or `children` property,
-when passing a container widget with `Widget.astalify` these rules still apply.
-While subclasses of `Gtk.Bin` *can* take a `child` property in gjs, you might notice
-a warning that it is deprecated. You can workaround this with a simple wrapper function.
-
-```tsx
-const GtkFrame = Widget.astalify<
- typeof Gtk.Frame,
- FrameProps,
- "Frame"
->(Gtk.Frame)
-
-export function Frame({ child, ...props }: FrameProps) {
- const frame = GtkFrame(props)
- frame.add(child) // use the widget's child adding function
- return frame
-}
-```
-
-:::
-
## TypeScript
Type of widgets are available through `Widget`.
diff --git a/docs/guide/getting-started/nix.md b/docs/guide/getting-started/nix.md
index c9ed270..81f4e4d 100644
--- a/docs/guide/getting-started/nix.md
+++ b/docs/guide/getting-started/nix.md
@@ -20,7 +20,7 @@ Using Astal on Nix will require you to package your project.
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
- packages.${system}.default = astal.lib.mkLuaPacakge {
+ packages.${system}.default = astal.lib.mkLuaPackage {
inherit pkgs;
src = ./path/to/project; # should contain init.lua
diff --git a/docs/vitepress.config.ts b/docs/vitepress.config.ts
index 735401d..47ed81e 100644
--- a/docs/vitepress.config.ts
+++ b/docs/vitepress.config.ts
@@ -20,6 +20,7 @@ export default defineConfig({
themeConfig: {
logo: "/icon.svg",
+ outline: "deep",
footer: {
message: "Released under the LGPL v2.1 License",
@@ -67,8 +68,10 @@ export default defineConfig({
{ text: "Theming", link: "/theming" },
{ text: "CLI and App", link: "/cli-app" },
{ text: "Widget", link: "/widget" },
- { text: "Utilities", link: "/utilities" },
{ text: "Variable", link: "/variable" },
+ { text: "Binding", link: "/binding" },
+ { text: "GObject", link: "/gobject" },
+ { text: "Utilities", link: "/utilities" },
{ text: "FAQ", link: "/faq" },
],
},