summaryrefslogtreecommitdiff
path: root/docs/guide/lua/first-widgets.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/guide/lua/first-widgets.md')
-rw-r--r--docs/guide/lua/first-widgets.md265
1 files changed, 265 insertions, 0 deletions
diff --git a/docs/guide/lua/first-widgets.md b/docs/guide/lua/first-widgets.md
new file mode 100644
index 0000000..efc1c4f
--- /dev/null
+++ b/docs/guide/lua/first-widgets.md
@@ -0,0 +1,265 @@
+# First Widgets
+
+## 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.
+:::