First Widgets
Getting Started
Start by importing the singleton Astal.Application instance.
local App = require("astal.gtk3.app")
App:start({
main = function()
-- you will instantiate Widgets here
-- and setup anything else if you need
end
})
Then run lua init.lua
in the terminal, and that's it! Now you have an Astal instance running written in 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 which will hold all widgets.
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
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.
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.
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:
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 lets you connect the state of an object 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:
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)
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:
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.
local astal = require("astal")
local Variable = astal.Variable
local Widget = require("astal.gtk3.widget")
local child = Variable(Widget.Box())
return Widget.Box({
child(),
})
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.