Skip to content

Reactivity

In order for widgets to have dynamic content we pass Bindings as properties or setup a hook. A Binding is just an object that holds information for widget constructors to setup a listener.

Property Bindings

We can make a Binding from a Variable

const percent = Variable(0.5)
const slider = Widget.Slider({
value: percent.bind(),
onChange: ({ value }) => percent.value = value,
})

From a Service

const { speaker } = await Service.import("audio")
const slider = Widget.Slider({
value: speaker.bind("volume"),
onChange: ({ value }) => speaker.volume = value,
})

Merge any number of Bindings into another Binding

const a = Variable(0.3)
const b = Variable(0.7)
const merged = Utils.merge([a.bind(), b.bind()], (a, b) => {
return a * b
})
const level = Widget.LevelBar({
value: merged
})

Turn one or multiple Service signals into a Binding

const mpris = await Service.import("mpris")
const label = Widget.Label({
label: Utils.watch("initial-label", mpris, "player-added", (busName) => {
return `player ${busName} was added`
})
})
const label = Widget.Label({
label: Utils.watch("initial-label", [
[mpris, "player-added"],
[mpris, "player-removed"],
], (busName) => {
return `player ${busName} was added or removed`
})
})

A Binding can be transformed according to need

const num = Variable(0)
const label = Widget.Label({
// will throw an error, because number is not assignable to string
label: num.bind(),
// will have to be transformed
label: num.bind().as(n => n.toString()),
label: num.bind().as(String)
})

Hooks

You can call these on any Widget that you have a reference on. They will return this reference, meaning you can chain them up in any order in any number.

const widget = Widget()
widget.hook()
widget.on()
widget.poll()
widget.keybind()
const widget = Widget()
.hook()
.on()
const widget = Widget({
setup: self => {
self.hook()
self.on()
}
})
const widget = Widget({
setup: self => self
.hook()
.on()
})

Hook

hook will setup a listener to a GObject and will handle disconnection when the widget is destroyed. It will connect to the changed signal by default when not specified otherwise.

const battery = await Service.import("battery")
// .hook(GObject, callback, signal?)
const BatteryPercent = () => Label()
.hook(battery, self => {
self.label = `${battery.percent}%`
self.visible = battery.available
}, "changed")

On

on is the same as connect but instead of the signal handler id, it returns a reference to the widget. on will setup a callback on a widget signal.

These two are equivalent

function MyButton() {
const self = Widget.Button()
self.connect("clicked", () => {
print(self, "is clicked")
})
return self
}
const MyButton = () => Widget.Button()
.on("clicked", self => {
print(self, "is clicked")
})

Poll

Avoid using this as much as possible, using this is considered bad practice.

These two are equivalent

function MyLabel() {
const self = Widget.Label()
Utils.interval(1000, () => {
self.label = Utils.exec('date')
}, self)
return self
}
const MyLabel = () => Widget.Label()
.poll(1000, self => {
self.label = Utils.exec('date')
})

Keybind

It is possible to setup keybindings on focused widgets

// usually this way
Widget.Button().on("key-press-event", (self, event) => {
// check event for keys
})
// with .keybind()
Widget.Button().keybind(["MOD1", "CONTROL"], "a", (self, event) => {
print("alt+control+a was pressed")
})