summaryrefslogtreecommitdiffstats
path: root/lua-bindings
diff options
context:
space:
mode:
Diffstat (limited to 'lua-bindings')
-rw-r--r--lua-bindings/diaphragm.lua404
1 files changed, 404 insertions, 0 deletions
diff --git a/lua-bindings/diaphragm.lua b/lua-bindings/diaphragm.lua
new file mode 100644
index 0000000..ffb3e4b
--- /dev/null
+++ b/lua-bindings/diaphragm.lua
@@ -0,0 +1,404 @@
1local M = {}
2
3M.core = require("libdiaphragm")
4
5M.util = {}
6
7function M.util.eprint(s, ...)
8 io.stderr:write(string.format(s, ...))
9 io.stderr:write("\n")
10end
11
12function M.util.list_concat(...)
13 local result = {}
14 for i = 1, select("#", ...) do
15 local current = select(i, ...)
16 if current ~= nil then
17 for _, v in ipairs(current) do
18 table.insert(result, v)
19 end
20 end
21 end
22 return result
23end
24
25function M.util.list_intersperse_with(func, list)
26 local result = {}
27
28 for i = 1, #list - 1 do
29 table.insert(result, list[i])
30 table.insert(result, func())
31 end
32 table.insert(result, list[#list])
33
34 return result
35end
36
37function M.util.tbl_map(func, t)
38 local result = {}
39 for k, v in pairs(t) do
40 result[k] = func(v)
41 end
42 return result
43end
44
45function M.util.tbl_print(t)
46 io.stderr:write("{ ")
47 for k, v in pairs(t) do
48 io.stderr:write(string.format("%s = %s, ", k, v))
49 end
50 io.stderr:write("}\n")
51end
52
53function M.util.tbl_extend(...)
54 local result = {}
55 for i = 1, select("#", ...) do
56 local current = select(i, ...)
57 if current ~= nil then
58 for k, v in pairs(current) do
59 result[k] = v
60 end
61 end
62 end
63 return result
64end
65
66function M.util.tbl_contains(t, value)
67 for _, v in pairs(t) do
68 if v == value then
69 return true
70 end
71 end
72 return false
73end
74
75-- TODO: depending on objects
76function M.util.is_reserved(s)
77 return M.util.tbl_contains({
78 "top",
79 "left",
80 "width",
81 "height",
82 "right",
83 "bottom",
84 "vert_center",
85 "horiz_center",
86
87 "top_left",
88 "top_right",
89 "bottom_left",
90 "bottom_right",
91 "middle_left",
92 "middle_right",
93 "top_middle",
94 "bottom_middle",
95 "center",
96
97 "stroke_color",
98 "stroke_width",
99 "fill_color",
100
101 -- "x",
102 -- "y",
103 --
104 "size",
105 }, s)
106end
107
108function M.util.tbl_filter_by_key(func, t)
109 local result = {}
110 for k, v in pairs(t) do
111 if func(k) then
112 result[k] = v
113 end
114 end
115 return result
116end
117
118function M.util.tbl_filter_reserved(t)
119 return M.util.tbl_filter_by_key(function(k)
120 return not M.util.is_reserved(k)
121 end, t)
122end
123
124function M.util.tbl_assign_reserved(t, rhs)
125 for k, v in pairs(t) do
126 if M.util.is_reserved(k) then
127 rhs[k] = v
128 end
129 end
130end
131
132-- Return lhs, but with keys that are not in rhs
133function M.util.tbl_diff(lhs, rhs)
134 local result = {}
135 for k, v in pairs(lhs) do
136 if rhs[k] == nil then
137 result[k] = v
138 end
139 end
140 return result
141end
142
143-- Assign keys from lhs to rhs, useful if rhs is userdata
144function M.util.tbl_assign(lhs, rhs)
145 for k, v in pairs(lhs) do
146 rhs[k] = v
147 end
148end
149
150M.float = {}
151
152M.float.new = M.core.float
153M.float.max = M.core.float_max
154M.float.min = M.core.float_min
155
156M.text = {}
157M.text.font = M.core.font
158
159function M.text.new(params)
160 params = params or {}
161 local result = M.core.text(params)
162 M.util.tbl_assign_reserved(params, result)
163 return result
164end
165
166M.rectangle = {}
167
168function M.rectangle.new(params)
169 params = params or {}
170 local result = M.core.rectangle()
171 M.util.tbl_assign_reserved(params, result)
172 return result
173end
174
175function M.rectangle.surrounding(content, params)
176 params = params or {}
177 local margin = params.margin or 0
178 local margin_left = params.margin_left or margin
179 local margin_right = params.margin_right or margin
180 local margin_top = params.margin_top or margin
181 local margin_bottom = params.margin_bottom or margin
182
183 return M.rectangle.new(M.util.tbl_extend({
184 left = content.left - margin_left,
185 right = content.right + margin_right,
186 top = content.top - margin_top,
187 bottom = content.bottom + margin_bottom,
188 }, params))
189end
190
191M.straight_path = {}
192
193function M.straight_path.new(params)
194 params = params or {}
195 local result = M.core.straight_path(params)
196 M.util.tbl_assign_reserved(params, result)
197 return result
198end
199
200function M.shape(params)
201 params = params or {}
202 local result = M.util.tbl_filter_reserved(params)
203
204 local shape = M.core.complex_shape()
205 setmetatable(result, {
206 __index = function(_, k)
207 if M.util.is_reserved(k) then
208 return shape[k]
209 end
210 end,
211 })
212 M.util.tbl_assign_reserved(params, shape)
213
214 return result
215end
216
217M.layout = {}
218
219function M.layout.margin(params)
220 local result = M.shape(params)
221
222 result.margin_left = result.margin_left or M.float.new()
223 result.margin_right = result.margin_right or M.float.new()
224 result.margin_top = result.margin_top or M.float.new()
225 result.margin_bottom = result.margin_bottom or M.float.new()
226
227 function result:draw()
228 M.constrain(self.left:eq(self.content.left - self.margin_left))
229 M.constrain(self.right:eq(self.content.right + self.margin_right))
230 M.constrain(self.top:eq(self.content.top - self.margin_top))
231 M.constrain(self.bottom:eq(self.content.bottom + self.margin_bottom))
232
233 self.content:draw()
234 end
235
236 return result
237end
238
239function M.layout.margin_left(params)
240 return M.layout.margin(M.util.tbl_extend(params, {
241 margin_right = 0,
242 margin_top = 0,
243 margin_bottom = 0,
244 }))
245end
246
247function M.layout.margin_right(params)
248 return M.layout.margin(M.util.tbl_extend(params, {
249 margin_left = 0,
250 margin_top = 0,
251 margin_bottom = 0,
252 }))
253end
254
255function M.layout.margin_top(params)
256 return M.layout.margin(M.util.tbl_extend(params, {
257 margin_left = 0,
258 margin_right = 0,
259 margin_bottom = 0,
260 }))
261end
262
263function M.layout.margin_bottom(params)
264 return M.layout.margin(M.util.tbl_extend(params, {
265 margin_left = 0,
266 margin_right = 0,
267 margin_top = 0,
268 }))
269end
270
271-- TODO: factor with vstack
272-- TODO: also as just a set of constraints
273function M.layout.hstack(params)
274 local result = M.shape(params)
275
276 result.spacing = result.spacing or 0
277 result.align = result.align or "top"
278 if result.align == "middle" or result.align == "center" then
279 result.align = "vert_center"
280 end
281
282 function result:draw()
283 local len = #self.elements
284 assert(len >= 1, "hstack must have at least 1 element in `elements`")
285
286 local tops = {}
287 local bottoms = {}
288
289 local previous_right = self.left - self.spacing
290 local previous_vert_anchor = self[self.align]
291
292 for _, el in pairs(self.elements) do
293 local el_vert_anchor = el[self.align]
294
295 M.constrain(el.left:eq(previous_right + self.spacing))
296 M.constrain(el_vert_anchor:eq(previous_vert_anchor))
297
298 table.insert(tops, el.top)
299 table.insert(bottoms, el.bottom)
300
301 el:draw()
302
303 previous_right = el.right
304 previous_vert_anchor = el_vert_anchor
305 end
306
307 M.constrain(self.top:eq(M.float.min(tops)))
308 M.constrain(self.bottom:eq(M.float.max(bottoms)))
309 M.constrain(self.right:eq(previous_right))
310 end
311
312 return result
313end
314
315function M.layout.vstack(params)
316 local result = M.shape(params)
317
318 result.spacing = result.spacing or 0
319 result.align = result.align or "left"
320 if result.align == "middle" or result.align == "center" then
321 result.align = "horiz_center"
322 end
323
324 function result:draw()
325 local len = #self.elements
326 assert(len >= 1, "vstack must have at least 1 element in `elements`")
327
328 local lefts = {}
329 local rights = {}
330
331 local previous_bottom = self.top - self.spacing
332 local previous_horiz_anchor = self[self.align]
333
334 for _, el in pairs(self.elements) do
335 local el_horiz_anchor = el[self.align]
336
337 M.constrain(el.top:eq(previous_bottom + self.spacing))
338 M.constrain(el_horiz_anchor:eq(previous_horiz_anchor))
339
340 table.insert(lefts, el.left)
341 table.insert(rights, el.right)
342
343 el:draw()
344
345 previous_bottom = el.bottom
346 previous_horiz_anchor = el_horiz_anchor
347 end
348
349 M.constrain(self.left:eq(M.float.min(lefts)))
350 M.constrain(self.right:eq(M.float.max(rights)))
351 M.constrain(self.bottom:eq(previous_bottom))
352 end
353
354 return result
355end
356
357M.constraint = {}
358
359function M.constraint.left_of(lhs, rhs, margin)
360 M.constrain(lhs.right:eq(rhs.left - (margin or 0)))
361end
362
363function M.constraint.right_of(rhs, lhs, margin)
364 M.constrain(lhs.right:eq(rhs.left - (margin or 0)))
365end
366
367function M.constraint.above(above, below, margin)
368 M.constrain(above.bottom:eq(below.top - (margin or 0)))
369end
370
371function M.constraint.below(below, above, margin)
372 M.constrain(above.bottom:eq(below.top - (margin or 0)))
373end
374
375function M.constraint.same_size(elems)
376 local height = M.float.new()
377 local width = M.float.new()
378
379 for _, el in pairs(elems) do
380 M.constrain(el.height:eq(height))
381 M.constrain(el.width:eq(width))
382 end
383end
384
385function M.constraint.same_height(elems)
386 local height = M.float.new()
387
388 for _, el in pairs(elems) do
389 M.constrain(el.height:eq(height))
390 end
391end
392
393function M.constraint.same_width(elems)
394 local width = M.float.new()
395
396 for _, el in pairs(elems) do
397 M.constrain(el.width:eq(width))
398 end
399end
400
401M.constrain = M.core.constrain
402M.draw = M.core.draw
403
404return M