From 65654282a98cb0590d498e4ed9c924f97646f1dc Mon Sep 17 00:00:00 2001 From: Aylur Date: Wed, 25 Sep 2024 23:22:01 +0000 Subject: docs: restructure --- docs/guide/ags/cli-app.md | 131 +++++++++++++ docs/guide/ags/faq.md | 290 +++++++++++++++++++++++++++++ docs/guide/ags/first-widgets.md | 400 ++++++++++++++++++++++++++++++++++++++++ docs/guide/ags/installation.md | 65 +++++++ docs/guide/ags/theming.md | 169 +++++++++++++++++ docs/guide/ags/utilities.md | 174 +++++++++++++++++ docs/guide/ags/variable.md | 141 ++++++++++++++ docs/guide/ags/widget.md | 227 +++++++++++++++++++++++ 8 files changed, 1597 insertions(+) create mode 100644 docs/guide/ags/cli-app.md create mode 100644 docs/guide/ags/faq.md create mode 100644 docs/guide/ags/first-widgets.md create mode 100644 docs/guide/ags/installation.md create mode 100644 docs/guide/ags/theming.md create mode 100644 docs/guide/ags/utilities.md create mode 100644 docs/guide/ags/variable.md create mode 100644 docs/guide/ags/widget.md (limited to 'docs/guide/ags') diff --git a/docs/guide/ags/cli-app.md b/docs/guide/ags/cli-app.md new file mode 100644 index 0000000..ceed56a --- /dev/null +++ b/docs/guide/ags/cli-app.md @@ -0,0 +1,131 @@ +# 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 +``` + +## 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) { + print(...args) + }, + + // every subsequent calls + client(message: (msg: string) => string, ...args: Array) { + 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 new file mode 100644 index 0000000..6edc250 --- /dev/null +++ b/docs/guide/ags/faq.md @@ -0,0 +1,290 @@ +# Frequently asked question, common issues, tips and tricks + +## Monitor id does not match compositor + +The monitor 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 + +```tsx +import Hyprland from "gi://AstalHyprland" + +function Bar(gdkmonitor) { + return +} + +function main() { + for (const m of Hyprland.get_default().get_monitors()) { + Bar(m.gdk_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 `-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") +``` + +## Binding custom structures + +The `bind` function can take two types of objects. + +```ts +interface Subscribable { + 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