1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
# Binding
As mentioned before binding an object's state to another -
so in most cases a `Variable` or a `GObject.Object` property to a widget's property -
is done through the `bind` function which returns a `Binding` object.
`Binding` objects simply hold information about the source and how it should be transformed
which Widget constructors can use to setup a connection between themselves and the source.
```lua
---@class Binding<T>
---@field private transform_fn fun(value: T): any
---@field private emitter Connectable | Subscribable<T>
---@field private property? string
---@field as fun(transform: fun(value: T): any): Binding
---@field get fun(): T
---@field subscribe fun(self, callback: fun(value: T)): function
```
A `Binding` can be constructed from an object implementing
the `Subscribable` interface (usually a `Variable`)
or an object implementing the `Connectable` interface and one of its properties
(usually a `GObject.Object` instance).
Lua type annotations are not expressive enough to explain this,
so I'll use TypeScript to demonstrate it.
<!--TODO: use Teal maybe?-->
```ts
function bind<T>(obj: Subscribable<T>): Binding<T>
function bind<
Obj extends Connectable,
Prop extends keyof Obj,
>(obj: Obj, prop: Prop): Binding<Obj[Prop]>
```
## Subscribable and Connectable interface
Any object implementing one of these interfaces can be used with `bind`.
```ts
interface Subscribable<T> {
subscribe(callback: (value: T) => void): () => void
get(): T
}
interface Connectable {
connect(signal: string, callback: (...args: any[]) => unknown): number
disconnect(id: number): void
}
```
`Connectable` is usually used for GObjects coming from [libraries](../libraries/references)
You won't be implementing it in Lua code.
## Example Custom Subscribable
When binding the children of a box from an array, usually not all elements
of the array changes each time, so it would make sense to not destroy
the widget which represents the element.
::: code-group
```lua :line-numbers [varmap.lua]
local Gtk = require("astal.gtk3").Gtk
local Variable = require("astal.variable")
---@param initial table
return function(initial)
local map = initial
local var = Variable()
local function notify()
local arr
for _, value in pairs(map) do
table.insert(arr, value)
end
var:set(arr)
end
local function delete(key)
if Gtk.Widget:is_type_of(map[key]) then
map[key]:destroy()
end
map[key] = nil
end
notify() -- init
return {
set = function(key, value)
delete(key)
map[key] = value
notify()
end,
delete = function(key)
delete(key)
notify()
end,
get = function()
return var:get()
end,
subscribe = function(callback)
return var:subscribe(callback)
end,
}
end
```
:::
And this `VarMap<key, Widget>` can be used as an alternative to `Variable<Array<Widget>>`.
```lua
function MappedBox()
local map = varmap({
["1"] = Widget.Label({ label = "1" }),
["2"] = Widget.Label({ label = "2" }),
})
return Widget.Box({
setup = function (self)
self:hook(gobject, "added", function (_, id)
map.set(id, Widget.Label({ label = id }))
end)
self:hook(gobject, "removed", function (_, id)
map.delete(id)
end)
end,
bind(map):as(function (arr)
-- can be sorted here
return arr
end),
})
end
```
|