Skip to content

First Widgets ​

Getting Started ​

Start by importing the singleton Astal.Application instance.

lua
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.

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
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.

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.

lua
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 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:

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:

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.

Released under the LGPL v2.1 License