summaryrefslogtreecommitdiff
path: root/docs/guide/ags
diff options
context:
space:
mode:
authorAylur <[email protected]>2024-10-15 01:22:24 +0000
committerAylur <[email protected]>2024-10-15 01:22:24 +0000
commitede8890a08b3fbbb1f6df3b8c277ab6424d1befd (patch)
treeedcf54da90550a1c53c0221a7340e43b479ecee5 /docs/guide/ags
parentd63332b533b390e7e68f8f1fc2432958c4d36a4f (diff)
docs: better explain ags
Diffstat (limited to 'docs/guide/ags')
-rw-r--r--docs/guide/ags/binding.md234
-rw-r--r--docs/guide/ags/cli-app.md167
-rw-r--r--docs/guide/ags/faq.md266
-rw-r--r--docs/guide/ags/first-widgets.md402
-rw-r--r--docs/guide/ags/gobject.md166
-rw-r--r--docs/guide/ags/installation.md65
-rw-r--r--docs/guide/ags/theming.md161
-rw-r--r--docs/guide/ags/utilities.md174
-rw-r--r--docs/guide/ags/variable.md153
-rw-r--r--docs/guide/ags/widget.md208
10 files changed, 0 insertions, 1996 deletions
diff --git a/docs/guide/ags/binding.md b/docs/guide/ags/binding.md
deleted file mode 100644
index f1592a0..0000000
--- a/docs/guide/ags/binding.md
+++ /dev/null
@@ -1,234 +0,0 @@
-# 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/cli-app.md b/docs/guide/ags/cli-app.md
deleted file mode 100644
index ec6d174..0000000
--- a/docs/guide/ags/cli-app.md
+++ /dev/null
@@ -1,167 +0,0 @@
-# CLI and App
-
-`App` is a singleton **instance** of [Astal.Application](https://aylur.github.io/libastal/class.Application.html).
-
-```ts
-import { App } from "astal"
-```
-
-## Entry point
-
-:::code-group
-
-```ts [app.ts]
-App.start({
- main() {
- // setup anything
- // instantiate widgets
- },
-})
-```
-
-:::
-
-:::warning
-You can not instantiate widgets outside of the main function.
-:::
-
-## Instance identifier
-
-You can run multiple instance by defining a unique instance name.
-
-```ts
-App.start({
- instanceName: "my-instance", // defaults to "astal"
- main() {},
-})
-```
-
-## Messaging from CLI
-
-If you want to interact with an instance from the cli, you can do so by sending a message.
-
-```ts
-App.start({
- main() {},
- requestHandler(request: string, res: (response: any) => void) {
- if (request == "say hi") {
- res("hi cli")
- }
- res("unknown command")
- },
-})
-```
-
-:::code-group
-
-```sh [ags]
-ags -m "say hi"
-# hi cli
-```
-
-```sh [astal]
-astal say hi
-# hi cli
-```
-
-:::
-
-If you want to run arbitrary JavaScript from cli, you can use `App.eval`.
-It will evaluate the passed string as the body of an `async` function.
-
-```ts
-App.start({
- main() {},
- requestHandler(js: string, res) {
- App.eval(js).then(res).catch(res)
- },
-})
-```
-
-If the string does not contain a semicolon, a single expression is assumed and returned implicity.
-
-```sh
-ags -m "'hello'"
-# hello
-```
-
-If the string contains a semicolon, you have to return explicitly
-
-```sh
-ags -m "'hello';"
-# undefined
-
-ags -m "return 'hello';"
-# hello
-```
-
-## Toggling Windows by their name
-
-In order for AGS to know about your windows, you have to register them.
-You can do this by specifying a **unique** `name` and calling `App.add_window`
-
-```tsx {4}
-import { App } from "astal"
-
-function Bar() {
- return <window name="Bar" setup={self => App.add_window(self)}>
- <box />
- </window>
-}
-```
-
-You can also invoke `App.add_window` by simply passing the `App` to the `application` prop.
-
-```tsx {4}
-import { App } from "astal"
-
-function Bar() {
- return <window name="Bar" application={App}>
- <box />
- </window>
-}
-```
-
-:::warning
-When assigning the `application` prop make sure `name` comes before.
-Props are set sequentially and if name is applied after application it won't work.
-:::
-
-```sh
-ags -t Bar
-```
-
-## App without AGS
-
-As mentioned before AGS is only a scaffolding tool. You can setup
-a dev environment and a bundler yourself. In which case you won't be using
-the ags cli to run the bundled scripts. The produced script can run as the main instance
-and a "client" instance.
-
-The first time you run your bundled script the `main` function gets executed.
-While that instance is running any subsequent execution of the script will call
-the `client` function.
-
-:::code-group
-
-```ts [main.ts]
-App.start({
- // main instance
- main(...args: Array<string>) {
- print(...args)
- },
-
- // every subsequent calls
- client(message: (msg: string) => string, ...args: Array<string>) {
- const res = message("you can message the main instance")
- console.log(res)
- },
-
- // this runs in the main instance
- requestHandler(request: string, res: (response: any) => void) {
- res("response from main")
- },
-})
-```
-
-:::
diff --git a/docs/guide/ags/faq.md b/docs/guide/ags/faq.md
deleted file mode 100644
index 76d8e72..0000000
--- a/docs/guide/ags/faq.md
+++ /dev/null
@@ -1,266 +0,0 @@
-# Frequently asked question, common issues, tips and tricks
-
-## Monitor id does not match compositor
-
-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.
-
-```tsx
-import { App } from "astal"
-
-function Bar(gdkmonitor) {
- return <window gdkmonitor={gdkmonitor} />
-}
-
-function main() {
- for (const monitor of App.get_monitors()) {
- if (monitor.model == "your-desired-model") {
- Bar(monitor)
- }
- }
-}
-
-App.start({ main })
-```
-
-## Environment variables
-
-JavaScript is **not** an bash.
-
-```ts
-const HOME = exec("echo $HOME") // does not work
-```
-
-`exec` and `execAsync` runs the passed program as is, its **not** run in a
-shell environment, so the above example just passes `$HOME` as a string literal
-to the `echo` program.
-
-:::danger Please don't do this
-You could pass it to bash, but that is a horrible approach.
-
-```ts
-const HOME = exec("bash -c 'echo $HOME'")
-```
-
-:::
-
-You can read environment variables with [GLib.getenv](https://gjs-docs.gnome.org/glib20~2.0/glib.getenv).
-
-```ts
-import GLib from "gi://GLib"
-
-const HOME = GLib.getenv("HOME")
-```
-
-## 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`
-
-:::code-group
-
-```ts [app.ts]
-App.start({
- icons: `${SRC}/icons`,
- main() {
- Widget.Icon({
- icon: "custom-symbolic", // custom-symbolic.svg
- css: "color: green;", // can be colored, like other named icons
- })
- },
-})
-```
-
-:::
-
-:::info
-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.
-
-```ts
-print("print this line to stdout")
-printerr("print this line to stderr")
-```
-
-## Populate the global scope with frequently accessed variables
-
-It might be annoying to always import Gtk only for `Gtk.Align` enums.
-
-:::code-group
-
-```ts [globals.ts]
-import Gtk from "gi://Gtk"
-
-declare global {
- const START: number
- const CENTER: number
- const END: number
- const FILL: number
-}
-
-Object.assign(globalThis, {
- START: Gtk.Align.START,
- CENTER: Gtk.Align.CENTER,
- END: Gtk.Align.END,
- FILL: Gtk.Align.FILL,
-})
-```
-
-:::
-
-:::code-group
-
-```tsx [Bar.tsx]
-export default function Bar() {
- return <window>
- <box halign={START} />
- </window>
-}
-```
-
-:::
-
-:::code-group
-
-```ts [app.ts]
-import "./globals"
-import Bar from "./Bar"
-
-App.start({
- main: Bar
-})
-```
-
-:::
-
-:::info
-It is considered bad practice to populate the global scope, but its your code, not a public library.
-:::
-
-## Auto create Window for each Monitor
-
-To have Window widgets appear on a monitor when its plugged in, listen to `App.monitor_added`.
-
-:::code-group
-
-```tsx [Bar.tsx]
-export default function Bar(gdkmonitor: Gdk.Monitor) {
- return <window gdkmonitor={gdkmonitor} />
-}
-```
-
-:::
-
-:::code-group
-
-```ts [app.ts]
-import { Gdk, Gtk } from "astal"
-import Bar from "./Bar"
-
-function main() {
- const bars = new Map<Gdk.Monitor, Gtk.Widget>()
-
- // initialize
- for (const gdkmonitor of App.get_monitors()) {
- bars.set(gdkmonitor, Bar(gdkmonitor))
- }
-
- App.connect("monitor-added", (_, gdkmonitor) => {
- bars.set(gdkmonitor, Bar(gdkmonitor))
- })
-
- App.connect("monitor-removed", (_, gdkmonitor) => {
- bars.get(gdkmonitor)?.destroy()
- bars.delete(gdkmonitor)
- })
-}
-
-App.start({ main })
-```
-
-:::
-
-## Error: Can't convert non-null pointer to JS value
-
-These happen when accessing list type properties. Gjs fails to correctly bind
-`List` and other array like types of Vala as a property.
-
-```ts
-import Notifd from "gi://AstalNotifd"
-const notifd = Notifd.get_default()
-
-notifd.notifications // ❌ // [!code error]
-
-notifd.get_notifications() // ✅
-```
-
-## How to create regular floating windows
-
-Use `Gtk.Window` with [Widget.astalify](/guide/ags/widget#how-to-use-non-builtin-gtk-widgets).
-
-By default `Gtk.Window` is destroyed on close. To prevent this add a handler for `delete-event`.
-
-```tsx {4-7}
-const RegularWindow = Widget.astalify(Gtk.Window)
-
-return <RegularWindow
- onDeleteEvent={(self) => {
- self.hide()
- return true
- }}
->
- {child}
-</RegularWindow>
-```
-
-## 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.
-
-## Custom widgets with bindable properties
-
-In function components you can wrap any primitive to handle both
-binding and value cases as one.
-
-```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
- })
- }
-
- return <box setup={setup}>
- </box>
-}
-```
-
-You can pass the prop the super constructor in subclasses
-
-```tsx
-@register()
-class MyWidget extends Widget.Box {
- @property(String)
- set prop(v: string) {
- // handler
- }
-
- constructor(props: { prop: string | Binding<string> }) {
- super(props)
- }
-}
-```
diff --git a/docs/guide/ags/first-widgets.md b/docs/guide/ags/first-widgets.md
deleted file mode 100644
index 6dba7b3..0000000
--- a/docs/guide/ags/first-widgets.md
+++ /dev/null
@@ -1,402 +0,0 @@
-# First Widgets
-
-AGS is the predecessor of Astal, which was written purely in TypeScript and so only supported
-JavaScript/TypeScript. Now it serves as a scaffolding tool for Astal projects in TypeScript.
-While what made AGS what it is, is now part of the Astal project, for simplicity we will
-refer to the Astal TypeScript lib as AGS.
-
-:::tip
-If you are not familiar with the JavaScript syntax [MDN](https://developer.mozilla.org/en-US/)
-and [javascript.info](https://javascript.info/) have great references.
-:::
-
-## Getting Started
-
-Start by initializing a project
-
-```sh
-ags --init
-```
-
-then run `ags` in the terminal
-
-```sh
-ags
-```
-
-Done! You have now a custom written bar using Gtk.
-
-:::tip
-AGS will transpile every `.ts`, `.jsx` and `.tsx` files into regular javascript then
-it will bundle everything into a single javascript file which then GJS can execute.
-The bundler used is [esbuild](https://esbuild.github.io/).
-:::
-
-## Root of every shell component: Window
-
-Astal apps are composed of widgets. A widget is a piece of UI that has its own logic and style.
-A widget can be as small as a button or an entire bar.
-The top level widget is always a [Window](https://aylur.github.io/libastal/class.Window.html) which will hold all widgets.
-
-::: code-group
-
-```tsx [widget/Bar.tsx]
-function Bar(monitor = 0) {
- return <window className="Bar" monitor={monitor}>
- <box>Content of the widget</box>
- </window>
-}
-```
-
-:::
-
-::: code-group
-
-```ts [app.ts]
-import Bar from "./widget/Bar"
-
-App.start({
- main() {
- Bar(0)
- Bar(1) // instantiate for each monitor
- },
-})
-```
-
-:::
-
-## Creating and nesting widgets
-
-Widgets are JavaScript functions which return Gtk widgets,
-either by using JSX or using a widget constructor.
-
-:::code-group
-
-```tsx [MyButton.tsx]
-function MyButton(): JSX.Element {
- return <button onClicked="echo hello">
- <label label="Click me!" />
- </button>
-}
-```
-
-```ts [MyButton.ts]
-import { Widget } from "astal"
-
-function MyButton(): Widget.Button {
- return new Widget.Button(
- { onClicked: "echo hello" },
- new Widget.Label({ label: "Click me!" }),
- )
-}
-```
-
-:::
-
-:::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 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.
-:::
-
-Now that you have declared `MyButton`, you can nest it into another component.
-
-```tsx
-function MyBar() {
- return <window>
- <box>
- Click The button
- <MyButton />
- </box>
- </window>
-}
-```
-
-Notice that widgets you defined start with a capital letter `<MyButton />`.
-Lowercase tags are builtin widgets, while capital letter is for custom widgets.
-
-## Displaying Data
-
-JSX lets you put markup into JavaScript.
-Curly braces let you “escape back” into JavaScript so that you can embed some variable
-from your code and display it.
-
-```tsx
-function MyWidget() {
- const label = "hello"
-
- return <button>{label}</button>
-}
-```
-
-You can also pass JavaScript to markup attributes
-
-```tsx
-function MyWidget() {
- const label = "hello"
-
- return <button label={label} />
-}
-```
-
-## Conditional Rendering
-
-You can use the same techniques as you use when writing regular JavaScript code.
-For example, you can use an if statement to conditionally include JSX:
-
-```tsx
-function MyWidget() {
- let content
-
- if (condition) {
- content = <True />
- } else {
- content = <False />
- }
-
- return <box>{content}</box>
-}
-```
-
-You can also inline a [conditional `?`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_operator) (ternary) expression.
-
-```tsx
-function MyWidget() {
- return <box>{condition ? <True /> : <False />}</box>
-}
-```
-
-When you don’t need the `else` branch, you can also use a shorter [logical && syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND#short-circuit_evaluation):
-
-```tsx
-function MyWidget() {
- return <box>{condition && <True />}</box>
-}
-```
-
-:::info
-As you can guess from the above snippet, [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) values are not rendered.
-:::
-
-## Rendering lists
-
-You can use [`for` loops](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for) or [array `map()` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
-
-```tsx
-function MyWidget() {
- const labels = [
- "label1"
- "label2"
- "label3"
- ]
-
- return <box>
- {labels.map(label => (
- <label label={label} />
- ))}
- </box>
-}
-```
-
-## Widget signal handlers
-
-You can respond to events by declaring event handler functions inside your widget:
-
-```tsx
-function MyButton() {
- function onClicked(self: Widget.Button, ...args) {
- console.log(self, "was clicked")
- }
-
- return <button onClicked={onClicked} />
-}
-```
-
-The handler can also be a string, which will get executed in a subprocess asynchronously.
-
-```tsx
-function MyButton() {
- return <button onClicked="echo hello" />
-}
-```
-
-:::info
-Attributes prefixed with `on` will connect to a `signal` of the widget.
-Their types are not generated, but written by hand, which means not all of them are typed.
-Refer to the Gtk and Astal docs to have a full list of them.
-:::
-
-## How properties are passed
-
-Using JSX, a custom widget will always have a single object as its parameter.
-
-```ts
-type Props = {
- myprop: string
- child?: JSX.Element // when only one child is passed
- children?: Array<JSX.Element> // when multiple children are passed
-}
-
-function MyWidget({ myprop, child, children }: Props) {
- //
-}
-```
-
-```tsx
-// child prop of MyWidget is the box
-return <MyWidget myprop="hello">
- <box />
-</MyWidget>
-```
-
-```tsx
-// children prop of MyWidget is [box, box, box]
-return <MyWidget myprop="hello">
- <box />
- <box />
- <box />
-</MyWidget>
-```
-
-## State management
-
-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.
-
-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:
-
-```tsx
-import { Variable, bind } from "astal"
-
-function Counter() {
- const count = Variable(0)
-
- function increment() {
- count.set(count.get() + 1)
- }
-
- return <box>
- <label label={bind(count).as(num => num.toString())} />
- <button onClicked={increment}>
- Click to increment
- <button>
- </box>
-}
-```
-
-:::info
-Bindings have an `.as()` method which lets you transform the assigned value.
-In the case of a Label, its label property expects a string, so it needs to be
-turned to a string first.
-:::
-
-:::tip
-`Variables` have a shorthand for `bind(variable).as(transform)`
-
-```tsx
-const v = Variable(0)
-const transform = (v) => v.toString()
-
-return <box>
- {/* these two are equivalent */}
- <label label={bind(v).as(transform)} />
- <label label={v(transform)} />
-</box>
-```
-
-:::
-
-Here is an example of a battery percent label that binds the `percentage`
-property of the Battery object from the [Battery Library](/guide/libraries/battery):
-
-```tsx
-import Battery from "gi://AstalBattery"
-import { bind } from "astal"
-
-function BatteryPercentage() {
- const bat = Battery.get_default()
-
- return <label label={bind(bat, "percentage").as((p) => p * 100 + " %")} />
-}
-```
-
-## Dynamic children
-
-You can also use a `Binding` for `child` and `children` properties.
-
-```tsx
-const child = Variable(<box />)
-
-return <box>{child}</box>
-```
-
-```tsx
-const num = Variable(3)
-const range = (n) => [...Array(n).keys()]
-
-return <box>
- {num(n => range(n).map(i => (
- <button>
- {i.toString()}
- <button/>
- )))}
-<box>
-```
-
-:::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 **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. 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>>`.
-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 }) {
- // children is a Binding over an Array of widgets
-}
-
-return <MyContainer>
- <box />
- {num(n => range(n).map(i => (
- <button>
- {i.toString()}
- <button/>
- )))}
- [
- [
- <button />
- ]
- <button />
- ]
-</MyContainer>
-```
-
-:::info
-You can pass the followings as children:
-
-- widgets
-- deeply nested arrays of widgets
-- bindings of widgets,
-- bindings of deeply nested arrays of widgets
-
-[falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) values are not rendered and anything not from this list
-will be coerced into a string and rendered as a label
-:::
diff --git a/docs/guide/ags/gobject.md b/docs/guide/ags/gobject.md
deleted file mode 100644
index 83b7c45..0000000
--- a/docs/guide/ags/gobject.md
+++ /dev/null
@@ -1,166 +0,0 @@
-# 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/installation.md b/docs/guide/ags/installation.md
deleted file mode 100644
index 0adcf68..0000000
--- a/docs/guide/ags/installation.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# Installation
-
-## Nix
-
-maintainer: [@Aylur](https://github.com/Aylur)
-
-Read more about it on the [nix page](../getting-started/nix#ags)
-
-You can try without installing.
-
-<!--TODO: remove v2 after merge-->
-```sh
-nix run github:aylur/ags/v2 -- --help
-```
-
-## Bulding AGS from source
-
-1. [Install Astal](../getting-started/installation.md) if you have not already
-
-2. Install the following dependencies
-
-:::code-group
-
-```sh [<i class="devicon-archlinux-plain"></i> Arch]
-sudo pacman -Syu go npm gjs
-```
-
-```sh [<i class="devicon-fedora-plain"></i> Fedora]
-sudo dnf install golang npm gjs
-```
-
-```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
-sudo apt install golang-go npm gjs
-```
-
-:::
-
-3. Clone the repo and Install
-
-<!--TODO: remove v2 after merge-->
-```sh
-git clone https://github.com/aylur/ags.git
-cd ags/src
-git checkout v2 # https://github.com/Aylur/ags/pull/504
-go install
-```
-
-:::info
-If you have installed Astal **not** in `/usr` make sure to set its path.
-
-```sh
-go install -ldflags "-X main.astalGjs=$(pkg-config --variable prefix astal-0.1)/share/astal/gjs"
-```
-
-:::
-
-:::tip
-`go install` installs the `ags` binary to `$GOPATH/bin` so make sure its in your `$PATH`.
-You can move it to another directory if you like. For example
-
-```sh
-mv $GOPATH/bin/ags ~/.local/bin/ags
-```
-
-:::
diff --git a/docs/guide/ags/theming.md b/docs/guide/ags/theming.md
deleted file mode 100644
index cb0ebff..0000000
--- a/docs/guide/ags/theming.md
+++ /dev/null
@@ -1,161 +0,0 @@
-# Theming
-
-Since the widget toolkit is **GTK3** theming is done with **CSS**.
-
-- [CSS tutorial](https://www.w3schools.com/css/)
-- [GTK CSS Overview wiki](https://docs.gtk.org/gtk3/css-overview.html)
-- [GTK CSS Properties Overview wiki](https://docs.gtk.org/gtk3/css-properties.html)
-
-:::warning GTK is not the web
-While most features are implemented in GTK,
-you can't assume anything that works on the web will work with GTK.
-Refer to the [GTK docs](https://docs.gtk.org/gtk3/css-overview.html)
-to see what is available.
-:::
-
-So far every widget you made used your default GTK3 theme.
-To make them more custom, you can apply stylesheets to them.
-
-## From file at startup
-
-You can pass a path to a file or css as a string in `App.start`
-
-:::code-group
-
-```ts [app.ts]
-const inlineCss = `
-window {
- background-color: transparent;
-}
-`
-
-App.start({
- css: "./style.css",
- css: `${SRC}/style.css'`,
- css: inlineCss,
-})
-```
-
-:::
-
-:::info
-The global `SRC` will point to the directory `app.ts` is in.
-AGS will set the current working directory to `--config`, so `./style.css` also works.
-:::
-
-## Css Property on Widgets
-
-```ts
-Widget.Label({
- css: "color: blue; padding: 1em;",
- label: "hello",
-})
-```
-
-:::info
-The `css` property of a widget will not cascade to its children.
-:::
-
-## Apply Stylesheets at Runtime
-
-You can apply additional styles at runtime.
-
-```ts
-App.apply_css("/path/to/file.css")
-```
-
-```ts
-App.apply_css(`
-window {
- background-color: transparent;
-}
-`)
-```
-
-```ts
-App.reset_css() // reset if need
-```
-
-:::warning
-`App.apply_css` will apply on top of other stylesheets applied before.
-You can reset stylesheets with `App.resetCss`
-:::
-
-## Inspector
-
-If you are not sure about the widget hierarchy or any CSS selector,
-you can use the [GTK inspector](https://wiki.gnome.org/Projects/GTK/Inspector)
-
-```sh
-# to bring up the inspector run
-ags --inspector
-```
-
-## Using SCSS
-
-Gtk's CSS only supports a subset of what the web offers.
-Most notably nested selectors are unsupported by Gtk, but this can be
-workaround by using preprocessors like [SCSS](https://sass-lang.com/).
-
-:::code-group
-
-```sh [<i class="devicon-archlinux-plain"></i> Arch]
-sudo pacman -Syu dart-sass
-```
-
-```sh [<i class="devicon-fedora-plain"></i> Fedora]
-npm install -g sass # not packaged on Fedora
-```
-
-```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
-npm install -g sass # not packaged on Ubuntu
-```
-
-:::
-
-Importing `scss` files will simply return transpiled css.
-
-:::code-group
-
-```ts [app.ts]
-import style from "./style.scss"
-
-App.start({
- css: style,
- main() {},
-})
-```
-
-:::
-
-:::tip
-If you for example want to set scss varibles from JS,
-You can inline import, compose, and transpile yourself.
-
-```ts [app.ts]
-import style1 from "inline:./style1.scss"
-import style2 from "inline:./style2.scss"
-
-const tmpscss = "/tmp/style.scss"
-const target = "/tmp/style.css"
-
-writeFile(tmpscss, `
- $var1: red;
- $var1: blue;
- ${style1}
- ${style1}
-`)
-
-exec(`sass ${tmpscss} ${target}`)
-
-App.start({
- css: target,
-})
-
-```
-
-:::
-
-:::info
-If you want other preprocessors support builtin open an Issue.
-:::
diff --git a/docs/guide/ags/utilities.md b/docs/guide/ags/utilities.md
deleted file mode 100644
index aae255c..0000000
--- a/docs/guide/ags/utilities.md
+++ /dev/null
@@ -1,174 +0,0 @@
-# Utilities
-
-## File functions
-
-Import them from `astal` or `astal/file`
-
-```ts
-import {
- readFile,
- readFileAsync,
- writeFile,
- writeFileAsync,
- monitorFile,
-} from "astal"
-```
-
-### Reading files
-
-```ts
-function readFile(path: string): string
-function readFileAsync(path: string): Promise<string>
-```
-
-### Writing files
-
-```ts
-function writeFile(path: string, content: string): void
-function writeFileAsync(path: string, content: string): Promise<void>
-```
-
-### Monitoring files
-
-```ts
-function monitorFile(
- path: string,
- callback: (file: string, event: Gio.FileMonitorEvent) => void,
-): Gio.FileMonitor
-```
-
-## Timeouts and Intervals
-
-Import them from `astal` or `astal/time`
-
-```ts
-import { interval, timeout, idle } from "astal"
-```
-
-You can use javascript native `setTimeout` or `setInterval`
-they return a [GLib.Source](https://docs.gtk.org/glib/struct.Source.html) instance.
-Alternatively you can use these functions provided by Astal,
-which return an [Astal.Time](https://aylur.github.io/libastal/class.Time.html) instance.
-
-`Astal.Time` has a `now` signal and a `cancelled` signal.
-
-### Interval
-
-Will immediately execute the function and every `interval` millisecond.
-
-```ts
-function interval(interval: number, callback?: () => void): Astal.Time
-```
-
-### Timeout
-
-Will execute the `callback` after `timeout` millisecond.
-
-```ts
-function timeout(timeout: number, callback?: () => void): Astal.Time
-```
-
-### Idle
-
-Executes `callback` whenever there are no higher priority events pending.
-
-```ts
-function idle(callback?: () => void): Astal.Time
-```
-
-Example:
-
-```ts
-const timer = interval(1000, () => {
- console.log("optional callback")
-})
-
-timer.connect("now", () => {
- console.log("tick")
-})
-
-timer.connect("cancelled", () => {
- console.log("cancelled")
-})
-
-timer.cancel()
-```
-
-## Process functions
-
-Import them from `astal` or `astal/proc`
-
-```ts
-import { subprocess, exec, execAsync } from "astal"
-```
-
-### Subprocess
-
-You can start a subprocess and run callback functions whenever it outputs to
-stdout or stderr. [Astal.Process](https://aylur.github.io/libastal/class.Process.html) has a `stdout` and `stderr` signal.
-
-```ts
-function subprocess(args: {
- cmd: string | string[]
- out?: (stdout: string) => void
- err?: (stderr: string) => void
-}): Astal.Process
-
-function subprocess(
- cmd: string | string[],
- onOut?: (stdout: string) => void,
- onErr?: (stderr: string) => void,
-): Astal.Process
-```
-
-Example:
-
-```ts
-const proc = subprocess(
- "some-command",
- (out) => console.log(out), // optional
- (err) => console.error(out), // optional
-)
-
-// or with signals
-const proc = subprocess("some-command")
-proc.connect("stdout", (out) => console.log(out))
-proc.connect("stderr", (err) => console.error(err))
-```
-
-### Executing external commands and scripts
-
-```ts
-function exec(cmd: string | string[]): string
-function execAsync(cmd: string | string[]): Promise<string>
-```
-
-Example:
-
-```ts
-try {
- const out = exec("/path/to/script")
- console.log(out)
-} catch (err) {
- console.error(err)
-}
-
-execAsync(["bash", "-c", "/path/to/script.sh"])
- .then((out) => console.log(out))
- .catch((err) => console.error(err))
-```
-
-:::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`,
-and they do **not** handle logical operators like `&&` and `||`.
-
-If you want bash, run them with bash.
-
-```ts
-exec(["bash", "-c", "command $VAR && command"])
-exec("bash -c 'command $VAR' && command")
-```
-
-:::
diff --git a/docs/guide/ags/variable.md b/docs/guide/ags/variable.md
deleted file mode 100644
index 4207f61..0000000
--- a/docs/guide/ags/variable.md
+++ /dev/null
@@ -1,153 +0,0 @@
-# Variable
-
-```js
-import { Variable } from "astal"
-```
-
-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).
-:::
-
-## Example Usage
-
-```typescript
-const myvar = Variable("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
-})
-```
-
-:::warning
-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.
-:::
-
-## Variable Composition
-
-Using `Variable.derive` any `Subscribable` object can be composed.
-
-```typescript
-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 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 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`,
-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(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(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()
-```
-
-:::warning
-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()
- 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
deleted file mode 100644
index 01cff81..0000000
--- a/docs/guide/ags/widget.md
+++ /dev/null
@@ -1,208 +0,0 @@
-# Widget
-
-## Additional widget properties
-
-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; }`.
-- cursor: `string` - Cursor style when hovering over widgets that have hover states, e.g it won't work on labels. [list of valid values](https://docs.gtk.org/gdk3/ctor.Cursor.new_from_name.html).
-- clickThrough: `boolean` - Lets click events through.
-
-To have a full list of available properties, reference the documentation of the widget.
-
-- [Astal widgets](https://aylur.github.io/libastal/index.html#classes)
-- [Gtk widgets](https://docs.gtk.org/gtk3/#classes)
-
-## Additional widget methods
-
-### setup
-
-`setup` is a convenience prop to remove the need to predefine widgets
-before returning them in cases where a reference is needed.
-
-without `setup`
-
-```tsx
-function MyWidget() {
- const button = Widget.Button()
- // setup button
- return button
-}
-```
-
-using `setup`
-
-```tsx
-function MyWidget() {
- function setup(button: Widget.Button) {
- // setup button
- }
-
- return <buttons setup={setup} />
-}
-```
-
-### hook
-
-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()
- }}
- />
-}
-```
-
-with `hook`
-
-```tsx
-function MyWidget() {
- return <box
- setup={(self) => {
- self.hook(gobject, "signal", callback)
- self.hook(variable, callback)
- }}
- />
-}
-```
-
-### toggleClassName
-
-Toggle classNames based on a condition
-
-```tsx
-function MyWidget() {
- return <box
- setup={(self) => {
- self.toggleClassName("classname", someCondition)
- }}
- />
-}
-```
-
-## How to use non builtin Gtk widgets
-
-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
-- sets up signal handlers that are passed as props prefixed with `on`
-
-```tsx
-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) {
-
- }
-
- return <ColorButton
- setup={setup}
- useAlpha
- rgba={new Gdk.RGBA({
- red: 1,
- green: 0,
- blue: 0,
- alpha: 0.5,
- })}
- onColorSet={(self) => {
- console.log(self.rgba)
- }}
- />
-}
-```
-
-:::info
-Signal properties have to be annotated manually for TypeScript.
-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.
-:::
-
-## TypeScript
-
-Type of widgets are available through `Widget`.
-Here is an example Widget that takes in and handles a possibly `Binding` prop.
-
-```tsx
-import { Binding, Variable, Widget } from "astal"
-
-export interface ToggleButtonProps extends Widget.ButtonProps {
- onToggled?: (self: Widget.Button, on: boolean) => void
- state?: Binding<boolean> | boolean
- child?: JSX.Element
-}
-
-export default function ToggleButton(btnprops: ToggleButtonProps) {
- const { state = false, onToggled, setup, child, ...props } = btnprops
- const innerState = Variable(state instanceof Binding ? state.get() : state)
-
- return <button
- {...props}
- setup={self => {
- setup?.(self)
-
- self.toggleClassName("active", innerState.get())
- self.hook(innerState, () => self.toggleClassName("active", innerState.get()))
-
- if (state instanceof Binding) {
- self.hook(state, () => innerState.set(state.get()))
- }
- }}
- onClicked={self => {
- onToggled?.(self, !innerState.get())
- }}
- >
- {child}
- </button>
-}
-```
-
-## Builtin Widgets
-
-You can check the [source code](https://github.com/aylur/astal/blob/main/core/gjs/src/widgets.ts) to have a full list of builtin widgets.
-
-These widgets are available by default in JSX.
-
-- box: [Astal.Box](https://aylur.github.io/libastal/class.Box.html)
-- button: [Astal.Button](https://aylur.github.io/libastal/class.Button.html)
-- centerbox: [Astal.CenterBox](https://aylur.github.io/libastal/class.CenterBox.html)
-- circularprogress: [Astal.CircularProgress](https://aylur.github.io/libastal/class.CircularProgress.html)
-- drawingarea: [Gtk.DrawingArea](https://docs.gtk.org/gtk3/class.DrawingArea.html)
-- entry: [Gtk.Entry](https://docs.gtk.org/gtk3/class.Entry.html)
-- eventbox: [Astal.EventBox](https://aylur.github.io/libastal/class.EventBox.html)
-- icon: [Astal.Icon](https://aylur.github.io/libastal/class.Icon.html)
-- label: [Astal.Label](https://aylur.github.io/libastal/class.Label.html)
-- levelbar: [Astal.LevelBar](https://aylur.github.io/libastal/class.LevelBar.html)
-- overlay: [Astal.Overlay](https://aylur.github.io/libastal/class.Overlay.html)
-- revealer: [Gtk.Revealer](https://docs.gtk.org/gtk3/class.Revealer.html)
-- scrollable: [Astal.Scrollable](https://aylur.github.io/libastal/class.Scrollable.html)
-- slider: [Astal.Slider](https://aylur.github.io/libastal/class.Slider.html)
-- stack: [Astal.Stack](https://aylur.github.io/libastal/class.Stack.html)
-- switch: [Gtk.Switch](https://docs.gtk.org/gtk3/class.Switch.html)
-- window: [Astal.Window](https://aylur.github.io/libastal/class.Window.html)