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