· 6 years ago · Sep 24, 2019, 09:12 AM
1-------------------------------------------------------------------------------------------
2---Custon drawing API
3-------------------------------------------------------------------------------------------
4local function drawPixelInternal( target, xPos, yPos )
5 target.setCursorPos(xPos, yPos)
6 target.write(" ")
7end
8
9local function drawPixel(target, xPos, yPos, nColour )
10 if nColour then
11 target.setBackgroundColor( nColour )
12 end
13 drawPixelInternal( target, xPos, yPos )
14end
15
16local function drawLine(target, startX, startY, endX, endY, nColour )
17 if nColour then
18 target.setBackgroundColor( nColour )
19 end
20
21 startX = math.floor(startX)
22 startY = math.floor(startY)
23 endX = math.floor(endX)
24 endY = math.floor(endY)
25
26 if startX == endX and startY == endY then
27 drawPixelInternal( target, startX, startY )
28 return
29 end
30
31 local minX = math.min( startX, endX )
32 if minX == startX then
33 minY = startY
34 maxX = endX
35 maxY = endY
36 else
37 minY = endY
38 maxX = startX
39 maxY = startY
40 end
41
42 local xDiff = maxX - minX
43 local yDiff = maxY - minY
44
45 if xDiff > math.abs(yDiff) then
46 local y = minY
47 local dy = yDiff / xDiff
48 for x=minX,maxX do
49 drawPixelInternal( target, x, math.floor( y + 0.5 ) )
50 y = y + dy
51 end
52 else
53 local x = minX
54 local dx = xDiff / yDiff
55 if maxY >= minY then
56 for y=minY,maxY do
57 drawPixelInternal( target, math.floor( x + 0.5 ), y )
58 x = x + dx
59 end
60 else
61 for y=minY,maxY,-1 do
62 drawPixelInternal( target, math.floor( x + 0.5 ), y )
63 x = x - dx
64 end
65 end
66 end
67end
68
69local function drawRect(target, x1, y1, x2, y2, nColour)
70 for i = 0, y2 - 1 do
71 drawLine(target, x1, y1 + i, x1 + x2 - 1, y1 + i, nColour)
72 end
73end
74-------------------------------------------------------------------------------------------
75---End of custom drawing API
76-------------------------------------------------------------------------------------------
77local peripheralPool = {}
78local SELECTED_OBJECT = nil
79local backupPullEvent = nil
80-------------------------------------------------------------------------------------------
81---PUBLIC GUI API
82-------------------------------------------------------------------------------------------
83function SET_SELECTED_OBJECT(OBJ)
84 SELECTED_OBJECT = OBJ
85end
86
87function CLEAR_SCREEN(color, target)
88 if not target then target = term.native() end
89 target.setBackgroundColor(color)
90 target.clear()
91end
92
93function NO_EXIT()
94 os.pullEvent("GUI_USER_EXIT")
95 cleanup(MainPanel)
96end
97
98function EXIT()
99 os.queueEvent("GUI_USER_EXIT")
100end
101
102local function cleanup(object)
103 for _, child in pairs(object._CHILDREN) do
104 cleanup(child)
105 child = nil
106 end
107end
108
109local function getPeripheralName(p)
110 return peripheralPool[p]
111end
112
113function connectPeripheral(name)
114 local p = peripheral.wrap(name)
115 peripheralPool[p] = name
116 return p
117end
118
119local function getTextLayoutPos(layout, text, x, y, width, height, px, py)
120 if layout == "topleft" then
121 return x, y
122 elseif layout == "top" then
123 local cx = x + math.floor( ( width - string.len(text) ) / 2)
124 return cx, y
125 elseif layout == "topright" then
126 local cx = x + ( width - string.len(text) ) + 1
127 return cx, y
128 elseif layout == "center" then
129 local cy = y + math.floor(height / 2)
130 local cx = x + math.floor( ( width - string.len(text) ) / 2)
131 return cx, cy
132 elseif layout == "left" then
133 local cy = y + math.floor(height / 2)
134 return x, cy
135 elseif layout == "right" then
136 local cx = x +( width - string.len(text) ) + 1
137 local cy = y + math.floor(height / 2)
138 return cx, cy
139 elseif layout == "bottomleft" then
140 local cy = y + math.floor(height / 2) + 1
141 return x, cy
142 elseif layout == "bottom" then
143 local cx = x + math.floor( ( width - string.len(text) ) / 2)
144 local cy = y + math.floor(height / 2) + 1
145 return cx, cy
146 elseif layout == "bottomright" then
147 local cx = x +( width - string.len(text) ) + 1
148 local cy = y + math.floor(height / 2) + 1
149 return cx, cy
150 end
151end
152
153local function inBounds(x, y, x1, y1, w, h)
154 if ( ( x >= x1 and x <= ( x1 + w) ) and (y >= y1 and y <= ( y1 + h ) ) ) then
155 return true
156 end
157 return false
158end
159
160--Defaults for all objects
161local function getDefaults()
162 local mt = {
163 target = term.native(),
164 x = 1,
165 y = 1,
166 dynX = 1,
167 dynY = 1,
168 width = 1,
169 height = 1,
170 enabled = true,
171 visible = true,
172 text = "",
173 func = nil,
174 text_pos = "center",
175 color_text = colors.white,
176 color_bg = colors.blue,
177 color_used = colors.red,
178 _PARENT = nil,
179 _CHILDREN = {},
180
181 addPARENT = function(s, object)
182 s._PARENT = object
183 end,
184
185 addCHILD = function(s, object) end,
186
187 enable = function(s)
188 s.enabled = true
189 end,
190
191 disable = function(s)
192 s.enabled = false
193 end,
194
195 show = function(s)
196 s.visible = true
197 end,
198
199 showNDraw = function(s)
200 s:show()
201 s:draw()
202 end,
203
204 showNDrawNEnable = function(s)
205 s:showNDraw()
206 s:enable()
207 end,
208
209 hide = function(s)
210 s.visible = false
211 end,
212
213 hideNDisable = function(s)
214 s:hide()
215 s:disable()
216 end,
217
218 showNEnable = function(s)
219 s:showNDraw()
220 s:enable()
221 end,
222
223 setText = function(s, t)
224 s.text = t
225 s:draw()
226 end,
227
228 move = function(s, x, y)
229 s.x = x
230 s.y = y
231 s:_dynRefresh()
232 end,
233
234 resize = function(s, w, h)
235 s.width = w
236 s.height = h
237 end,
238
239 _dynRefresh = function(s)
240 px, py = 1, 1
241 if s._PARENT then
242 px, py = s._PARENT.dynX, s._PARENT.dynY
243 end
244 s.dynX = s.x + px - 1
245 s.dynY = s.y + py - 1
246 end,
247
248 clickCheck = function(s) return false end,
249 draw = function(s) end,
250 eventReact = function(s, e) end,
251
252 used = function(s)
253 if s.func then s:func() end
254 end
255
256 }
257 return {__index = mt}
258end
259
260--Panel constructor
261function NewPanel(x, y, visible, enabled)
262 local panel = {
263 addCHILD = function(s, ...)
264 local args = {...}
265 for _, object in pairs(args) do
266 table.insert(s._CHILDREN, object)
267 object:addPARENT(s)
268 end
269 end,
270
271 draw = function(s)
272 if not s.visible then return end
273 s:_dynRefresh()
274 for _, child in pairs(s._CHILDREN) do
275 child:draw()
276 end
277 end
278 }
279 panel = setmetatable(panel, getDefaults())
280
281 panel.x = x
282 panel.y = y
283 panel.visible = visible
284 panel.enabled = enabled
285
286 return panel
287end
288
289--Button constructor
290function NewButton(target, x, y, width, height, text, func, color_bg, color_text, color_used)
291 local button = {
292 draw = function(s, color)
293 if not s.visible then return end
294 if not color then color = s.color_bg end
295 local cursorX, cursorY = s.target.getCursorPos()
296
297 s:_dynRefresh()
298
299 drawRect(s.target, s.dynX, s.dynY, s.width, s.height, color)
300
301 local cx, cy = getTextLayoutPos(s.text_pos, s.text, s.dynX, s.dynY, s.width, s.height)
302 s.target.setTextColor(s.color_text)
303 s.target.setCursorPos(cx, cy)
304 s.target.write(s.text)
305
306 s.target.setCursorPos(cursorX, cursorY)
307 end,
308
309 clickCheck = function(s, t)
310 if not s.enabled then return end
311
312 if t[1] == "monitor_touch" and t[2] ~= getPeripheralName(s.target)
313 or s.target ~= term.native() and t[1] == "mouse_click" then
314 return
315 end
316
317 s:_dynRefresh()
318
319 if inBounds(t[3], t[4], s.dynX, s.dynY, s.width - 1, s.height - 1) then
320 s:used()
321 return true
322 end
323 return false
324 end,
325 }
326 button = setmetatable(button, getDefaults())
327
328 button.target = target
329 button.x = x
330 button.y = y
331 button.width = width
332 button.height = height
333 button.text = text
334 button.func = func
335 button.color_text = color_text
336 button.color_bg = color_bg
337 button.color_used = color_used
338
339 return button
340end
341
342--CheckBox constructor
343function NewCheckBox(target, x, y, width, height, text, func, color_bg, color_text, color_used)
344 local chbox = {
345 check = false,
346
347 draw = function(s, color)
348 if not s.visible then return end
349 if not color then color = s.color_bg end
350 local cursorX, cursorY = s.target.getCursorPos()
351
352 s:_dynRefresh()
353
354 drawRect(s.target, s.dynX, s.dynY, s.width, s.height, color)
355
356 local cx, cy = getTextLayoutPos(s.text_pos, "[ ]-" .. s.text, s.dynX, s.dynY, s.width, s.height)
357 s.target.setTextColor(s.color_text)
358 s.target.setCursorPos(cx, cy)
359 s.target.write("["..( (s.check == true) and "X" or " ") .. "]-"..s.text)
360
361 s.target.setCursorPos(cursorX, cursorY)
362 end,
363
364 clickCheck = function(s, t)
365 if not s.enabled then return end
366
367 if t[1] == "monitor_touch" and t[2] ~= getPeripheralName(s.target)
368 or s.target ~= term.native() and t[1] == "mouse_click" then
369 return
370 end
371
372 s:_dynRefresh()
373
374 if inBounds(t[3], t[4], s.dynX, s.dynY, s.width - 1, s.height - 1) then
375 s:used()
376 return true
377 end
378 return false
379 end,
380
381 used = function(s)
382 s.check = not s.check
383 if s.func then s:func() end
384 s:draw()
385 end,
386 }
387 chbox = setmetatable(chbox, getDefaults())
388
389 chbox.target = target
390 chbox.x = x
391 chbox.y = y
392 chbox.width = width
393 chbox.height = height
394 chbox.text = text
395 chbox.func = func
396 chbox.color_text = color_text
397 chbox.color_bg = color_bg
398 chbox.color_used = color_used
399
400 return chbox
401end
402
403--Progress Bar constructor
404function NewProgressBar(target, x, y, width, height, color_bg, color_used)
405 local pbar = {
406 step = 0.01,
407 progress = 0.0,
408
409 setProgress = function(s, np)
410 s.progress = (np > 1) and 1 or np
411 s:draw()
412 end,
413
414 clear = function(s)
415 s.progress = 0
416 s:draw()
417 end,
418
419 stepIt = function(s)
420 s.progress = s.progress + s.step
421 if s.progress > 1 then s.progress = 1 end
422 s:draw()
423 end,
424
425 draw = function(s)
426 if not s.visible then return end
427 local cursorX, cursorY = s.target.getCursorPos()
428 s:_dynRefresh()
429
430 local pos = s.width * s.progress
431 if pos < s.width then
432 drawRect(s.target, s.dynX + pos, s.dynY, s.width - pos, s.height, s.color_bg)
433 end
434 if pos > 0 and pos ~= s.width then
435 drawRect(s.target, s.dynX, s.dynY, pos+1, s.height, s.color_used)
436 end
437
438 s.text = tostring( math.floor(s.progress * 100) ).."%"
439
440 local cx, cy = getTextLayoutPos(s.text_pos, s.text, s.dynX, s.dynY, s.width, s.height)
441 s.target.setTextColor(s.color_text)
442 s.target.setCursorPos(cx, cy)
443
444 if cx > s.dynX + pos then
445 s.target.setBackgroundColor(s.color_bg)
446 s.target.write(s.text)
447 else
448 s.target.write(string.sub(s.text, 1, math.floor(s.dynX + pos - cx + 1) ) )
449 s.target.setBackgroundColor(s.color_bg)
450 s.target.write(string.sub(s.text, math.floor(s.dynX + pos - cx + 2), #s.text))
451 end
452
453 s.target.setCursorPos(cursorX, cursorY)
454 end,
455 }
456 pbar = setmetatable(pbar, getDefaults())
457 pbar.target = target
458 pbar.x = x
459 pbar.y = y
460 pbar.width = width
461 pbar.height = height
462 pbar.color_bg = color_bg
463 pbar.color_used = color_used
464
465 return pbar
466end
467
468function NewTextLine(target, x, y, width, text, func, color_bg, color_text)
469 local textline = {
470 secure = false,
471 _textpos = 1,
472 _cursorpos = 1,
473
474 draw = function(s)
475 if not s.visible then return end
476 local cursorX, cursorY = s.target.getCursorPos()
477
478 s:_dynRefresh()
479
480 drawRect(s.target, s.dynX, s.dynY, s.width, 1, s.color_bg)
481
482 s.target.setTextColor(s.color_text)
483 s.target.setCursorPos(s.dynX, s.dynY)
484 local temp = string.sub(s.text, s._textpos, s._textpos + s.width)
485 if s.secure then
486 for i = 1, #temp do
487 s.target.write("*")
488 end
489 else
490 s.target.write(temp)
491 end
492
493 s.target.setCursorPos(cursorX, cursorY)
494 end,
495
496 clickCheck = function(s, t)
497 if not s.enabled then return end
498
499 if t[1] == "monitor_touch" and t[2] ~= getPeripheralName(s.target)
500 or s.target ~= term.native() and t[1] == "mouse_click" then
501 return
502 end
503
504 s:_dynRefresh()
505
506 if inBounds(t[3], t[4], s.dynX, s.dynY, s.width - 1, s.height - 1) then
507 SELECTED_OBJECT = s
508 return true
509 end
510 return false
511 end,
512
513 eventReact = function(s, e)
514 if not s.enabled then return end
515 if e[1] == "key" then
516 if e[2] == 28 then
517 SELECTED_OBJECT = nil
518 s:used()
519 elseif e[2] == 14 then
520 s.text = string.sub(s.text, 1, #s.text - 1)
521 s._cursorpos = s._cursorpos - 1
522
523 if s._cursorpos <= s._textpos then
524 s._textpos = s._textpos - 4
525 end
526 if s._textpos < 1 then
527 s._textpos = 1
528 end
529 if s._cursorpos < 1 then
530 s._cursorpos = 1
531 end
532
533 s:draw()
534 elseif e[2] == 203 then
535 --left
536 elseif e[2] == 205 then
537 --right
538 end
539 elseif e[1] == "char" then
540 s.text = s.text .. e[2] --testing
541
542 s._cursorpos = s._cursorpos + 1
543
544 if s._cursorpos > s._textpos + s.width - 1 then
545 s._textpos = s._textpos + 1
546 end
547
548 s:draw()
549 end
550 end,
551 }
552 textline = setmetatable(textline, getDefaults())
553 textline.target = target
554 textline.x = x
555 textline.y = y
556 textline.width = width
557 textline.text = text
558 textline.func = func
559 textline.color_bg = color_bg
560 textline.color_text = color_text
561 return textline
562end
563
564--TextArea constructor
565function NewTextArea(target, x, y, width, height, text, color_bg, color_text)
566 local textarea = {
567 draw = function(s)
568 if not s.visible then return end
569 local cursorX, cursorY = s.target.t()
570
571 s:_dynRefresh()
572
573 drawRect(s.target, s.dynX, s.dynY, s.width, s.height, s.color_bg)
574
575 local k = 0
576 for i = 1, string.len(s.text), s.width do
577 s.target.setCursorPos(s.dynX, s.dynY + k)
578 k = k + 1
579 s.target.setTextColor(s.color_text)
580 s.target.write(string.sub(s.text, i, i + s.width - 1))
581 end
582
583 s.target.setCursorPos(cursorX, cursorY)
584 end,
585
586 clickCheck = function(s, t)
587 if not s.enabled then return end
588
589 if t[1] == "monitor_touch" and t[2] ~= getPeripheralName(s.target)
590 or s.target ~= term.native() and t[1] == "mouse_click" then
591 return
592 end
593
594 s:_dynRefresh()
595
596 if inBounds(t[3], t[4], s.dynX, s.dynY, s.width - 1, s.height - 1) then
597 SELECTED_OBJECT = s
598 return true
599 end
600 return false
601 end,
602
603 eventReact = function(s, e)
604 if not s.enabled then return end
605 if e[1] == "key" then
606 if e[2] == 28 then
607 --testing
608 elseif e[2] == 14 then
609 s.text = string.sub(s.text, 1, #s.text - 1)
610 s:draw()
611 end
612 elseif e[1] == "char" then
613 s.text = s.text .. e[2] --testing
614 s:draw()
615 end
616 end,
617 }
618 textarea = setmetatable(textarea, getDefaults())
619 textarea.target = target
620 textarea.x = x
621 textarea.y = y
622 textarea.width = width
623 textarea.height = height
624 textarea.text = text
625 textarea.color_bg = color_bg
626 textarea.color_text = color_text
627 return textarea
628end
629
630--Label constructor
631function NewLabel(target, x, y, text, color_bg, color_text)
632 local label = {
633 draw = function(s)
634 if not s.visible then return end
635 local cursorX, cursorY = s.target.getCursorPos()
636
637 s:_dynRefresh()
638
639 drawLine(s.target, s.dynX, s.dynY, s.dynX + string.len(s.text) - 1, s.dynY , s.color_bg)
640 s.target.setTextColor(s.color_text)
641 s.target.setCursorPos(s.dynX, s.dynY)
642 s.target.write(s.text)
643
644 s.target.setCursorPos(cursorX, cursorY)
645 end,
646 }
647 label = setmetatable(label, getDefaults())
648 label.target = target
649 label.x = x
650 label.y = y
651 label.text = text
652 label.color_bg = color_bg
653 label.color_text = color_text
654 return label
655end
656
657--click/touch handler
658local function exec(event, object)
659 if not object.enabled then return end
660 for _, child in pairs(object._CHILDREN) do
661 exec(event, child)
662 if child:clickCheck(event) then
663 return true
664 end
665 end
666end
667
668MainPanel = NewPanel()
669
670--Event handler. Call this if overwrite os.pullEvent()
671function eventHandler(e)
672 if e[1] == "mouse_click" or e[1] == "monitor_touch" then
673 --Check if selected object or its children clicked
674 if SELECTED_OBJECT then
675 exec(e, SELECTED_OBJECT)
676 end
677 SELECTED_OBJECT = nil
678 exec(e, MainPanel)
679 elseif e[1] == "key" or e[1] == "char"then
680 if SELECTED_OBJECT then
681 SELECTED_OBJECT:eventReact(e)
682 end
683 end
684end
685
686backupPullEvent = os.pullEvent
687
688function os.pullEvent()
689 local e = { os.pullEventRaw() }
690 eventHandler(e)
691 return e[1], e[2], e[3], e[4], e[5]
692end