-
-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathlayer.lua
More file actions
359 lines (291 loc) · 9.64 KB
/
Copy pathlayer.lua
File metadata and controls
359 lines (291 loc) · 9.64 KB
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
local log = require("snowcap.log")
local client = require("snowcap.grpc.client").client
local widget = require("snowcap.widget")
local widget_signal = require("snowcap.widget.signal")
---@class snowcap.layer
local layer = {}
local layer_handle = {}
---@class snowcap.layer.LayerHandle
---@field id integer
---@field private _update fun(msg:any)
local LayerHandle = {}
---Convert a LayerHandle into a Popup's ParentHandle
---@return snowcap.popup.ParentHandle
function LayerHandle:as_parent()
return require("snowcap.popup").parent.Layer(self)
end
---@param id integer
---@param update fun(msg: any?)
---@return snowcap.layer.LayerHandle
function layer_handle.new(id, update)
---@type snowcap.layer.LayerHandle
local self = {
id = id,
_update = update,
}
setmetatable(self, { __index = LayerHandle })
return self
end
---@enum snowcap.layer.Anchor
local anchor = {
TOP = 1,
BOTTOM = 2,
LEFT = 3,
RIGHT = 4,
TOP_LEFT = 5,
TOP_RIGHT = 6,
BOTTOM_LEFT = 7,
BOTTOM_RIGHT = 8,
}
---@enum snowcap.layer.KeyboardInteractivity
local keyboard_interactivity = {
NONE = 1,
ON_DEMAND = 2,
EXCLUSIVE = 3,
}
---@enum snowcap.layer.ZLayer
local zlayer = {
BACKGROUND = 1,
BOTTOM = 2,
TOP = 3,
OVERLAY = 4,
}
---@package
---@enum snowcap.layer.FocusEvent
local focus_event = {
GAINED = 1,
LOST = 2,
}
---@alias snowcap.layer.ExclusiveZone
---| integer
---| "respect"
---| "ignore"
---@param zone snowcap.layer.ExclusiveZone
---@return integer
local function exclusive_zone_to_api(zone)
if type(zone) == "number" then
return zone
end
if zone == "respect" then
return 0
end
return -1
end
---@class snowcap.layer.LayerArgs
---@field program snowcap.widget.Program
---@field anchor snowcap.layer.Anchor?
---@field keyboard_interactivity snowcap.layer.KeyboardInteractivity
---@field exclusive_zone snowcap.layer.ExclusiveZone
---@field layer snowcap.layer.ZLayer
---@param args snowcap.layer.LayerArgs
---@return snowcap.layer.LayerHandle|nil handle A handle to the layer surface, or nil if an error occurred.
function layer.new_widget(args)
---@type table<integer, any>
local callbacks = {}
local widget_def = args.program:view() or widget.row({ children = {} })
widget._traverse_widget_tree(widget_def, callbacks, widget._collect_callbacks)
---@type snowcap.layer.v1.NewLayerRequest
local request = {
layer = args.layer,
exclusive_zone = exclusive_zone_to_api(args.exclusive_zone),
anchor = args.anchor,
keyboard_interactivity = args.keyboard_interactivity,
widget_def = widget.widget_def_into_api(widget_def),
}
local response, err = client:snowcap_layer_v1_LayerService_NewLayer(request)
if err then
log.error(err)
return nil
end
assert(response)
if not response.layer_id then
log.error("no layer_id received")
return nil
end
local layer_id = response.layer_id or 0
---@type fun(msg: any?)
local update_on_msg = function(msg)
if msg ~= nil then
args.program:update(msg)
end
---@diagnostic disable-next-line: redefined-local
local _, err = client:snowcap_layer_v1_LayerService_RequestView({
layer_id = layer_id,
})
if err then
log.error(err)
end
end
local handle = layer_handle.new(layer_id, update_on_msg)
---@type fun(oper: snowcap.widget.operation.Operation)
local forward_operation = function(oper)
handle:operate(oper)
end
---@type fun(): snowcap.signal.HandlerPolicy
local close_surface = function()
handle:close()
return require("snowcap.signal").HandlerPolicy.Discard
end
args.program:connect(widget_signal.redraw_needed, update_on_msg)
args.program:connect(widget_signal.send_message, update_on_msg)
args.program:connect(widget_signal.operation, forward_operation)
args.program:connect(widget_signal.request_close, close_surface)
args.program:event({
created = widget.SurfaceHandle.from_layer_handle(handle),
})
err = client:snowcap_layer_v1_LayerService_GetLayerEvents({
layer_id = layer_id,
}, function(response) ---@diagnostic disable-line:redefined-local
response.layer_events = response.layer_events or {}
for _, layer_event in ipairs(response.layer_events) do
if layer_event.closing ~= nil then
return true
end
local focus = layer_event.focus --[[@as snowcap.layer.FocusEvent]]
---@type snowcap.widget.SurfaceEvent?
local event = nil
if focus == focus_event.GAINED then
event = {
focus_gained = {},
}
elseif focus == focus_event.LOST then
event = {
focus_lost = {},
}
end
if event then
args.program:event(event)
end
end
---@diagnostic disable-next-line: redefined-local
local _, err = client:snowcap_layer_v1_LayerService_RequestView({
layer_id = layer_id,
})
if err then
log.error(err)
end
end, function()
args.program:event({
closing = {},
})
end)
err = client:snowcap_widget_v1_WidgetService_GetWidgetEvents({
layer_id = layer_id,
}, function(response) ---@diagnostic disable-line:redefined-local
for _, event in ipairs(response.widget_events) do
---@diagnostic disable-next-line:invisible
local msg = widget._message_from_event(callbacks, event)
if msg then
local ok, update_err = pcall(function()
args.program:update(msg)
end)
if not ok then
log.error(update_err)
end
end
end
---@diagnostic disable-next-line:redefined-local
local widget_def = args.program:view() or widget.row({ children = {} })
callbacks = {}
widget._traverse_widget_tree(widget_def, callbacks, widget._collect_callbacks)
---@diagnostic disable-next-line:redefined-local
local _, err = client:snowcap_layer_v1_LayerService_UpdateLayer({
layer_id = layer_id,
widget_def = widget.widget_def_into_api(widget_def),
})
if err then
log.error(err)
end
end, function()
args.program:event({
closing = {},
})
end)
return handle
end
---Do something when a key event is received.
---@param on_event fun(handle: snowcap.layer.LayerHandle, event: snowcap.input.KeyEvent)
function LayerHandle:on_key_event(on_event)
local err = client:snowcap_input_v1_InputService_KeyboardKey(
{ layer_id = self.id },
function(response)
---@cast response snowcap.input.v1.KeyboardKeyResponse
local mods = response.modifiers or {}
mods.shift = mods.shift or false
mods.ctrl = mods.ctrl or false
mods.alt = mods.alt or false
mods.super = mods.super or false
---@cast mods snowcap.input.Modifiers
---@type snowcap.input.KeyEvent
local event = {
key = response.key or 0,
mods = mods,
pressed = response.pressed,
captured = response.captured,
text = response.text,
}
on_event(self, event)
end
)
if err then
log.error(err)
end
end
---@param on_press fun(mods: snowcap.input.Modifiers, key: snowcap.Key)
function LayerHandle:on_key_press(on_press)
self:on_key_event(function(_, event)
if not event.pressed or event.captured then
return
end
on_press(event.mods, event.key)
end)
end
---@class snowcap.layer.LayerUpdateArgs
---@field anchor? snowcap.layer.Anchor
---@field keyboard_interactivity? snowcap.layer.KeyboardInteractivity
---@field exclusive_zone? snowcap.layer.ExclusiveZone
---@field layer? snowcap.layer.ZLayer
---Update this layer's attributes.
---@param args snowcap.layer.LayerUpdateArgs
---@return boolean True if the operation succeed.
function LayerHandle:update(args)
local exclusive_zone = args.exclusive_zone and exclusive_zone_to_api(args.exclusive_zone) or nil
local _, err = client:snowcap_layer_v1_LayerService_UpdateLayer({
layer_id = self.id,
anchor = args.anchor,
keyboard_interactivity = args.keyboard_interactivity,
exclusive_zone = exclusive_zone,
layer = args.layer,
})
if err then
log.error(err)
end
return err == nil
end
function LayerHandle:close()
local _, err = client:snowcap_layer_v1_LayerService_Close({ layer_id = self.id })
if err then
log.error(err)
end
end
function LayerHandle:send_message(message)
self._update(message)
end
---Sends an `Operation` to this layer.
---@param operation snowcap.widget.operation.Operation
function LayerHandle:operate(operation)
local _, err = client:snowcap_layer_v1_LayerService_OperateLayer({
layer_id = self.id,
operation = require("snowcap.widget.operation")._to_api(operation), ---@diagnostic disable-line: invisible
})
if err then
log.error(err)
end
end
layer.anchor = anchor
layer.keyboard_interactivity = keyboard_interactivity
layer.zlayer = zlayer
return layer