diff options
author | Kevin <[email protected]> | 2024-10-25 13:50:00 -0300 |
---|---|---|
committer | GitHub <[email protected]> | 2024-10-25 18:50:00 +0200 |
commit | 8306ec0789854f9e04bc1708c4a7dc2afc1b4c90 (patch) | |
tree | 881784894310510e4c62e2fc850ea934ca5aee86 /docs | |
parent | e8715aec5e05e0438192e611afea2fe6f10cb80f (diff) |
docs: lua docs (#50)
Diffstat (limited to 'docs')
-rw-r--r-- | docs/guide/lua/binding.md | 139 | ||||
-rw-r--r-- | docs/guide/lua/cli-app.md | 131 | ||||
-rw-r--r-- | docs/guide/lua/first-widgets.md | 264 | ||||
-rw-r--r-- | docs/guide/lua/installation.md | 24 | ||||
-rw-r--r-- | docs/guide/lua/theming.md | 130 | ||||
-rw-r--r-- | docs/guide/lua/utilities.md | 191 | ||||
-rw-r--r-- | docs/guide/lua/variable.md | 162 | ||||
-rw-r--r-- | docs/guide/lua/widget.md | 158 | ||||
-rw-r--r-- | docs/guide/typescript/binding.md | 6 | ||||
-rw-r--r-- | docs/guide/typescript/utilities.md | 4 | ||||
-rw-r--r-- | docs/guide/typescript/variable.md | 4 | ||||
-rw-r--r-- | docs/vitepress.config.ts | 12 |
12 files changed, 1214 insertions, 11 deletions
diff --git a/docs/guide/lua/binding.md b/docs/guide/lua/binding.md new file mode 100644 index 0000000..f4d5f0b --- /dev/null +++ b/docs/guide/lua/binding.md @@ -0,0 +1,139 @@ +# 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. + +```lua +---@class Binding<T> +---@field private transform_fn fun(value: T): any +---@field private emitter Connectable | Subscribable<T> +---@field private property? string +---@field as fun(transform: fun(value: T): any): Binding +---@field get fun(): T +---@field subscribe fun(self, callback: fun(value: T)): function +``` + +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). + +Lua type annotations are not expressive enough to explain this, +so I'll use TypeScript to demonstrate it. + +<!--TODO: use Teal maybe?--> + +```ts +function bind<T>(obj: Subscribable<T>): 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 +} +``` + +`Connectable` is usually used for GObjects coming from [libraries](../libraries/references) +You won't be implementing it in Lua code. + +## 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 + +```lua :line-numbers [varmap.lua] +local Gtk = require("astal.gtk3").Gtk +local Variable = require("astal.variable") + +---@param initial table +return function(initial) + local map = initial + local var = Variable() + + local function notify() + local arr + for _, value in pairs(map) do + table.insert(arr, value) + end + var:set(arr) + end + + local function delete(key) + if Gtk.Widget:is_type_of(map[key]) then + map[key]:destroy() + end + + map[key] = nil + end + + notify() -- init + + return { + set = function(key, value) + delete(key) + map[key] = value + notify() + end, + delete = function(key) + delete(key) + notify() + end, + get = function() + return var:get() + end, + subscribe = function(callback) + return var:subscribe(callback) + end, + } +end +``` + +::: + +And this `VarMap<key, Widget>` can be used as an alternative to `Variable<Array<Widget>>`. + +```lua +function MappedBox() + local map = varmap({ + ["1"] = Widget.Label({ label = "1" }), + ["2"] = Widget.Label({ label = "2" }), + }) + + return Widget.Box({ + setup = function (self) + self:hook(gobject, "added", function (_, id) + map.set(id, Widget.Label({ label = id })) + end) + self:hook(gobject, "removed", function (_, id) + map.delete(id) + end) + end, + bind(map):as(function (arr) + -- can be sorted here + return arr + end), + }) +end +``` diff --git a/docs/guide/lua/cli-app.md b/docs/guide/lua/cli-app.md new file mode 100644 index 0000000..6bfaa9e --- /dev/null +++ b/docs/guide/lua/cli-app.md @@ -0,0 +1,131 @@ +# CLI and App + +`App` is a singleton **instance** of an [Astal.Application](https://aylur.github.io/libastal/astal3/class.Application.html). + +Depending on gtk version require paths will differ + +<!--TODO: remove gtk4 notice when its available--> + +```lua +local App = require("astal.gtk3.app") + +local App = require("astal.gtk4.app") -- not yet available +``` + +## Entry point + +`App:start` is a wrapper function for `App:run` +that will only run the application if there is no +instance already running with the specified name. + +:::code-group + +```lua [init.lua] +App:start({ + instance_name = "my-instance", -- defaults to "astal" + main = function() + -- setup anything + -- instantiate widgets + end, +}) +``` + +::: + +## Messaging from CLI + +If you want to interact with an instance from the CLI, +you can do so by sending a request. + +```lua +App:start({ + main = function() end, + + ---@param request string + ---@param res fun(response: any): nil + request_handler = function(request, res) + if request == "say hi" then + return res("hi cli") + end + res("unknown command") + end +}) +``` + +```sh +astal say hi +# hi cli +``` + +## Toggling Windows by their name + +In order for Astal to know about your windows, you have to register them. +You can do this by specifying a **unique** `name` and calling `App:add_window`. + +```lua +local App = require("astal.gtk3.app") + +local function Bar() + return Widget.Window({ + name = "Bar", + setup = function(self) + App:add_window(self) + end, + Widget.Box() + }) +end +``` + +You can also invoke `App:add_window` by simply passing the `App` to the `application` prop. + +```lua +local App = require("astal.gtk3.app") + +local function Bar() + return Widget.Window({ + name = "Bar", + application = App, + Widget.Box() + }) +end +``` + +```sh +astal -t Bar +``` + +:::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. +::: + +## Client instances + +The first time `App:start` is invoked the `main` function gets called. +While that instance is running any subsequent execution of the script will call +the `client` function. + +:::code-group + +```lua [init.lua] +App:start({ + -- main instance + main = function(...) + local args = { ... } + print(string.format("{%s}", table.concat(args, ", "))) + end, + + -- client instance + client = function(request, ...) + local res = request("you can send a request to the main instance") + print(res) + end, + + -- this runs in the main instance + request_handler = function(request, res) + res("response from main instance") + end +}) +``` + +::: diff --git a/docs/guide/lua/first-widgets.md b/docs/guide/lua/first-widgets.md index 2abe7c5..efc1c4f 100644 --- a/docs/guide/lua/first-widgets.md +++ b/docs/guide/lua/first-widgets.md @@ -1,3 +1,265 @@ # First Widgets -🚧 Lua documentation is in Progress 🚧 +## Getting Started + +Start by importing the singleton +[Astal.Application](https://aylur.github.io/libastal/astal3/class.Application.html) instance. + +:::code-group + +```lua [init.lua] +local App = require("astal.gtk3.app") + +App:start({ + main = function() + -- you will instantiate Widgets here + -- or setup anything else if you need + print("hi") + end +}) +``` + +::: + +Then run `lua init.lua` in the terminal, and that's it! +Now you have an instance running with Lua. + +## 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/astal3/class.Window.html) +which will hold all widgets. + +::: code-group + +```lua [widget/Bar.lua] +local Widget = require("astal.gtk3.widget") +local Anchor = require("astal.gtk3").Astal.WindowAnchor + +return function(monitor) + return Widget.Window({ + monitor = monitor, + anchor = Anchor.TOP + Anchor.LEFT + Anchor.RIGHT, + exclusivity = "EXCLUSIVE", + Widget.Label({ + label = "Example label content", + }), + }) +end +``` + +::: + +::: code-group + +```lua [init.lua] +local App = require("astal.gtk3.app") +local Bar = require("widget.Bar") + +App:start { + main = function() + Bar(0) + Bar(1) -- instantiate for each monitor + end, +} +``` + +::: + +## Creating and nesting widgets + +Widgets are simply Lua functions that return Gtk widgets, +you can nest widgets by passing them as arguments to the table in the function. + +:::code-group + +```lua [widget/MyButton.lua] +local Widget = require("astal.gtk3.widget") + +return function(text) + return Widget.Button({ + on_click_release = function(_, event) + if event.button == "PRIMARY" then + print("Left click") + elseif event.button == "SECONDARY" then + print("Right click") + end + end, + Widget.Label({ + label = text, + }), + }) +end +``` + +::: + +Now, you should be able to nest it into another widgets. + +::: code-group + +```lua [widget/Bar.lua] {13} +local MyButton = require("widget.MyButton") +local Anchor = require("astal.gtk3").Astal.WindowAnchor + +return function(monitor) + return Widget.Window({ + monitor = monitor, + anchor = Anchor.TOP + Anchor.LEFT + Anchor.RIGHT, + exclusivity = "EXCLUSIVE", + Widget.Box({ + Widget.Label({ + label = "Click the button", + }), + MyButton("hi, im a button"), + }), + }) +end +``` + +::: + +## Widget signal handlers + +You can respond to events by declaring event handler functions inside your widget: + +```lua +local function MyButton() + return Widget.Button({ + on_click_release = function(_, event) + print(event.button) + end, + }) +end +``` + +:::info +Keys prefixed with `on_` will connect to a `signal` of the widget. +Refer to the Gtk and Astal docs to have a full list of them. +::: + +## 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: + +```lua +local astal = require("astal") +local bind = astal.bind +local Variable = astal.Variable +local Widget = require("astal.gtk3.widget") + +local function Counter() + local count = Variable(0) + return Widget.Box({ + Widget.Label({ + label = bind(count):as(tostring), + }), + Widget.Button({ + label = "Click to increment", + on_click_release = function() + count:set(count:get() + 1) + end, + }), + }) +end +``` + +:::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 +converted into a string first. +::: + +:::tip +`Variables` have a shorthand for `bind(variable):as(transform)` + +```lua +local v = Variable(0) + +return Widget.Box { + -- these three are equivalent + Widget.Label({ label = bind(v):as(tostring) }), + Widget.Label({ label = v():as(tostring) }), + Widget.Label({ label = v(tostring) }), +} +``` + +::: + +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): + +```lua +local astal = require("astal") +local bind = astal.bind +local Battery = astal.require("AstalBattery") +local Widget = require("astal.gtk3.widget") + +local function BatteryPercentage() + local bat = Battery.get_default() + + return Widget.Label({ + label = bind(bat, "percentage"):as(function(p) + return string.format("%.0f%%", p * 100) + end), + }) +end +``` + +## Dynamic children + +You can also use a `Binding` for `child` and `children` properties. + +```lua +local astal = require("astal") +local Variable = astal.Variable +local Widget = require("astal.gtk3.widget") + +local child = Variable(Widget.Box()) + +return Widget.Box({ + child(), +}) +``` + +```lua +local num = Variable(3) + +return Widget.Box { + num():as(function(n) + local tbl = {} + for i = 1, n do + table.insert(tbl, Widget.Button({ + label = tostring(i) + })) + end + return tbl + end) +} +``` + +:::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 `no_implicity_destroy` property on the container widget. +::: + +:::info +You can pass the followings as children: + +- widgets +- deeply nested arrays of widgets +- bindings of widgets, +- bindings of deeply nested arrays of widgets + +`nil` is the only value that is not rendered and anything not from this list +will be coerced into a string and rendered as a label. +::: diff --git a/docs/guide/lua/installation.md b/docs/guide/lua/installation.md index 48241f9..b99d8df 100644 --- a/docs/guide/lua/installation.md +++ b/docs/guide/lua/installation.md @@ -1,3 +1,25 @@ # Installation -🚧 Lua documentation is in Progress 🚧 +## Nix + +maintainer: [@Aylur](https://github.com/Aylur) + +Read more about it on the [nix page](../getting-started/nix) + +## Arch + +```sh +yay -S lua-libastal-git +``` + +## From Source + +1. [Install Astal](../getting-started/installation.md) if you have not already + +2. Install the Astal Lua package + +```sh +git clone https://github.com/aylur/astal.git +cd lang/lua +sudo luarocks make +``` diff --git a/docs/guide/lua/theming.md b/docs/guide/lua/theming.md new file mode 100644 index 0000000..502e8e9 --- /dev/null +++ b/docs/guide/lua/theming.md @@ -0,0 +1,130 @@ +# 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 + +```lua [init.lua] +local inline_css = [[ + window { + background-color: transparent; + } +]] + +App:start({ + css = inline_css, + css = "/path/to/style.css", + css = "./style.css", +}) +``` + +::: + +:::warning +When using relative paths, so for example `./style.css` keep in mind that they +will be relative to the current working directory. +::: + +## Css Property on Widgets + +```lua +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. + +```lua +App:apply_css("/path/to/file.css") +``` + +```lua +App:apply_css([[ + window { + background-color: transparent; + } +]]) +``` + +```lua +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:reset_css` +or by passing `true` as a second parameter to `App:apply_css`. +::: + +## 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 +astal --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 +``` + +::: + +:::code-group + +```lua [init.lua] +local scss = "./style.scss" +local css = "/tmp/style.css" + +astal.exec(string.format("sass %s %s", scss, css)) + +App:start({ + css = css, +}) +``` + +::: diff --git a/docs/guide/lua/utilities.md b/docs/guide/lua/utilities.md new file mode 100644 index 0000000..1ef70c7 --- /dev/null +++ b/docs/guide/lua/utilities.md @@ -0,0 +1,191 @@ +# Utilities + +## File functions + +```lua +local read_file = astal.read_file +local read_file_async = astal.read_file_async +local write_file = astal.write_file +local write_file_async = astal.write_file_async +local monitor_file = astal.monitor_file +``` + +### Reading files + +```lua +---@param path string +---@return string +local function read_file(path) end + +---@param path string +---@param callback fun(content: string, err: string): nil +local function read_file_async(path, callback) end +``` + +### Writing files + +```lua +---@param path string +---@param content string +local function write_file(path, content) end + +---@param path string +---@param content string +---@param callback? fun(err: string): nil +local function write_file_async(path, content, callback) end +``` + +### Monitoring files + +```lua +---@param path string +---@param callback fun(file: string, event: Gio.FileMonitorEvent): nil +local function monitor_file(path, callback) end +``` + +## Timeouts and Intervals + +```lua +local interval = astal.interval +local timeout = astal.timeout +local idle = astal.idle +``` + +### Interval + +Will immediately execute the function and every `interval` millisecond. + +```lua +---@param interval number +---@param callback? function +---@return Astal.Time +local function interval(interval, callback) end +``` + +### Timeout + +Will execute the `callback` after `timeout` millisecond. + +```lua +---@param timeout number +---@param callback? function +---@return Astal.Time +local function timeout(timeout, callback) end +``` + +### Idle + +Executes `callback` whenever there are no higher priority events pending. + +```lua +---@param callback? function +---@return Astal.Time +local function idle(callback) end +``` + +Example: + +```lua +local timer = interval(1000, function() + print("optional callback") +end) + +timer.on_now = function() + print("now") +end + +timer.on_cancelled = function() + print("cancelled") +end + +timer:cancel() +``` + +## Process functions + +```lua +local subprocess = astal.subprocess +local exec = astal.exec +local exec_async = astal.exec_async +``` + +### Subprocess + +You can start a subprocess and run callback functions whenever it outputs to +stdout or stderr. [Astal.Process](https://aylur.github.io/libastal/io/class.Process.html) has a `stdout` and `stderr` signal. + +```lua +---@param commandline string | string[] +---@param on_stdout? fun(out: string): nil +---@param on_stderr? fun(err: string): nil +---@return Astal.Process | nil +local function subprocess(commandline, on_stdout, on_stderr) end +``` + +Example: + +```lua +local proc = subprocess( + "some-command", + function(out) print(out) end, + function(err) print(err) end, +) + +-- with signals +local proc = subprocess("some-command") + +proc.on_stdout = function(_, stdout) + print(stdout) +end + +proc.on_stderr = function(_, stderr) + print(stderr) +end +``` + +### Executing external commands and scripts + +```lua +---@param commandline string | string[] +---@return string, string +local function exec(commandline) end + +---@param commandline string | string[] +---@param callback? fun(out: string, err: string): nil +local function exec_async(commandline, callback) end +``` + +Example: + +```lua +local out, err = exec("/path/to/script") + +if err ~= nil then + print(err) +else + print(out) +end + +exec_async({ "bash", "-c", "/path/to/script.sh" }, function(out, err) + if err ~= nil then + print(err) + else + print(out) + end +end) +``` + +:::warning +`subprocess`, `exec`, and `exec_async` 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. + +```lua +exec({ "bash", "-c", "command $VAR && command" }) +exec("bash -c 'command $VAR' && command") +``` + +::: diff --git a/docs/guide/lua/variable.md b/docs/guide/lua/variable.md new file mode 100644 index 0000000..915632c --- /dev/null +++ b/docs/guide/lua/variable.md @@ -0,0 +1,162 @@ +# Variable + +```lua +local Variable = require("astal").Variable +local Variable = require("astal.variable") +``` + +Variable is just a simple object which holds a single value. +It also has some shortcuts for hooking up subprocesses, intervals and other gobjects. + +## Example Usage + +```lua +local my_var = Variable("initial-value") + +-- whenever its value changes, callback will be executed +my_var:subscribe(function(value) + print(value) +end) + +-- settings its value +my_var:set("new value") + +-- getting its value +local value = my_var:get() + +-- binding them to widgets +Widget.Label({ + label = bind(my_var):as(function(value) + return string.format("transformed %s", value) + end), + -- shorthand for the above + label = my_var(function(value) + return string.format("transformed %s", value) + end) +}) +``` + +:::warning +Make sure to the transform functions you pass 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. + +```lua +local v1 = Variable(1) -- Variable +local v2 = bind(obj, "prop") -- Binding +local v3 = { -- Subscribable + get = function() + return 3 + end, + subscribe = function() + return function() end + end, +} + +-- 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 +local v4 = Variable.derive({ v1, v2, v3 }, function(v1, v2, v3) + return v1 * v2 * v3 +end) +``` + +## Subprocess shorthands + +Using `:poll` and `:watch` you 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 [exec_async](/guide/lua/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. + +```lua +Variable(""):poll(1000, { "bash", "-c", "command $VAR && command" }) +``` + +::: + +```lua +local my_var = Variable(0) + :poll(1000, "command", function(out, prev) + return tonumber(out) + end) + :poll(1000, { "bash", "-c", "command" }, function(out, prev) + return tonumber(out) + end) + :poll(1000, function(prev) + return prev + 1 + end) +``` + +```lua +local my_var = Variable(0) + :watch("command", function(out, prev) + return tonumber(out) + end) + :watch({ "bash", "-c", "command" }, function(out, prev) + return tonumber(out) + end) +``` + +You can temporarily stop them and restart them whenever. + +```lua +my_var:stop_watch() -- this kills the subprocess +my_var:stop_poll() + +my_var:start_listen() -- launches the subprocess again +my_var:start_poll() + +print(my_var:is_listening()) +print(my_var:is_polling()) +``` + +## Gobject connection shorthands + +Using `:observe` you can connect gobject signals and capture their value. + +```lua +local my_var = Variable("") + :observe(obj1, "signal", function() + return "" + end):observe(obj2, "signal", function() + return "" + end) +``` + +## Dispose if no longer needed + +This will stop the interval, force exit the subprocess and disconnect gobjects. + +```lua +my_var:drop() +``` + +:::warning +Don't forget to drop derived variables or variables with +either `:poll`, `:watch` or `:observe` when they are defined inside closures. + +```lua +local function MyWidget() + local my_var = Variable():poll() + + return Widget.Box({ + on_destroy = function() + my_var:drop() + end + }) +end +``` + +::: diff --git a/docs/guide/lua/widget.md b/docs/guide/lua/widget.md new file mode 100644 index 0000000..d9f99fa --- /dev/null +++ b/docs/guide/lua/widget.md @@ -0,0 +1,158 @@ +# Widget + +## Gtk3 + +### Additional widget properties + +These are properties that Astal additionally adds to Gtk.Widgets + +- class_name: `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). +- click_through: `boolean` - Lets click events through. + +To have a full list of available properties, reference the documentation of the widget. + +- [Astal3 widgets](https://aylur.github.io/libastal/astal3/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` + +```lua +local function MyWidget() + local button = Widget.Button() + -- setup button + return button +end +``` + +using `setup` + +```lua +local function MyWidget() + return Widget.Button({ + setup = function(self) + -- setup button + end, + }) +end +``` + +#### hook + +Shorthand for connecting and disconnecting to [Subscribable and Connectable](./binding#subscribable-and-connectable-interface) objects. + +without `hook` + +```lua +local function MyWidget() + local id = gobject.on_signal:connect(callback) + local unsub = variable:subscribe(callback) + + return Widget.Box({ + on_destroy = function() + GObject.signal_handler_disconnect(gobject, id) + unsub() + end, + }) +end +``` + +with `hook` + +```lua +local function MyWidget() + return Widget.Box({ + setup = function(self) + self:hook(gobject, "signal", callback) + self:hook(variable, callback) + end, + }) +end +``` + +#### toggle_class_name + +Toggle class names based on a condition. + +```lua +local function MyWidget() + return Widget.Box({ + setup = function(self) + self:toggle_class_name("classname", some_condition) + end, + }) +end +``` + +### How to use non builtin Gtk widgets + +Using the `astalify` function you can wrap widgets +to behave like builtin widgets. +It 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 + +```lua +local astal = require("astal.gtk3") +local astalify = astal.astalify +local Gtk = astal.Gtk +local Gdk = astal.Gdk + +local ColorButton = astalify(Gtk.ColorButton) + +local function MyWidget() + return ColorButton({ + setup = function(self) + -- setup ColorButton instance + end, + use_alpha = true, + rgba = Gdk.RGBA({ + red = 1, + green = 0, + blue = 0, + alpha = 0.5, + }), + on_color_set = function(self) + print(self.rgba:to_string()) + end + }) +end +``` + +### Builtin Widgets + +You can check the [source code](https://github.com/Aylur/astal/blob/main/lang/lua/astal/gtk3/widget.lua) to have a full list of builtin widgets. + +These widgets are available by default in Lua. + +- Box: [Astal.Box](https://aylur.github.io/libastal/astal3/class.Box.html) +- Button: [Astal.Button](https://aylur.github.io/libastal/astal3/class.Button.html) +- CenterBox: [Astal.CenterBox](https://aylur.github.io/libastal/astal3/class.CenterBox.html) +- CircularProgress: [Astal.CircularProgress](https://aylur.github.io/libastal/astal3/class.CircularProgress.html) +- DrawingArea: [Gtk.DrawingArea](https://docs.gtk.org/gtk3/astal3/class.DrawingArea.html) +- Entry: [Gtk.Entry](https://docs.gtk.org/gtk3/astal3/class.Entry.html) +- Eventbox: [Astal.EventBox](https://aylur.github.io/libastal/astal3/class.EventBox.html) +- Icon: [Astal.Icon](https://aylur.github.io/libastal/astal3/class.Icon.html) +- Label: [Astal.Label](https://aylur.github.io/libastal/astal3/class.Label.html) +- Levelbar: [Astal.LevelBar](https://aylur.github.io/libastal/astal3/class.LevelBar.html) +- Overlay: [Astal.Overlay](https://aylur.github.io/libastal/astal3/class.Overlay.html) +- Revealer: [Gtk.Revealer](https://docs.gtk.org/gtk3/astal3/class.Revealer.html) +- Scrollable: [Astal.Scrollable](https://aylur.github.io/libastal/astal3/class.Scrollable.html) +- Slider: [Astal.Slider](https://aylur.github.io/libastal/astal3/class.Slider.html) +- Stack: [Astal.Stack](https://aylur.github.io/libastal/astal3/class.Stack.html) +- Switch: [Gtk.Switch](https://docs.gtk.org/gtk3/astal3/class.Switch.html) +- Window: [Astal.Window](https://aylur.github.io/libastal/astal3/class.Window.html) + +## Gtk4 + +🚧 Work in Progress 🚧 diff --git a/docs/guide/typescript/binding.md b/docs/guide/typescript/binding.md index 05645ab..15fe3cc 100644 --- a/docs/guide/typescript/binding.md +++ b/docs/guide/typescript/binding.md @@ -25,11 +25,11 @@ 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<T>(obj: Subscribable<T>): Binding<T> function bind< - Obj extends Connectable, - Prop extends keyof Obj, + Obj extends Connectable, + Prop extends keyof Obj, >(obj: Obj, prop: Prop): Binding<Obj[Prop]> ``` diff --git a/docs/guide/typescript/utilities.md b/docs/guide/typescript/utilities.md index 02dfdaf..361c33b 100644 --- a/docs/guide/typescript/utilities.md +++ b/docs/guide/typescript/utilities.md @@ -128,8 +128,8 @@ const proc = subprocess( // or with signals const proc = subprocess("some-command") -proc.connect("stdout", (out) => console.log(out)) -proc.connect("stderr", (err) => console.error(err)) +proc.connect("stdout", (_, out) => console.log(out)) +proc.connect("stderr", (_, err) => console.error(err)) ``` ### Executing external commands and scripts diff --git a/docs/guide/typescript/variable.md b/docs/guide/typescript/variable.md index 2abacbd..e6f3434 100644 --- a/docs/guide/typescript/variable.md +++ b/docs/guide/typescript/variable.md @@ -35,7 +35,7 @@ Widget.Label({ ``` :::warning -Make sure to make the transform functions passed to `.as()` are pure. +Make sure to the transform functions you pass to `:as()` are pure. The `.get()` function can be called anytime by `astal` especially when `deriving`, so make sure there are no sideeffects. ::: @@ -126,7 +126,7 @@ const myvar = Variable("") ## Dispose if no longer needed -This will stop the interval and force exit the subprocess and disconnect gobjects. +This will stop the interval, force exit the subprocess and disconnect gobjects. ```js myVar.drop() diff --git a/docs/vitepress.config.ts b/docs/vitepress.config.ts index 2880477..2df3eea 100644 --- a/docs/vitepress.config.ts +++ b/docs/vitepress.config.ts @@ -61,7 +61,7 @@ export default defineConfig({ { text: "TypeScript", base: "/guide/typescript", - collapsed: false, + collapsed: true, items: [ { text: "Installation", link: "/installation" }, { text: "First Widgets", link: "/first-widgets" }, @@ -78,10 +78,18 @@ export default defineConfig({ { text: "Lua", base: "/guide/lua", - collapsed: false, + collapsed: true, items: [ { text: "Installation", link: "/installation" }, { text: "First Widgets", link: "/first-widgets" }, + { text: "Theming", link: "/theming" }, + { text: "CLI and App", link: "/cli-app" }, + { text: "Widget", link: "/widget" }, + { text: "Variable", link: "/variable" }, + { text: "Binding", link: "/binding" }, + // { text: "GObject", link: "/gobject" }, + { text: "Utilities", link: "/utilities" }, + // { text: "FAQ", link: "/faq" }, ], }, { |