· 7 years ago · Nov 13, 2018, 06:12 PM
1widgets = {}
2lastSessionWidgets = {}
3
4tabs = {}
5lastSessionTabs = {}
6tabsPage = {}
7activeTab = 0
8oldActiveTabData = {}
9activeTabData = {}
10activeTabPage = 1
11
12textboxGroup = {}
13
14bRun = true
15bDialogRunning = false
16bCanButtonAnim = true
17
18--[[
19
20Events :
21"button_clicked" : string button_name, number mouse_button, number mouse_x, number mouse_y
22"button_close_clicked" : -
23"textbox_text" : string textbox_name, string new_value, boolean return_key_used
24"switch_state" : string switch_name, boolean new_state, number mouse_button, number mouse_x, number mouse_y
25"numeric_value" : string value_name, number new_value, number mouse_button, number mouse_x, number mouse_y
26"list_selected" : string list_name, string new_value, number mouse_button, number mouse_x, number mouse_y
27"numericslider_value" : string numericslider_name, number new_value, number mouse_button, number mouse_x, number mouse_y
28"tab_changed" : string tab_id, number mouse_button, number mouse_x, number mouse_y
29"auto_tab_changed" : string tab_id
30
31Parallel functions :
32parallel.waitForAll(arcui.eventHandler, arcui.marqueeAnim)
33
34--> Main loop
35eventHandler : Make buttons, textboxes and sliders work
36
37--> Required animations
38marqueeAnim : Enable animation for widget_progress_marquee
39
40--> Optional animations
41progressAnim : Enable animation for widget_progress (still in beta)
42buttonAnim : Enable animation for widget_button (still in beta)
43listAnim : Enable animation for widget_list (still in beta)
44
45Functions :
46--> Window
47drawWindow(string window_title, boolean show_close_button, boolean skip_animation, number title_bar_color, number background_color, boolean show_tabs_bottom) : Draw an arcUI window
48redrawWindow() : Quickly redraw the window and it's contents
49closeWindow(boolean skip_animation) : Close the window, to use before closing the app
50openDialog(string dialog_id, string dialog_title, string dialog_message[, string left_button_text, string right_button_text, number background_color, number text_color, number left_button_color, number right_button_color, number left_button_text_color, number right_button_text_color, boolean hide_left_button]) : Open an interactive dialog (WARN: Use only when a window is opened)
51
52--> Widgets (Every draw() function has a corresponding create() function)
53deleteWidget(string widget_id) : Disable and delete a widget
54deleteAllWidgets() : Delete all the widgets in the screen
55widgetUpdate(string widget_id) : Redraw a widget
56getWidgetList() : Returns a list of widget with their types
57getWidgetListByType(string widget_type) : Returns a list of widgets filtered by the type
58setProperty(string widget_id, string value_index, string value) : Change a property of a widget
59setProperties(string widget_id, string first_value_index, string first_value, ...) : Change multiple properties of a widget
60getProperty(string widget_id, string value_index) : Get a property of a widget
61
62drawButton(string widget_id, number start_x, number start_y, number end_x, number end_y, string button_text[, number button_color, boolean enable_animation]) : Add an interactive button to the screen
63drawProgress(string widget_id, number start_x, number y, number end_x, number max_value, number value) : Add a progress bar to the screen
64drawMarqueeProgress(string widget_id, number start_x, number y, number end_x) : Add a progress bar with an undetermined value to the screen
65drawLabel(string widget_id, number x, number y, string label_text[, number background_color, number text_color]) : Add an editable label to the screen
66drawTextbox(string widget_id, number start_x, number y, number end_x[, string textbox_placeholder, string char_replace, boolean enable_history, table history, table auto_completion, number inactive_background_color, number active_background_color, number inactive_text_color, number active_text_color, boolean numeric_values_only]) : Add a flexible textbox to the screen
67drawSwitch(string widget_id, number x, number y, string slider_text[, boolean default_state]) : Add a 4x1 switch to the screen
68drawNumeric(string widget_id, number start_x, number y, number end_x, number default_value, number min_value, number max_value[, number incrementation]) : Add a NumericUpDown to the screen
69drawList(string widget_id, number start_x, number start_y, number end_x, number end_y, table values[, number selected_color, number text_color, number background_color, number second_background_color]) : Add a ListBox to the screen
70drawNumericSlider(string widget_id, number start_x, number y, number end_x[, number default_value, number min_value, number max_value, number slider_color]) : Add a numeric slider to the screen
71drawScrollManager(string widget_id, number start_x, number start_y, number end_x, number end_y, boolean auto_resize, number size) : Add a ScrollManager to the screen
72
73--> Tabs
74addTab(string tab_id, string tab_name[, number tab_position]) : Add a tab to the tab manager
75removeTab(string tab_id) : Remove a tab from the tab manager
76
77linkToTab(string tab_id, table widget) : Link a widget to a tab (widget is like linkToScrollManager())
78unlinkFromTab(string widget_id) : Unlink a widget from it's tab
79
80getTabCount() : Returns the tab count
81getTabPageCount() : Return the tab page count
82getTabPageCountBeforeActive() : Return the number of tabs before the active page
83
84--> ScrollManager
85linkToScrollManager(string scrollmanager_id, table widget) : Manage a widget with a ScrollManager
86autoResizeScrollManager(string scrollmanager_id) : Resize automatically a ScrollManager
87scrollScrollManager(number relative_scroll, string scrollmanager_id) : Scroll a ScrollManager relatively
88scrollScrollManagerTo(number absolute_scroll, string scrollmanager_id) : Scroll a ScrollManager with an absolute value
89Ex: arcui.linkToScrollManager("scrlmanid", arcui.createNumericSlider("numslid", 2, 2, 20, 50))
90
91--> Textbox groups
92createTextboxGroup(string group_id) : Create a new textbox group
93removeTextboxGroup(string group_id) : Remove a textbox group
94getTextboxGroup(string group_id) : Get the textbox(es) in a group
95addTextboxToGroup(string group_id, string textbox_id) : Add a textbox to a group
96addTextboxesToGroup(string group_id, string first_textbox_id, ...) : Add multiple textboxes to a group
97
98--> Utils
99columnFormatter(string first_column, number first_column_length, ...) : Format a string to mimic columns (Each column must have a length of 4 chars minimum)
100writeFormatted("string formatted_string") : Write a text with dynamic color changes (See "String formatting" section)
101
102String formatting :
103Every string in arcUI can be formatted to change the text and background color dynamically.
104Example: arcui.writeFormatted("$1Text $3Background$r&rReset")
105
106'$x' will change the text color, with 'x' a valid color or r to reset
107'&x' will change the background color, with 'x' a valid color or r to reset
108
109Credits :
110Made by Kuruyia (formerly known as arc13)
111
112The MIT License (MIT)
113
114Copyright (c) 2017-2018 Kuruyia
115
116Permission is hereby granted, free of charge, to any person obtaining a copy of
117this software and associated documentation files (the "Software"), to deal in
118the Software without restriction, including without limitation the rights to
119use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
120the Software, and to permit persons to whom the Software is furnished to do so,
121subject to the following conditions:
122
123The above copyright notice and this permission notice shall be included in all
124copies or substantial portions of the Software.
125
126THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
127IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
128FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
129COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
130IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
131CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
132
133]]
134
135local function debugPrint(sToPrint)
136 local curX, curY = term.getCursorPos()
137 local monX, monY = term.getSize()
138 local bgColor = term.getBackgroundColor()
139 local textColor = term.getTextColor()
140
141 term.setCursorPos(1, monY)
142 term.setTextColor(colors.black)
143 term.setBackgroundColor(colors.white)
144
145 term.clearLine()
146 term.write(tostring(sToPrint))
147
148 term.setCursorPos(curX, curY)
149 term.setBackgroundColor(bgColor)
150 term.setTextColor(textColor)
151end
152
153local function custRead(nSize, _sReplaceChar, _tHistory, _fnComplete, sWidget)
154 term.setCursorBlink( true )
155
156 local sLine = ""
157 local nHistoryPos
158 local nPos = 0
159 local nLastUsedKey = 0
160 local bNumericOnly = false
161 if _sReplaceChar then
162 _sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
163 end
164
165 if sWidget and widgets[sWidget] then
166 local widgetValue = tostring(widgets[sWidget]["value"])
167 if widgetValue then
168 sLine = widgetValue
169 nPos = sLine:len()
170 end
171
172 if widgets[sWidget]["numericOnly"] then
173 bNumericOnly = true
174 end
175 end
176
177 local tCompletions
178 local nCompletion
179 local function recomplete()
180 if _fnComplete and nPos == string.len(sLine) then
181 tCompletions = _fnComplete( sLine , sWidget)
182 if tCompletions and #tCompletions > 0 then
183 nCompletion = 1
184 else
185 nCompletion = nil
186 end
187 else
188 tCompletions = nil
189 nCompletion = nil
190 end
191 end
192
193 local function uncomplete()
194 tCompletions = nil
195 nCompletion = nil
196 end
197
198 local w,monHeight = term.getSize()
199 local sx = term.getCursorPos()
200
201 w = nSize
202
203 local function redraw( _bClear )
204 local nScroll = 0
205 if sx + nPos >= w then
206 nScroll = (sx + nPos) - w
207 end
208
209 term.setBackgroundColor(widgets[sWidget]["activeColor"])
210
211 local cx,cy = term.getCursorPos()
212 term.setCursorPos( sx, cy )
213 local sReplace = (_bClear and " ") or _sReplaceChar
214 if sReplace then
215 term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 ) ) )
216 else
217 term.write( string.sub( sLine, nScroll + 1 ) )
218 end
219
220 if nCompletion then
221 local sCompletion = tCompletions[ nCompletion ]
222 local oldText, oldBg
223 if not _bClear then
224 oldText = term.getTextColor()
225 oldBg = term.getBackgroundColor()
226 term.setTextColor( colors.white )
227 term.setBackgroundColor( colors.lightBlue )
228 end
229 if sReplace then
230 term.write( string.rep( sReplace, string.len( sCompletion ) ) )
231 else
232 term.write( sCompletion )
233 end
234 if not _bClear then
235 term.setTextColor( oldText )
236 term.setBackgroundColor( oldBg )
237 end
238 end
239
240 term.setCursorPos( sx + nPos - nScroll, cy )
241 end
242
243 local function clear()
244 redraw( true )
245 end
246
247 recomplete()
248 redraw()
249
250 local function acceptCompletion()
251 if nCompletion then
252 -- Clear
253 clear()
254
255 -- Find the common prefix of all the other suggestions which start with the same letter as the current one
256 local sCompletion = tCompletions[ nCompletion ]
257 local sFirstLetter = string.sub( sCompletion, 1, 1 )
258 local sCommonPrefix = sCompletion
259 for n=1,#tCompletions do
260 local sResult = tCompletions[n]
261 if n ~= nCompletion and string.find( sResult, sFirstLetter, 1, true ) == 1 then
262 while #sCommonPrefix > 1 do
263 if string.find( sResult, sCommonPrefix, 1, true ) == 1 then
264 break
265 else
266 sCommonPrefix = string.sub( sCommonPrefix, 1, #sCommonPrefix - 1 )
267 end
268 end
269 end
270 end
271
272 -- Append this string
273 sLine = sLine .. sCommonPrefix
274 nPos = string.len( sLine )
275 else
276 return false
277 end
278
279 recomplete()
280 redraw()
281
282 return true
283 end
284 while true do
285 local sEvent, param, a2, a3 = os.pullEvent()
286 if sEvent == "char" then
287 -- Typed key
288 if not bNumericOnly or (bNumericOnly and tonumber(param)) then
289 clear()
290 sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
291 nPos = nPos + 1
292 recomplete()
293 redraw()
294 end
295
296 elseif sEvent == "paste" then
297 -- Pasted text
298 if not bNumericOnly or (bNumericOnly and tonumber(param)) then
299 clear()
300 sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
301 nPos = nPos + string.len( param )
302 recomplete()
303 redraw()
304 end
305
306 elseif sEvent == "key" then
307 nLastUsedKey = param
308
309 if param == keys.enter then
310 -- Enter
311 if nCompletion then
312 clear()
313 uncomplete()
314 redraw()
315 end
316 break
317
318 elseif param == keys.left then
319 -- Left
320 if nPos > 0 then
321 clear()
322 nPos = nPos - 1
323 recomplete()
324 redraw()
325 end
326
327 elseif param == keys.right then
328 -- Right
329 if nPos < string.len(sLine) then
330 -- Move right
331 clear()
332 nPos = nPos + 1
333 recomplete()
334 redraw()
335 else
336 -- Accept autocomplete
337 acceptCompletion()
338 end
339
340 elseif param == keys.up or param == keys.down then
341 -- Up or down
342 if nCompletion then
343 -- Cycle completions
344 clear()
345 if param == keys.up then
346 nCompletion = nCompletion - 1
347 if nCompletion < 1 then
348 nCompletion = #tCompletions
349 end
350 elseif param == keys.down then
351 nCompletion = nCompletion + 1
352 if nCompletion > #tCompletions then
353 nCompletion = 1
354 end
355 end
356 redraw()
357
358 elseif _tHistory then
359 -- Cycle history
360 clear()
361 if param == keys.up then
362 -- Up
363 if nHistoryPos == nil then
364 if #_tHistory > 0 then
365 nHistoryPos = #_tHistory
366 end
367 elseif nHistoryPos > 1 then
368 nHistoryPos = nHistoryPos - 1
369 end
370 else
371 -- Down
372 if nHistoryPos == #_tHistory then
373 nHistoryPos = nil
374 elseif nHistoryPos ~= nil then
375 nHistoryPos = nHistoryPos + 1
376 end
377 end
378 if nHistoryPos then
379 sLine = _tHistory[nHistoryPos]
380 nPos = string.len( sLine )
381 else
382 sLine = ""
383 nPos = 0
384 end
385 uncomplete()
386 redraw()
387
388 end
389
390 elseif param == keys.backspace then
391 -- Backspace
392 if nPos > 0 then
393 clear()
394 sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
395 nPos = nPos - 1
396 recomplete()
397 redraw()
398 end
399
400 elseif param == keys.home then
401 -- Home
402 if nPos > 0 then
403 clear()
404 nPos = 0
405 recomplete()
406 redraw()
407 end
408
409 elseif param == keys.delete then
410 -- Delete
411 if nPos < string.len(sLine) then
412 clear()
413 sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )
414 recomplete()
415 redraw()
416 end
417
418 elseif param == keys["end"] then
419 -- End
420 if nPos < string.len(sLine ) then
421 clear()
422 nPos = string.len(sLine)
423 recomplete()
424 redraw()
425 end
426
427 elseif param == keys.tab then
428 -- Tab (accept autocomplete)
429 if not acceptCompletion() and widgets[sWidget]["group"] then
430 break
431 end
432 end
433
434 elseif sEvent == "term_resize" then
435 -- Terminal resized
436 w,monHeight = term.getSize()
437 w = nSize
438 redraw()
439
440 elseif sEvent == "mouse_click" or (sEvent == "monitor_touch" and w ~= 51 and monHeight ~= 19) then
441 -- Someone clicked somewhere, act as enter key and queueing event
442 if nCompletion then
443 clear()
444 uncomplete()
445 redraw()
446 end
447 break
448 end
449end
450
451local cx, cy = term.getCursorPos()
452term.setCursorBlink( false )
453term.setCursorPos( w + 1, cy )
454print()
455
456return sLine, nLastUsedKey
457end
458
459local function tostringn(value)
460 if value == nil then
461 return nil
462 else
463 return tostring(value)
464 end
465end
466
467local function checkArguments(bNeeded, ...)
468 local tArgs = {...}
469 local nActualArg = 1
470
471 if #tArgs % 2 == 1 then
472 table.remove(tArgs, #tArgs)
473 end
474
475 for i = 1, #tArgs, 2 do
476 if type(tArgs[i]) ~= tArgs[i + 1] and (bNeeded or type(tArgs[i]) ~= "nil") then
477 return false, "Argument #"..nActualArg..": Expected "..tArgs[i + 1]..", got "..type(tArgs[i])
478 end
479
480 nActualArg = nActualArg + 1
481 end
482
483 return true
484end
485
486local function reverseTwoArgs(arg1, arg2)
487 return arg2, arg1
488end
489
490function createTextboxGroup(groupId)
491 groupId = tostringn(groupId)
492
493 local bValidArgs, sErrMsg = checkArguments(true, groupId, "string")
494 if not bValidArgs then
495 return false, sErrMsg
496 end
497
498 if textboxGroup[groupId] then
499 return false, "The specified textbox group already exists"
500 end
501
502 textboxGroup[groupId] = {}
503 return true
504end
505
506function removeTextboxGroup(groupId)
507 groupId = tostringn(groupId)
508
509 local bValidArgs, sErrMsg = checkArguments(true, groupId, "string")
510 if not bValidArgs then
511 return false, sErrMsg
512 end
513
514 if not textboxGroup[groupId] then
515 return false, "The specified textbox group does not exist"
516 end
517
518 for k, v in pairs(textboxGroup[groupId]) do
519 widgets[v]["group"] = nil
520 end
521
522 textboxGroup[groupId] = nil
523 return true
524end
525
526function getTextboxGroup(groupId)
527 groupId = tostringn(groupId)
528
529 local bValidArgs, sErrMsg = checkArguments(true, groupId, "string")
530 if not bValidArgs then
531 return false, sErrMsg
532 end
533
534 if not textboxGroup[groupId] then
535 return false, "The specified textbox group does not exist"
536 end
537
538 return textboxGroup[groupId]
539end
540
541function addTextboxToGroup(groupId, textboxId)
542 groupId = tostringn(groupId)
543 textboxId = tostringn(textboxId)
544
545 local bValidArgs, sErrMsg = checkArguments(true, groupId, "string", textboxId, "string")
546 if not bValidArgs then
547 return false, sErrMsg
548 end
549
550 if not widgets[textboxId] then
551 return false, "The specified textbox does not exist"
552 end
553
554 if not textboxGroup[groupId] then
555 return false, "The specified textbox group does not exist"
556 end
557
558 table.insert(textboxGroup[groupId], textboxId)
559 widgets[textboxId]["group"] = groupId
560
561 return true
562end
563
564function addTextboxesToGroup(groupId, ...)
565 groupId = tostringn(groupId)
566
567 local bValidArgs, sErrMsg = checkArguments(true, groupId, "string")
568 if not bValidArgs then
569 return false, sErrMsg
570 end
571
572 local tArgs = {...}
573
574 if not textboxGroup[groupId] then
575 return false, "The specified textbox group does not exist"
576 end
577
578 for i = 1, #tArgs do
579 tArgs[i] = tostringn(tArgs[i])
580
581 if not widgets[tArgs[i]] then
582 return false, "Argument#"..i.." : The specified textbox does not exist"
583 end
584 end
585
586 for i = 1, #tArgs do
587 table.insert(textboxGroup[groupId], tArgs[i])
588 widgets[tArgs[i]]["group"] = groupId
589 end
590
591 return true
592end
593
594function writeFormatted(sValue, isBackgroundChangesAllowed)
595 sValue = tostringn(sValue)
596 if not sValue then
597 return false, "Argument #1: Expected string, got "..type(sValue)
598 end
599
600 if not sValue:match("$") and not sValue:match("&") then
601 term.write(sValue)
602 else
603 local colorsDecToPaint = {["0"] = colors.white, ["1"] = colors.orange, ["2"] = colors.magenta, ["3"] = colors.lightBlue, ["4"] = colors.yellow, ["5"] = colors.lime, ["6"] = colors.pink, ["7"] = colors.gray, ["8"] = colors.lightGray, ["9"] = colors.cyan, ["a"] = colors.purple, ["b"] = colors.blue, ["c"] = colors.brown, ["d"] = colors.green, ["e"] = colors.red, ["f"] = colors.black}
604 local skipCurrentChar = false
605
606 local currentBgColor = term.getBackgroundColor()
607 local currentTextColor = term.getTextColor()
608
609 if type(isBackgroundChangesAllowed) == "nil" then
610 isBackgroundChangesAllowed = true
611 end
612
613 for i = 1, sValue:len() do
614 if not skipCurrentChar then
615 local currentChar = sValue:sub(i, i)
616
617 if currentChar ~= "$" and currentChar ~= "&" then
618 term.write(currentChar)
619 else
620 skipCurrentChar = true
621 local modifier = sValue:sub(i + 1, i + 1)
622
623 if colorsDecToPaint[modifier] then
624 if currentChar == "$" then
625 term.setTextColor(colorsDecToPaint[modifier])
626 elseif currentChar == "&" and isBackgroundChangesAllowed then
627 term.setBackgroundColor(colorsDecToPaint[modifier])
628 end
629 elseif modifier == "r" then
630 if currentChar == "$" then
631 term.setTextColor(currentTextColor)
632 elseif currentChar == "&" and isBackgroundChangesAllowed then
633 term.setBackgroundColor(currentBgColor)
634 end
635 else
636 term.write(modifier)
637 end
638 end
639 else
640 skipCurrentChar = false
641 end
642 end
643
644 term.setBackgroundColor(currentBgColor)
645 term.setTextColor(currentTextColor)
646 end
647end
648
649local function getWidgetHitbox(sWidgetId)
650 local widgetStartX = 0
651 local widgetStartY = 0
652 local widgetEndX = 0
653 local widgetEndY = 0
654 local widgetLossX = 0
655 local widgetLossTopY = 0
656 local widgetLossBotY = 0
657
658 if widgets[sWidgetId]["startX"] then
659 widgetStartX = widgets[sWidgetId]["startX"]
660 end
661 if widgets[sWidgetId]["startY"] then
662 widgetStartY = widgets[sWidgetId]["startY"]
663 end
664 if widgets[sWidgetId]["endX"] then
665 widgetEndX = widgets[sWidgetId]["endX"]
666 end
667 if widgets[sWidgetId]["endY"] then
668 widgetEndY = widgets[sWidgetId]["endY"]
669 end
670
671 if widgets[sWidgetId]["scrollManager"] then
672 if widgetStartY < widgets[widgets[sWidgetId]["scrollManager"]]["startY"] and widgetEndY >= widgets[widgets[sWidgetId]["scrollManager"]]["startY"] then
673 widgetStartY = widgets[widgets[sWidgetId]["scrollManager"]]["startY"]
674
675 widgetLossTopY = widgetStartY - widgets[sWidgetId]["startY"]
676 end
677
678 if widgetEndY > widgets[widgets[sWidgetId]["scrollManager"]]["endY"] and widgetStartY <= widgets[widgets[sWidgetId]["scrollManager"]]["endY"] then
679 widgetEndY = widgets[widgets[sWidgetId]["scrollManager"]]["endY"]
680
681 widgetLossBotY = widgets[sWidgetId]["endY"] - widgetEndY
682 end
683 end
684
685 return widgetStartX, widgetStartY, widgetEndX, widgetEndY, widgetLossX, widgetLossTopY, widgetLossBotY
686end
687
688function columnFormatter(...)
689 local tArgs = {...}
690 if #tArgs % 2 == 1 then
691 table.remove(tArgs)
692 end
693
694 if #tArgs == 0 then
695 return false, "Not enough arguments"
696 end
697
698 local stringCount = #tArgs / 2
699
700 for i = 1, #tArgs do
701 if i % 2 == 0 then
702 tArgs[i] = tonumber(tArgs[i])
703
704 if type(tArgs[i]) ~= "number" then
705 return false, "Argument #"..tostring(i)..": Expected number, got "..type(tArgs[i])
706 end
707 if tArgs[i] < 4 then
708 return false, "Argument #"..tostring(i)..": Not enough length, min 4"
709 end
710 else
711 tArgs[i] = tostringn(tArgs[i])
712
713 if type(tArgs[i]) ~= "string" then
714 return false, "Argument #"..tostring(i)..": Expected string, got "..type(tArgs[i])
715 end
716 end
717 end
718
719 local returnString = ""
720 for i = 1, #tArgs, 2 do
721 local currentString = tArgs[i]
722 local currentStringLength = currentString:len()
723 local currentStringExpectedLength = tArgs[i + 1]
724
725 local startAppendChars = ""
726 local endAppendChars = ""
727
728 if currentString:match("$") and currentString:match("&") then
729 local isStringTooBig = false
730 local formatCharCountStart = 0
731 local formatCharCountEnd = 0
732
733 for i = 1, currentStringLength, 2 do
734 local currentChar = currentString:sub(i, i)
735 local modifier = currentString:sub(i + 1, i + 1)
736
737 if (currentChar == "$" or currentChar == "&") and (modifier:match("%x") or modifier == "r") then
738 formatCharCountStart = formatCharCountStart + 1
739 else
740 break
741 end
742 end
743
744 for i = currentStringLength - 1, 1, -2 do
745 local currentChar = currentString:sub(i, i)
746 local modifier = currentString:sub(i + 1, i + 1)
747
748 if (currentChar == "$" or currentChar == "&") and (modifier:match("%x") or modifier == "r") then
749 formatCharCountEnd = formatCharCountEnd + 1
750 else
751 break
752 end
753 end
754
755 startAppendChars = currentString:sub(1, formatCharCountStart * 2)
756 endAppendChars = currentString:sub(currentStringLength - (formatCharCountEnd * 2) + 1, currentStringLength)
757
758 local separatedString = currentString:sub(formatCharCountStart * 2 + 1, currentStringLength - (formatCharCountEnd * 2))
759 local formatChars = 0
760 local sanitizedString = ""
761
762 local skipCurrentChar = false
763 for i = 1, separatedString:len() do
764 if not skipCurrentChar then
765 local currentChar = separatedString:sub(i, i)
766
767 if currentChar == "$" or currentChar == "&" then
768 skipCurrentChar = true
769
770 local modifier = separatedString:sub(i + 1, i + 1)
771
772 if modifier:match("%x") or modifier == "r" then
773 formatChars = formatChars + 2
774 else
775 formatChars = formatChars + 1
776 sanitizedString = sanitizedString..modifier
777 end
778 else
779 sanitizedString = sanitizedString..currentChar
780 end
781 else
782 skipCurrentChar = false
783 end
784 end
785
786 if ((i + 1) / 2 == stringCount and sanitizedString:len() > currentStringExpectedLength) or ((i + 1) / 2 ~= stringCount and sanitizedString:len() >= currentStringExpectedLength) then
787 currentString = sanitizedString
788 currentStringLength = sanitizedString:len()
789 else
790 currentStringExpectedLength = currentStringExpectedLength + formatChars
791 currentString = separatedString
792 currentStringLength = separatedString:len()
793 end
794 end
795
796 local function inflateFormatChars(currentString)
797 if currentString:match("$") and currentString:match("&") then
798 local newString = ""
799
800 for i = 1, currentString:len() do
801 local currentChar = currentString:sub(i, i)
802 newString = newString..currentChar
803
804 if currentChar == "$" or currentChar == "&" then
805 newString = newString..currentChar
806 end
807 end
808
809 return newString
810 else
811 return currentString
812 end
813 end
814
815 if (i + 1) / 2 == stringCount then
816 if currentStringLength > currentStringExpectedLength then
817 currentString = currentString:sub(1, currentStringExpectedLength - 3).."..."
818 currentString = inflateFormatChars(currentString)
819 currentString = currentString..endAppendChars
820 else
821 currentString = currentString..endAppendChars
822 end
823 else
824 if currentStringLength >= currentStringExpectedLength then
825 currentString = currentString:sub(1, currentStringExpectedLength - 4).."..."
826 currentString = inflateFormatChars(currentString)
827 currentString = currentString..endAppendChars.." "
828 elseif currentStringLength < currentStringExpectedLength then
829 currentString = currentString..endAppendChars
830 for i = 1, currentStringExpectedLength - currentStringLength do
831 currentString = currentString.." "
832 end
833 end
834 end
835
836 currentString = startAppendChars..currentString
837 returnString = returnString..currentString
838 end
839
840 return returnString
841end
842
843function widgetUpdate(sId)
844 if not sId or not widgets[sId] then
845 return false
846 end
847
848 local widgetStartX, widgetStartY, widgetEndX, widgetEndY, _, widgetLossTopY, widgetLossBotY = getWidgetHitbox(sId)
849 local monX, monY = term.getSize()
850 local oldColor = widgets["window"]["bgColor"]
851 local oldTextColor = term.getTextColor()
852
853 local function drawVerticalScrollbar(widgetWorkingHeight, sId, widgetStartY, widgetEndY, widgetLossTopY, widgetLossBotY)
854 if widgetWorkingHeight > widgetEndY - widgetStartY + 1 + widgetLossTopY then
855 local navBarWorkingLength = widgets[sId]["height"] - 1
856 local navBarLength = math.floor(widgets[sId]["height"] * navBarWorkingLength / widgetWorkingHeight)
857 local navBarJump = math.floor((widgets[sId]["valuesShownMin"] - 1) * navBarWorkingLength / widgetWorkingHeight)
858
859 if widgetLossTopY > 0 then
860 navBarJump = navBarJump - widgetLossTopY
861 end
862
863 if navBarLength > navBarWorkingLength then
864 navBarLength = navBarWorkingLength - 1
865 end
866
867 if (navBarJump + navBarLength >= navBarWorkingLength) or (widgets[sId]["valuesShownMax"] == widgetWorkingHeight) then
868 navBarJump = navBarWorkingLength - navBarLength - 1 - widgetLossTopY
869 end
870
871 if widgetLossTopY == 0 and widgetLossBotY == 0 then
872 paintutils.drawLine(widgetEndX, widgetStartY + 1 + navBarJump, widgetEndX, widgetStartY + 1 + navBarLength + navBarJump, colors.blue)
873 else
874 local navBarTop = widgetStartY + 1 + navBarJump
875 local navBarBottom = navBarTop + navBarLength
876
877 if navBarTop < widgetStartY then
878 navBarLength = navBarLength - (widgetStartY - navBarTop)
879 navBarTop = widgetStartY
880 end
881 navBarBottom = navBarTop + navBarLength
882
883 if navBarBottom > widgetEndY then
884 navBarLength = navBarLength - (navBarBottom - widgetEndY)
885 end
886 navBarBottom = navBarTop + navBarLength
887
888 if navBarLength >= 0 then
889 paintutils.drawLine(widgetEndX, navBarTop, widgetEndX, navBarBottom, colors.blue)
890 end
891 end
892 end
893 end
894
895 if widgetStartX > monX or widgetEndX < 1 or widgetStartY > monY or widgetEndY < 1 then
896 return false
897 end
898
899 if widgets[sId]["height"] then
900 widgets[sId]["height"] = widgets[sId]["endY"] - widgets[sId]["startY"]
901 end
902
903 if widgets[sId]["type"] == "widget_button" then
904 if widgets[sId]["enabled"] then
905 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetEndY, widgets[sId]["btnColor"])
906
907 if widgetEndY >= 1 and widgetStartY <= monY then
908 local nButtonMiddleX = (widgetEndX - widgetStartX) / 2
909 local nButtonMiddleInSpaceX = nButtonMiddleX + widgetStartX
910
911 local nButtonMiddleY = (widgetEndY - widgetStartY) / 2
912 local nButtonMiddleInSpaceY = nButtonMiddleY + widgetStartY
913
914 term.setCursorPos(nButtonMiddleInSpaceX - math.floor(string.len(widgets[sId]["text"]) / 2), nButtonMiddleInSpaceY)
915 term.write(widgets[sId]["text"])
916 end
917 else
918 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetEndY, oldColor)
919 end
920 elseif widgets[sId]["type"] == "widget_progress" then
921 if widgets[sId]["enabled"] then
922 local pixelWidth = (widgets[sId]["value"] * widgets[sId]["width"]) / widgets[sId]["maxValue"]
923 local pixelWidthInSpace = pixelWidth + widgetStartX
924
925 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, colors.cyan)
926
927 if widgets[sId]["value"] ~= 0 then
928 paintutils.drawLine(widgetStartX, widgetStartY, pixelWidthInSpace, widgetStartY, colors.blue)
929 end
930
931 widgets[sId]["value_width"] = pixelWidthInSpace
932 else
933 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetStartY, oldColor)
934 end
935 elseif widgets[sId]["type"] == "widget_progress_marquee" then
936 if widgets[sId]["enabled"] then
937 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, colors.cyan)
938 else
939 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetStartY, oldColor)
940 end
941 elseif widgets[sId]["type"] == "widget_label" then
942 if widgets[sId]["enabled"] then
943 local curX, curY = term.getCursorPos()
944
945 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetStartX + string.len(widgets[sId]["text"]), widgetStartY, widgets[sId]["bgColor"])
946
947 term.setBackgroundColor(widgets[sId]["bgColor"] or oldColor)
948 term.setTextColor(tonumber(widgets[sId]["textColor"]) or colors.white)
949 term.setCursorPos(widgetStartX, widgetStartY)
950 writeFormatted(widgets[sId]["text"])
951
952 term.setCursorPos(curX, curY)
953 term.setBackgroundColor(oldColor)
954 term.setTextColor(colors.white)
955 else
956 term.setCursorPos(widgetStartX, widgetStartY)
957
958 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetStartX + string.len(widgets[sId]["text"]), widgetStartY, term.getBackgroundColor())
959 end
960 elseif widgets[sId]["type"] == "widget_textbox" then
961 if widgets[sId]["enabled"] then
962 paintutils.drawLine(widgetStartX + 1, widgetStartY, widgetEndX - 1, widgetStartY, widgets[sId]["inactiveColor"])
963
964 if widgets[sId]["value"] ~= "" then
965 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, widgets[sId]["activeColor"])
966 term.setCursorPos(widgetStartX, widgetStartY)
967 term.setTextColor(widgets[sId]["textActiveColor"])
968 if not widgets[sId]["charReplace"] or widgets[sId]["charReplace"] == "" then
969 writeFormatted(widgets[sId]["value"]:sub(widgets[sId]["value"]:len() - (widgetEndX - widgetStartX - 1), widgets[sId]["value"]:len()))
970 else
971 local nTextLength = widgets[sId]["value"]:len()
972 local nTextboxWidth = widgetEndX - widgetStartX - 1
973
974 if nTextLength > nTextboxWidth then
975 nTextLength = nTextboxWidth
976 end
977
978 for i = 1, nTextLength do
979 write(widgets[sId]["charReplace"])
980 end
981 end
982 term.setTextColor(colors.white)
983 else
984 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, oldColor)
985 paintutils.drawLine(widgetStartX + 1, widgetStartY, widgetEndX - 1, widgetStartY, widgets[sId]["inactiveColor"])
986 if widgets[sId]["text"] then
987 term.setCursorPos(widgetStartX + 1, widgetStartY)
988 term.setTextColor(widgets[sId]["textInactiveColor"])
989 writeFormatted(widgets[sId]["text"])
990 term.setTextColor(colors.white)
991 end
992 end
993 else
994 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetStartY, term.getBackgroundColor())
995 end
996 elseif widgets[sId]["type"] == "widget_switch" then
997 if widgets[sId]["enabled"] then
998 if widgets[sId]["state"] == false then
999 paintutils.drawLine(widgetStartX, widgetStartY, widgetStartX + 3, widgetStartY, colors.white)
1000 paintutils.drawPixel(widgetStartX, widgetStartY, colors.gray)
1001 elseif widgets[sId]["state"] == true then
1002 paintutils.drawLine(widgetStartX, widgetStartY, widgetStartX + 3, widgetStartY, colors.green)
1003 paintutils.drawPixel(widgetStartX + 3, widgetStartY, colors.lime)
1004 end
1005
1006 term.setCursorPos(widgetStartX + 5, widgetStartY)
1007 term.setBackgroundColor(oldColor)
1008 writeFormatted(widgets[sId]["text"])
1009 else
1010 paintutils.drawLine(widgetStartX, widgetStartY, widgetStartX + 4 + string.len(widgets[sId]["text"]), widgetStartY, oldColor)
1011 end
1012 elseif widgets[sId]["type"] == "widget_numeric" then
1013 if widgets[sId]["enabled"] then
1014 paintutils.drawPixel(widgetStartX, widgetStartY, colors.blue)
1015 term.setCursorPos(widgetStartX, widgetStartY)
1016 term.write("-")
1017 paintutils.drawPixel(widgetEndX, widgetStartY, colors.blue)
1018 term.setCursorPos(widgetEndX, widgetStartY)
1019 term.write("+")
1020 paintutils.drawLine(widgetStartX + 1, widgetStartY, widgetEndX - 1, widgetStartY, colors.cyan)
1021
1022 local nNumericMiddleX = (widgetEndX - widgetStartX) / 2
1023 local nNumericMiddleInSpaceX = nNumericMiddleX + widgetStartX
1024
1025 term.setCursorPos(nNumericMiddleInSpaceX, widgetStartY)
1026 writeFormatted(tostring(widgets[sId]["value"]))
1027 else
1028 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, oldColor)
1029 end
1030 elseif widgets[sId]["type"] == "widget_list" then
1031 if widgets[sId]["enabled"] then
1032 widgets[sId]["valuesShown"] = {}
1033 widgets[sId]["valuesShownMax"] = widgets[sId]["valuesShownMin"] + widgets[sId]["height"]
1034
1035 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX - 1, widgetEndY, widgets[sId]["backgroundColor"])
1036 paintutils.drawLine(widgetEndX, widgetStartY, widgetEndX, widgetEndY, colors.gray)
1037
1038 if widgetLossTopY == 0 then
1039 if widgets[sId]["valuesShownMin"] == 1 then
1040 term.setTextColor(colors.lightGray)
1041 end
1042 term.setCursorPos(widgetEndX, widgetStartY)
1043 term.write("-")
1044 end
1045
1046 if widgetLossBotY == 0 then
1047 if widgets[sId]["valuesShownMax"] >= #widgets[sId]["values"] then
1048 term.setTextColor(colors.lightGray)
1049 else
1050 term.setTextColor(colors.white)
1051 end
1052 term.setCursorPos(widgetEndX, widgetEndY)
1053 term.write("+")
1054 end
1055
1056 if widgets[sId]["backgroundColor"] == widgets[sId]["secondBackgroundColor"] then
1057 term.setBackgroundColor(widgets[sId]["backgroundColor"])
1058 end
1059 term.setTextColor(widgets[sId]["textColor"])
1060
1061 for i = widgets[sId]["valuesShownMin"], widgets[sId]["valuesShownMax"] do
1062 table.insert(widgets[sId]["valuesShown"], widgets[sId]["values"][i])
1063 end
1064
1065 for i = 0, #widgets[sId]["valuesShown"] - 1 do
1066 if widgetStartY + i > widgetEndY or not widgets[sId]["valuesShown"][i + 1 + widgetLossTopY] then
1067 break
1068 end
1069
1070 local isCurrentlySelected = i + widgets[sId]["valuesShownMin"] + widgetLossTopY == widgets[sId]["selectedValue"]
1071 if isCurrentlySelected then
1072 paintutils.drawLine(widgetStartX, widgetStartY + i, widgetEndX - 1, widgetStartY + i, widgets[sId]["selectedColor"])
1073 elseif widgets[sId]["backgroundColor"] ~= widgets[sId]["secondBackgroundColor"] then
1074 if (i + widgets[sId]["valuesShownMin"] + widgetLossTopY) % 2 == 1 then
1075 paintutils.drawLine(widgetStartX, widgetStartY + i, widgetEndX - 1, widgetStartY + i, widgets[sId]["secondBackgroundColor"])
1076 else
1077 term.setBackgroundColor(widgets[sId]["backgroundColor"])
1078 end
1079 end
1080
1081 term.setCursorPos(widgetStartX, widgetStartY + i)
1082 writeFormatted(widgets[sId]["valuesShown"][i + 1 + widgetLossTopY], not isCurrentlySelected)
1083 term.setBackgroundColor(widgets[sId]["backgroundColor"])
1084 end
1085
1086 term.setTextColor(colors.white)
1087
1088 drawVerticalScrollbar(#widgets[sId]["values"], sId, widgetStartY, widgetEndY, widgetLossTopY, widgetLossBotY)
1089 else
1090 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetEndY, oldColor)
1091 end
1092 elseif widgets[sId]["type"] == "widget_numericslider" then
1093 if widgets[sId]["enabled"] then
1094 local nSliderPositionX = (widgets[sId]["value"] - widgets[sId]["minValue"]) * (widgetEndX - widgetStartX) / (widgets[sId]["maxValue"] - widgets[sId]["minValue"])
1095 local nSliderPositionInSpaceX = nSliderPositionX + widgetStartX
1096
1097 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, colors.black)
1098 paintutils.drawLine(widgetStartX, widgetStartY, nSliderPositionInSpaceX, widgetStartY, widgets[sId]["sliderColor"] or colors.gray)
1099 paintutils.drawPixel(nSliderPositionInSpaceX, widgetStartY, colors.white)
1100 else
1101 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, oldColor)
1102 end
1103 elseif widgets[sId]["type"] == "widget_scrollmanager" then
1104 if widgets[sId]["enabled"] then
1105 widgets[sId]["valuesShownMax"] = widgets[sId]["valuesShownMin"] + widgets[sId]["height"]
1106
1107 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetEndY, colors.lightBlue)
1108 paintutils.drawLine(widgetEndX, widgetStartY, widgetEndX, widgetEndY, colors.gray)
1109
1110 term.setTextColor(colors.white)
1111 if widgets[sId]["valuesShownMin"] <= 1 then
1112 term.setTextColor(colors.lightGray)
1113 end
1114
1115 term.setCursorPos(widgetEndX, widgetStartY)
1116 term.write("-")
1117
1118 term.setTextColor(colors.white)
1119 if widgets[sId]["valuesShownMax"] >= widgets[sId]["size"] then
1120 term.setTextColor(colors.lightGray)
1121 end
1122
1123 term.setCursorPos(widgetEndX, widgetEndY)
1124 term.write("+")
1125
1126 for i = 1, #widgets[sId]["linkedWidgets"] do
1127 widgetUpdate(widgets[sId]["linkedWidgets"][i])
1128 end
1129
1130 drawVerticalScrollbar(widgets[sId]["size"], sId, widgetStartY, widgetEndY, widgetLossTopY, widgetLossBotY)
1131 else
1132 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetEndY, oldColor)
1133 end
1134 else
1135 return false
1136 end
1137
1138 term.setBackgroundColor(oldColor)
1139 term.setTextColor(oldTextColor)
1140 return widgets[sId]
1141end
1142
1143function setProperties(sId, ...)
1144 local tArguments = {...}
1145
1146 if #tArguments % 2 == 1 then
1147 table.remove(tArguments, #tArguments)
1148 end
1149
1150 if widgets[sId] then
1151 local curX, curY = term.getCursorPos()
1152 local oldBgColor = term.getBackgroundColor()
1153 local oldTextColor = term.getTextColor()
1154
1155 local widgetStartX, widgetStartY, widgetEndX, widgetEndY = getWidgetHitbox(sId)
1156 local monX, monY = term.getSize()
1157
1158 if widgets[sId]["type"] == "widget_button" then
1159 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetEndY, term.getBackgroundColor())
1160 widgets[sId]["oldBtnColor"] = widgets[sId]["btnColor"]
1161 elseif widgets[sId]["type"] == "widget_progress" or widgets[sId]["type"] == "widget_progress_marquee" then
1162 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetStartY, term.getBackgroundColor())
1163 elseif widgets[sId]["type"] == "widget_label" then
1164 if sValueIndex == "text" and type(sNewValue) ~= "string" then
1165 if not tostring(sNewValue) then
1166 return false
1167 else
1168 sNewValue = tostring(sNewValue)
1169 end
1170 end
1171
1172 term.setCursorPos(widgetStartX, widgetStartY)
1173
1174 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetStartX + string.len(widgets[sId]["text"]), widgetStartY, widgets["window"]["bgColor"])
1175
1176 term.setCursorPos(curX, curY)
1177 elseif widgets[sId]["type"] == "widget_textbox" then
1178 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetStartY, term.getBackgroundColor())
1179 elseif widgets[sId]["type"] == "widget_switch" then
1180 paintutils.drawLine(widgetStartX, widgetStartY, widgetStartX + 4 + string.len(widgets[sId]["text"]), widgetStartY, oldColor)
1181 elseif widgets[sId]["type"] == "widget_numeric" then
1182 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, term.getBackgroundColor())
1183 elseif widgets[sId]["type"] == "widget_list" then
1184 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetEndY, term.getBackgroundColor())
1185 widgets[sId]["oldSelectedColor"] = widgets[sId]["selectedColor"]
1186 elseif widgets[sId]["type"] == "widget_numericslider" then
1187 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, term.getBackgroundColor())
1188 elseif widgets[sId]["type"] == "widget_scrollmanager" then
1189 paintutils.drawFilledBox(widgetStartX, widgetStartY, widgetEndX, widgetEndY, term.getBackgroundColor())
1190 end
1191
1192 for i = 1, #tArguments, 2 do
1193 if type(widgets[sId][tArguments[i]]) ~= "nil" then
1194 widgets[sId][tArguments[i]] = tArguments[i + 1]
1195 end
1196 end
1197
1198 widgetUpdate(sId)
1199
1200 term.setCursorPos(curX, curY)
1201 term.setTextColor(oldTextColor)
1202 term.setBackgroundColor(oldBgColor)
1203 else
1204 return false
1205 end
1206end
1207
1208function setProperty(sId, sValueIndex, sNewValue)
1209 return setProperties(sId, sValueIndex, sNewValue)
1210end
1211
1212function changeValue(sId, sValueIndex, sNewValue)
1213 return setProperty(sId, sValueIndex, sNewValue)
1214end
1215
1216function getProperty(sId, sValueIndex)
1217 if not widgets[sId] then
1218 return false, "Unknown widget"
1219 elseif not widgets[sId][sValueIndex] then
1220 return false, "Unknown property"
1221 end
1222
1223 return widgets[sId][sValueIndex]
1224end
1225
1226local function updateLinkedWidgets()
1227 local monX, monY = term.getSize()
1228
1229 for k, v in pairs(tabs) do
1230 if v["id"] ~= activeTabData["id"] and oldActiveTabData["id"] == v["id"] then
1231 for i = 1, #v["linkedWidgets"] do
1232 setProperties(v["linkedWidgets"][i], "startY", widgets[v["linkedWidgets"][i]]["startY"] + monY, "endY", widgets[v["linkedWidgets"][i]]["endY"] + monY)
1233
1234 if widgets[v["linkedWidgets"][i]]["linkedWidgets"] then
1235 for j = 1, #widgets[v["linkedWidgets"][i]]["linkedWidgets"] do
1236 setProperties(widgets[v["linkedWidgets"][i]]["linkedWidgets"][j], "startY", widgets[widgets[v["linkedWidgets"][i]]["linkedWidgets"][j]]["startY"] + monY, "endY", widgets[widgets[v["linkedWidgets"][i]]["linkedWidgets"][j]]["endY"] + monY)
1237 end
1238 end
1239 end
1240 end
1241 end
1242
1243 for i = 1, #activeTabData["linkedWidgets"] do
1244 setProperties(activeTabData["linkedWidgets"][i], "startY", widgets[activeTabData["linkedWidgets"][i]]["startY"] - monY, "endY", widgets[activeTabData["linkedWidgets"][i]]["endY"] - monY)
1245
1246 if widgets[activeTabData["linkedWidgets"][i]]["linkedWidgets"] then
1247 for j = 1, #widgets[activeTabData["linkedWidgets"][i]]["linkedWidgets"] do
1248 setProperties(widgets[activeTabData["linkedWidgets"][i]]["linkedWidgets"][j], "startY", widgets[widgets[activeTabData["linkedWidgets"][i]]["linkedWidgets"][j]]["startY"] - monY, "endY", widgets[widgets[activeTabData["linkedWidgets"][i]]["linkedWidgets"][j]]["endY"] - monY)
1249 end
1250 end
1251 end
1252end
1253
1254local function updateTabs()
1255 local oldColor = term.getBackgroundColor()
1256 local monX, monY = term.getSize()
1257 local isTabOnBottom = widgets["window"]["tabsBottom"]
1258 local tabsPosition = 2
1259
1260 if isTabOnBottom then
1261 tabsPosition = monY
1262 end
1263
1264 paintutils.drawLine(1, tabsPosition, monX, tabsPosition, widgets["window"]["bgColor"])
1265 if getTabPageCount() > 1 then
1266 if activeTabPage == 1 then
1267 paintutils.drawLine(monX - 1, tabsPosition, monX, tabsPosition, colors.blue)
1268 term.setCursorPos(monX - 1, tabsPosition)
1269 term.write(" >")
1270 elseif activeTabPage == getTabPageCount() then
1271 paintutils.drawLine(monX - 1, tabsPosition, monX, tabsPosition, colors.blue)
1272 term.setCursorPos(monX - 1, tabsPosition)
1273 term.write("< ")
1274 else
1275 paintutils.drawLine(monX - 1, tabsPosition, monX, tabsPosition, colors.blue)
1276 term.setCursorPos(monX - 1, tabsPosition)
1277 term.write("<>")
1278 end
1279 end
1280
1281 term.setBackgroundColor(widgets["window"]["titleBarColor"])
1282 term.setCursorPos(1, tabsPosition)
1283
1284 local activeTabPageIDs = tabsPage[activeTabPage]
1285 local tabBefore = getTabPageCountBeforeActive()
1286
1287 for i = 1, #activeTabPageIDs do
1288 if activeTab == tabBefore + i then
1289 term.setBackgroundColor(widgets["window"]["bgColor"])
1290 term.setTextColor(colors.yellow)
1291
1292 activeTabData = tabs[activeTabPageIDs[i]]
1293 end
1294 write(" "..tabs[activeTabPageIDs[i]]["name"].." ")
1295 if activeTab == tabBefore + i then
1296 term.setBackgroundColor(widgets["window"]["titleBarColor"])
1297 term.setTextColor(colors.white)
1298 end
1299 end
1300
1301 term.setBackgroundColor(oldColor)
1302 updateLinkedWidgets()
1303end
1304
1305local function updateTabManager()
1306 local oldColor = term.getBackgroundColor()
1307 local monX, monY = term.getSize()
1308
1309 tabsPage = {}
1310 tabsPage[1] = {}
1311
1312 local copyTabs = tabs
1313 local tabOrder = {}
1314 local orderedTabs = {}
1315 local actualPage = 1
1316 local charNumber = 0
1317
1318 for k, v in pairs(tabs) do
1319 table.insert(tabOrder, v["position"])
1320 end
1321 table.sort(tabOrder)
1322
1323 for k, v in ipairs(tabOrder) do
1324 for k1, v1 in pairs(copyTabs) do
1325 if v1["position"] == v then
1326 table.insert(orderedTabs, v1)
1327 break
1328 end
1329 end
1330 end
1331
1332 for k, v in pairs(orderedTabs) do
1333 local newCharNumber = charNumber + v["name"]:len() + 2
1334
1335 if newCharNumber <= monX - 2 then
1336 table.insert(tabsPage[actualPage], v["id"])
1337 charNumber = newCharNumber
1338 else
1339 actualPage = actualPage + 1
1340 tabsPage[actualPage] = {}
1341 table.insert(tabsPage[actualPage], v["id"])
1342 charNumber = v["name"]:len() + 2
1343 end
1344 end
1345
1346 if activeTabPage > actualPage then
1347 activeTabPage = actualPage
1348 end
1349
1350 if activeTab == 0 and getTabCount() > 0 then
1351 activeTab = 1
1352 end
1353
1354 updateTabs()
1355
1356 term.setBackgroundColor(oldColor)
1357end
1358
1359function getTabCount()
1360 local tabCount = 0
1361
1362 for k in pairs(tabs) do
1363 tabCount = tabCount + 1
1364 end
1365
1366 return tabCount
1367end
1368
1369function getTabPageCount()
1370 return #tabsPage
1371end
1372
1373function getTabPageCountBeforeActive()
1374 local tabBefore = 0
1375
1376 for i = 1, activeTabPage - 1 do
1377 tabBefore = tabBefore + #tabsPage[i]
1378 end
1379
1380 return tabBefore
1381end
1382
1383function linkToTab(tabId, tWidget)
1384 if not tabId or not tabs[tabId] then
1385 return false, "Unknown Tab"
1386 end
1387
1388 if type(tWidget) ~= "table" then
1389 return false, "Widget must be a table"
1390 end
1391
1392 local widgetId = tWidget["id"]
1393 local monX, monY = term.getSize()
1394
1395 widgets[widgetId] = tWidget
1396 table.insert(tabs[tabId]["linkedWidgets"], widgetId)
1397
1398 if activeTabData["id"] ~= tabId then
1399 if widgets[widgetId]["startY"] then
1400 widgets[widgetId]["startY"] = widgets[widgetId]["startY"] + monY
1401 end
1402 if widgets[widgetId]["endY"] then
1403 widgets[widgetId]["endY"] = widgets[widgetId]["endY"] + monY
1404 end
1405 end
1406
1407 widgets[widgetId]["tab"] = tabId
1408 setProperty(widgetId, "enabled", true)
1409
1410 return true
1411end
1412
1413function unlinkFromTab(id)
1414 if not id or not widgets[id] or not widgets[id]["tab"] then
1415 return false
1416 end
1417
1418 for i = 1, #tabs[widgets[id]["tab"]]["linkedWidgets"] do
1419 if tabs[widgets[id]["tab"]]["linkedWidgets"][i] == id then
1420 table.remove(tabs[widgets[id]["tab"]]["linkedWidgets"], i)
1421 break
1422 end
1423 end
1424
1425 widgets[id]["tab"] = nil
1426end
1427
1428function addTab(id, sName, nPosition)
1429 if not sName then
1430 return false
1431 end
1432
1433 local nPosition = nPosition or getTabCount() + 1
1434
1435 tabs[id] = {}
1436 tabs[id]["name"] = sName
1437 tabs[id]["position"] = nPosition
1438 tabs[id]["id"] = id
1439 tabs[id]["linkedWidgets"] = {}
1440
1441 updateTabManager()
1442 return tabs[id]
1443end
1444
1445function removeTab(id)
1446 if not id or not tabs[id] then
1447 return false
1448 end
1449
1450 if tabs[id]["position"] < activeTabData["position"] then
1451 activeTab = activeTab - 1
1452 elseif tabs[id]["position"] == activeTabData["position"] then
1453 activeTab = 1
1454 end
1455
1456 tabs[id] = nil
1457
1458 updateTabManager()
1459 return true
1460end
1461
1462function deleteAllTabs()
1463 lastSessionTabs = tabs
1464 tabs = {}
1465end
1466
1467function deleteWidget(id)
1468 if widgets[id] then
1469 setProperty(id, "enabled", false)
1470
1471 if widgets[id]["scrollManager"] and widgets[widgets[id]["scrollManager"]] then
1472 for i = 1, #widgets[widgets[id]["scrollManager"]]["linkedWidgets"] do
1473 if widgets[widgets[id]["scrollManager"]]["linkedWidgets"][i] == id then
1474 table.remove(widgets[widgets[id]["scrollManager"]]["linkedWidgets"], i)
1475 break
1476 end
1477 end
1478 end
1479
1480 widgets[id] = nil
1481 end
1482end
1483
1484function deleteAllWidgets()
1485 lastSessionWidgets = widgets
1486 widgets = {}
1487end
1488
1489function getWidgetList()
1490 local widgetList = {}
1491
1492 for k, v in pairs(widgets) do
1493 widgetList[k] = v["type"]
1494 end
1495
1496 return widgetList
1497end
1498
1499function getWidgetListByType(sType)
1500 if not sType then
1501 return false
1502 end
1503
1504 local widgetList = {}
1505
1506 for k, v in pairs(widgets) do
1507 if v["type"] == sType then
1508 table.insert(widgetList, k)
1509 end
1510 end
1511
1512 return widgetList
1513end
1514
1515function drawWindow(sTitle, bCloseButton, bNoAnimation, titleBarColor, bgColor, tabsBottom)
1516 local bDefaultCloseButton = true
1517 local sDefaultWindowTitle = "WindowTitle"
1518
1519 if widgets["window"] then
1520 bDefaultCloseButton = widgets["window"]["enabled"]
1521 sDefaultWindowTitle = widgets["window"]["title"]
1522 end
1523
1524 if type(bCloseButton) == "nil" then
1525 bCloseButton = bDefaultCloseButton
1526 end
1527
1528 local sWindowTitle = sTitle or sDefaultWindowTitle
1529 local monX, monY = term.getSize()
1530 local titleBarColor = titleBarColor or colors.gray
1531 local bgColor = bgColor or colors.lightGray
1532 local tabsBottom = tabsBottom or false
1533
1534 term.setBackgroundColor(colors.black)
1535 term.clear()
1536 paintutils.drawLine(1, 1, monX, 1, titleBarColor)
1537 term.setCursorPos(1, 1)
1538 term.write(sWindowTitle)
1539
1540 widgets["window"] = {}
1541 widgets["window"]["startX"] = monX
1542 widgets["window"]["startY"] = 1
1543 widgets["window"]["endX"] = monX
1544 widgets["window"]["endY"] = 1
1545 widgets["window"]["btnColor"] = colors.red
1546 widgets["window"]["text"] = "X"
1547 widgets["window"]["title"] = sWindowTitle
1548 widgets["window"]["titleBarColor"] = titleBarColor
1549 widgets["window"]["bgColor"] = bgColor
1550 widgets["window"]["tabsBottom"] = tabsBottom
1551 widgets["window"]["type"] = "widget_close_button"
1552
1553 if bCloseButton == nil or bCloseButton == true then
1554 paintutils.drawPixel(monX, 1, colors.red)
1555 term.setCursorPos(monX, 1)
1556 term.write("X")
1557 widgets["window"]["enabled"] = true
1558 else
1559 widgets["window"]["enabled"] = false
1560 end
1561
1562 if not bNoAnimation then
1563 local animationSpeed = math.ceil((monY - 1) * (0.05 / 0.3))
1564 for i = 2, monY, animationSpeed do
1565 paintutils.drawFilledBox(1, i, monX, i + (animationSpeed - 1), bgColor)
1566 sleep(0.05)
1567 end
1568 else
1569 paintutils.drawFilledBox(1, 2, monX, monY, bgColor)
1570 end
1571
1572 term.setCursorPos(1, 2)
1573 term.setBackgroundColor(bgColor)
1574
1575 for k, v in pairs(widgets) do
1576 widgetUpdate(k)
1577 end
1578
1579 if getTabCount() > 0 then
1580 updateTabs()
1581 end
1582
1583 return true
1584end
1585
1586function redrawWindow()
1587 return drawWindow(nil, nil, true)
1588end
1589
1590function closeWindow(bNoAnim)
1591 local monX, monY = term.getSize()
1592
1593 lastSessionWidgets = widgets
1594 lastSessionTabs = tabs
1595
1596 if not bNoAnim then
1597 local animationSpeed = 0 - math.ceil((monY - 1) * (0.05 / 0.3))
1598 for i = monY, 2, animationSpeed do
1599 paintutils.drawFilledBox(1, i, monX, i - (animationSpeed + 1), colors.black)
1600 sleep(0.01)
1601 end
1602 paintutils.drawLine(1, 3, monX, 3, colors.black)
1603
1604 paintutils.drawLine(1, 2, monX, 2, colors.black)
1605 sleep(0.2)
1606 end
1607
1608 term.setBackgroundColor(colors.black)
1609 term.clear()
1610 term.setCursorPos(1, 1)
1611
1612 deleteAllWidgets()
1613 deleteAllTabs()
1614 bRun = false
1615end
1616
1617function linkToScrollManager(sScrollManagerId, tWidget)
1618 if not widgets[sScrollManagerId] or widgets[sScrollManagerId]["type"] ~= "widget_scrollmanager" then
1619 return false, "Unknown ScrollManager"
1620 end
1621
1622 if type(tWidget) ~= "table" then
1623 return false, "Widget must be a table"
1624 end
1625
1626 local widgetId = tWidget["id"]
1627 local monX, monY = term.getSize()
1628 local addX = widgets[sScrollManagerId]["startX"] - 1
1629 local addY = widgets[sScrollManagerId]["startY"] - 1
1630
1631 if widgets[sScrollManagerId]["size"] < tWidget["endY"] and widgets[sScrollManagerId]["autosize"] then
1632 widgets[sScrollManagerId]["size"] = tWidget["endY"]
1633 widgetUpdate(sScrollManagerId)
1634 end
1635
1636 widgets[widgetId] = tWidget
1637 table.insert(widgets[sScrollManagerId]["linkedWidgets"], widgetId)
1638
1639 if widgets[widgetId]["startX"] then
1640 widgets[widgetId]["startX"] = widgets[widgetId]["startX"] + addX
1641 end
1642 if widgets[widgetId]["startY"] then
1643 widgets[widgetId]["startY"] = widgets[widgetId]["startY"] + addY
1644
1645 if widgets[widgetId]["startY"] > widgets[sScrollManagerId]["endY"] then
1646 widgets[widgetId]["startY"] = widgets[widgetId]["startY"] + (monY - widgets[sScrollManagerId]["endY"])
1647 end
1648 end
1649 if widgets[widgetId]["endX"] then
1650 widgets[widgetId]["endX"] = widgets[widgetId]["endX"] + addX
1651 end
1652 if widgets[widgetId]["endY"] then
1653 widgets[widgetId]["endY"] = widgets[widgetId]["endY"] + addY
1654
1655 if widgets[widgetId]["endY"] > widgets[sScrollManagerId]["endY"] and widgets[widgetId]["startY"] > widgets[sScrollManagerId]["endY"] then
1656 widgets[widgetId]["endY"] = widgets[widgetId]["endY"] + (monY - widgets[sScrollManagerId]["endY"])
1657 end
1658 end
1659
1660 widgets[widgetId]["scrollManager"] = sScrollManagerId
1661 setProperty(widgetId, "enabled", true)
1662
1663 return true
1664end
1665
1666function autoResizeScrollManager(sScrollManagerId)
1667 if not sScrollManagerId or not widgets[sScrollManagerId] or widgets[sScrollManagerId]["type"] ~= "widget_scrollmanager" then
1668 return false, "Unknown ScrollManager"
1669 end
1670
1671 local maxEndY = 0
1672 local widgetStartX, widgetStartY, widgetEndX, widgetEndY = getWidgetHitbox(sScrollManagerId)
1673 local monX, monY = term.getSize()
1674
1675 local scrollManagerTopPadding = widgetStartY - 1
1676 local scrollManagerBotPadding = monY - widgetEndY
1677 if scrollManagerTopPadding < 0 then
1678 scrollManagerTopPadding = 0
1679 end
1680 if scrollManagerBotPadding < 0 then
1681 scrollManagerBotPadding = 0
1682 end
1683
1684 for i = 1, #widgets[sScrollManagerId]["linkedWidgets"] do
1685 local currentWidgetEndY = widgets[widgets[sScrollManagerId]["linkedWidgets"][i]]["endY"]
1686
1687 if currentWidgetEndY < 1 then
1688 currentWidgetEndY = currentWidgetEndY + scrollManagerTopPadding
1689 end
1690 if currentWidgetEndY > monY then
1691 currentWidgetEndY = currentWidgetEndY - scrollManagerBotPadding
1692 end
1693
1694 if currentWidgetEndY + widgets[sScrollManagerId]["valuesShownMin"] - 1 > maxEndY then
1695 maxEndY = currentWidgetEndY + widgets[sScrollManagerId]["valuesShownMin"] - 1
1696 end
1697 end
1698
1699 maxEndY = maxEndY - scrollManagerTopPadding
1700
1701 if maxEndY < widgetEndY - widgetStartY + 1 then
1702 maxEndY = widgetEndY - widgetStartY + 1
1703 end
1704
1705 widgets[sScrollManagerId]["size"] = maxEndY
1706
1707 if widgets[sScrollManagerId]["valuesShownMax"] > maxEndY then
1708 scrollScrollManagerTo(widgets[sScrollManagerId]["size"] - widgets[sScrollManagerId]["height"], sScrollManagerId)
1709 end
1710
1711 widgetUpdate(sScrollManagerId)
1712end
1713
1714function createButton(id, startX, startY, endX, endY, btnColor, sText, bAnim)
1715 -- Ensure backward compatibility with older version of arcUI
1716 if (type(sText) == "number" or not sText) and type(btnColor) == "string" then
1717 sText, btnColor = reverseTwoArgs(sText, btnColor)
1718 end
1719
1720 -- Ensure types of args
1721 id = tostringn(id)
1722 startX = math.floor(tonumber(startX))
1723 startY = math.floor(tonumber(startY))
1724 endX = math.floor(tonumber(endX))
1725 endY = math.floor(tonumber(endY))
1726 btnColor = tonumber(btnColor)
1727 sText = tostringn(sText)
1728
1729 -- Check boolean args
1730 if bAnim == nil then
1731 bAnim = true
1732 end
1733
1734 -- Check required and optionals args
1735 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", endX, "number", endY, "number", sText, "string")
1736 if not bValidArgs then
1737 return false, sErrMsg
1738 end
1739
1740 bValidArgs, sErrMsg = checkArguments(false, btnColor, "number", bAnim, "boolean")
1741 if not bValidArgs then
1742 return false, sErrMsg
1743 end
1744
1745 -- Creating the widget
1746 local tWidget = {}
1747 tWidget["startX"] = startX
1748 tWidget["startY"] = startY
1749 tWidget["endX"] = endX
1750 tWidget["endY"] = endY
1751 tWidget["height"] = endY - startY
1752 tWidget["btnColor"] = btnColor or colors.blue
1753 tWidget["text"] = sText
1754 tWidget["enabled"] = false
1755 tWidget["type"] = "widget_button"
1756 tWidget["oldBtnColor"] = btnColor
1757 tWidget["animation"] = bAnim
1758 tWidget["id"] = id
1759
1760 -- Returning the widget
1761 return tWidget
1762end
1763
1764function createProgress(id, startX, startY, endX, maxValue, value)
1765 -- Ensure types of args
1766 id = tostringn(id)
1767 startX = math.floor(tonumber(startX))
1768 startY = math.floor(tonumber(startY))
1769 endX = math.floor(tonumber(endX))
1770 maxValue = tonumber(maxValue)
1771 value = tonumber(value)
1772
1773 -- Check required args
1774 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", endX, "number", maxValue, "number", value, "number")
1775 if not bValidArgs then
1776 return false, sErrMsg
1777 end
1778
1779 -- Creating the widget
1780 tWidget = {}
1781 tWidget["startX"] = startX
1782 tWidget["startY"] = startY
1783 tWidget["endX"] = endX
1784 tWidget["endY"] = startY
1785 tWidget["height"] = 0
1786 tWidget["value"] = value
1787 tWidget["maxValue"] = maxValue
1788 tWidget["anim_health"] = 50
1789 tWidget["anim_pixel"] = startX
1790 tWidget["enabled"] = false
1791 tWidget["type"] = "widget_progress"
1792 tWidget["width"] = endX - startX
1793 tWidget["id"] = id
1794
1795 -- Returning the widget
1796 return tWidget
1797end
1798
1799function createMarqueeProgress(id, startX, startY, endX)
1800 -- Ensure types of args
1801 id = tostringn(id)
1802 startX = math.floor(tonumber(startX))
1803 startY = math.floor(tonumber(startY))
1804 endX = math.floor(tonumber(endX))
1805
1806 -- Check required args
1807 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", endX, "number")
1808 if not bValidArgs then
1809 return false, sErrMsg
1810 end
1811
1812 -- Creating the widget
1813 tWidget = {}
1814 tWidget["startX"] = startX
1815 tWidget["startY"] = startY
1816 tWidget["endX"] = endX
1817 tWidget["endY"] = startY
1818 tWidget["height"] = 0
1819 tWidget["enabled"] = false
1820 tWidget["type"] = "widget_progress_marquee"
1821 tWidget["width"] = endX - startX
1822 tWidget["minGreen"] = startX
1823 tWidget["maxGreen"] = startX + 2
1824 tWidget["animDirection"] = "+"
1825 tWidget["id"] = id
1826
1827 -- Returning the widget
1828 return tWidget
1829end
1830
1831function createLabel(id, startX, startY, sText, bgColor, textColor)
1832 -- Ensure types of args
1833 id = tostringn(id)
1834 startX = tonumber(startX)
1835 startY = tonumber(startY)
1836 sText = tostringn(sText)
1837 bgColor = tonumber(bgColor)
1838 textColor = tonumber(bgColor)
1839
1840 -- Check required and optionals args
1841 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", sText, "string")
1842 if not bValidArgs then
1843 return false, sErrMsg
1844 end
1845
1846 bValidArgs, sErrMsg = checkArguments(false, bgColor, "number", textColor, "number")
1847 if not bValidArgs then
1848 return false, sErrMsg
1849 end
1850
1851 -- Creting the widget
1852 tWidget = {}
1853 tWidget["startX"] = startX
1854 tWidget["startY"] = startY
1855 tWidget["endX"] = startX
1856 tWidget["endY"] = startY
1857 tWidget["height"] = 0
1858 tWidget["text"] = sText
1859 tWidget["bgColor"] = bgColor or term.getBackgroundColor()
1860 tWidget["textColor"] = textColor or colors.white
1861 tWidget["enabled"] = false
1862 tWidget["type"] = "widget_label"
1863 tWidget["id"] = id
1864
1865 -- Returning the widget
1866 return tWidget
1867end
1868
1869function createTextbox(id, startX, startY, endX, placeholder, charReplace, bHistory, tHistory, tCompletion, inactiveColor, activeColor, textInactiveColor, textActiveColor, bNumericOnly)
1870 -- Ensure types of args
1871 id = tostringn(id)
1872 startX = math.floor(tonumber(startX))
1873 startY = math.floor(tonumber(startY))
1874 endX = math.floor(tonumber(endX))
1875 placeholder = tostringn(placeholder)
1876 charReplace = tostringn(charReplace)
1877 inactiveColor = tonumber(inactiveColor)
1878 activeColor = tonumber(activeColor)
1879 textInactiveColor = tonumber(textInactiveColor)
1880 textActiveColor = tonumber(textActiveColor)
1881
1882 -- Check boolean args
1883 if bHistory == nil then
1884 bHistory = false
1885 end
1886
1887 if bNumericOnly == nil then
1888 bNumericOnly = false
1889 end
1890
1891 -- Check required and optionals args
1892 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", endX, "number")
1893 if not bValidArgs then
1894 return false, sErrMsg
1895 end
1896
1897 bValidArgs, sErrMsg = checkArguments(false, placeholder, "string", charReplace, "string", bHistory, "boolean", tHistory, "table", tCompletion, "table", inactiveColor, "number", activeColor, "number", textInactiveColor, "number", textActiveColor, "number", bNumericOnly, "boolean")
1898 if not bValidArgs then
1899 return false, sErrMsg
1900 end
1901
1902 -- Creating the widget
1903 tWidget = {}
1904 tWidget["startX"] = startX
1905 tWidget["startY"] = startY
1906 tWidget["endX"] = endX
1907 tWidget["endY"] = startY
1908 tWidget["height"] = 0
1909 tWidget["width"] = endX - startX
1910 tWidget["text"] = placeholder
1911 tWidget["value"] = ""
1912 tWidget["charReplace"] = charReplace
1913 tWidget["bHistory"] = bHistory
1914 tWidget["tHistory"] = tHistory or {}
1915 tWidget["tCompletion"] = tCompletion or {}
1916 tWidget["inactiveColor"] = inactiveColor or colors.cyan
1917 tWidget["activeColor"] = activeColor or colors.green
1918 tWidget["textInactiveColor"] = textInactiveColor or colors.gray
1919 tWidget["textActiveColor"] = textActiveColor or colors.white
1920 tWidget["numericOnly"] = bNumericOnly
1921 tWidget["enabled"] = false
1922 tWidget["type"] = "widget_textbox"
1923 tWidget["id"] = id
1924
1925 -- Returning the widget
1926 return tWidget
1927end
1928
1929function createSwitch(id, startX, startY, sText, bState)
1930 -- Ensure types of args
1931 id = tostringn(id)
1932 startX = math.floor(tonumber(startX))
1933 startY = math.floor(tonumber(startY))
1934 sText = tostringn(sText)
1935
1936 -- Check boolean args
1937 if bState == nil then
1938 bState = false
1939 end
1940
1941 -- Check required and optionals args
1942 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", sText, "string")
1943 if not bValidArgs then
1944 return false, sErrMsg
1945 end
1946
1947 bValidArgs, sErrMsg = checkArguments(false, bState, "boolean")
1948 if not bValidArgs then
1949 return false, sErrMsg
1950 end
1951
1952 -- Creating the widget
1953 tWidget = {}
1954 tWidget["startX"] = startX
1955 tWidget["startY"] = startY
1956 tWidget["endX"] = startX
1957 tWidget["endY"] = startY
1958 tWidget["height"] = 0
1959 tWidget["text"] = sText
1960 tWidget["enabled"] = false
1961 tWidget["state"] = bState
1962 tWidget["type"] = "widget_switch"
1963 tWidget["id"] = id
1964
1965 -- Returning the widget
1966 return tWidget
1967end
1968
1969function createNumeric(id, startX, startY, endX, value, minValue, maxValue, increment)
1970 -- Ensure type of args
1971 id = tostringn(id)
1972 startX = math.floor(tonumber(startX))
1973 startY = math.floor(tonumber(startY))
1974 endX = math.floor(tonumber(endX))
1975 value = tonumber(value)
1976 minValue = tonumber(minValue)
1977 maxValue = tonumber(maxValue)
1978 increment = tonumber(increment)
1979
1980 -- Check required and optionals args
1981 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", endX, "number", value, "number", minValue, "number", maxValue, "number")
1982 if not bValidArgs then
1983 return false, sErrMsg
1984 end
1985
1986 bValidArgs, sErrMsg = checkArguments(false, increment, "number")
1987 if not bValidArgs then
1988 return false, sErrMsg
1989 end
1990
1991 -- Check widget bounds
1992 if value > maxValue or value < minValue then
1993 return false, "Value must be between maxValue and minValue"
1994 end
1995
1996 -- Creating the widget
1997 tWidget = {}
1998 tWidget["startX"] = startX
1999 tWidget["startY"] = startY
2000 tWidget["endX"] = endX
2001 tWidget["endY"] = startY
2002 tWidget["height"] = 0
2003 tWidget["width"] = endX - startX
2004 tWidget["value"] = value
2005 tWidget["minValue"] = minValue
2006 tWidget["maxValue"] = maxValue
2007 tWidget["increment"] = increment or 1
2008 tWidget["activeColor"] = colors.blue
2009 tWidget["numericOnly"] = true
2010 tWidget["enabled"] = false
2011 tWidget["type"] = "widget_numeric"
2012 tWidget["id"] = id
2013
2014 -- Returning the widget
2015 return tWidget
2016end
2017
2018function createList(id, startX, startY, endX, endY, tValues, nSelectedColor, nTextColor, nBGColor, nSBGColor)
2019 -- Ensure type of args
2020 id = tostringn(id)
2021 startX = math.floor(tonumber(startX))
2022 startY = math.floor(tonumber(startY))
2023 endX = math.floor(tonumber(endX))
2024 endY = math.floor(tonumber(endY))
2025 nSelectedColor = tonumber(nSelectedColor)
2026 nTextColor = tonumber(nTextColor)
2027 nBGColor = tonumber(nBGColor)
2028 nSBGColor = tonumber(nSBGColor)
2029
2030 -- Check required and optionals args
2031 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", endX, "number", endY, "number", tValues, "table")
2032 if not bValidArgs then
2033 return false, sErrMsg
2034 end
2035
2036 bValidArgs, sErrMsg = checkArguments(false, nSelectedColor, "number", nTextColor, "number", nBGColor, "number", nSBGColor, "number")
2037 if not bValidArgs then
2038 return false, sErrMsg
2039 end
2040
2041 -- Creating the widget
2042 tWidget = {}
2043 tWidget["startX"] = startX
2044 tWidget["startY"] = startY
2045 tWidget["endX"] = endX
2046 tWidget["endY"] = endY
2047 tWidget["height"] = endY - startY
2048 tWidget["values"] = tValues
2049 tWidget["valuesShown"] = {}
2050 tWidget["valuesShownMin"] = 1
2051 tWidget["valuesShownMax"] = 0
2052 tWidget["selectedValue"] = 0
2053 tWidget["selectedColor"] = nSelectedColor or colors.blue
2054 tWidget["oldSelectedColor"] = colors.lightBlue
2055 tWidget["textColor"] = nTextColor or colors.white
2056 tWidget["backgroundColor"] = nBGColor or colors.lightBlue
2057 tWidget["secondBackgroundColor"] = nSBGColor or nBGColor or colors.lightBlue
2058 tWidget["enabled"] = false
2059 tWidget["type"] = "widget_list"
2060 tWidget["id"] = id
2061
2062 -- Returning the widget
2063 return tWidget
2064end
2065
2066function createNumericSlider(id, startX, startY, endX, value, minValue, maxValue, sliderColor)
2067 -- Ensure types of args
2068 id = tostringn(id)
2069 startX = math.floor(tonumber(startX))
2070 startY = math.floor(tonumber(startY))
2071 endX = math.floor(tonumber(endX))
2072 value = tonumber(value)
2073 minValue = tonumber(minValue)
2074 maxValue = tonumber(maxValue)
2075 sliderColor = tonumber(sliderColor)
2076
2077 -- Check required and optionals args
2078 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", endX, "number")
2079 if not bValidArgs then
2080 return false, sErrMsg
2081 end
2082
2083 bValidArgs, sErrMsg = checkArguments(false, value, "number", minValue, "number", maxValue, "number", sliderColor, "number")
2084 if not bValidArgs then
2085 return false, sErrMsg
2086 end
2087
2088 -- Creating the widget
2089 tWidget = {}
2090 tWidget["startX"] = startX
2091 tWidget["startY"] = startY
2092 tWidget["endX"] = endX
2093 tWidget["endY"] = startY
2094 tWidget["height"] = 0
2095 tWidget["value"] = value or 0
2096 tWidget["minValue"] = minValue or 0
2097 tWidget["maxValue"] = maxValue or 100
2098 tWidget["sliderColor"] = sliderColor or colors.gray
2099 tWidget["enabled"] = false
2100 tWidget["type"] = "widget_numericslider"
2101 tWidget["id"] = id
2102
2103 -- Returning the widget
2104 return tWidget
2105end
2106
2107function createScrollManager(id, startX, startY, endX, endY, autoSize, size)
2108 -- Ensure types of args
2109 id = tostringn(id)
2110 startX = math.floor(tonumber(startX))
2111 startY = math.floor(tonumber(startY))
2112 endX = math.floor(tonumber(endX))
2113 endY = math.floor(tonumber(endY))
2114 size = tonumber(size)
2115
2116 -- Check boolean args
2117 if autoSize == nil then
2118 autoSize = true
2119 end
2120
2121 -- Check required and optionals args
2122 local bValidArgs, sErrMsg = checkArguments(true, id, "string", startX, "number", startY, "number", endX, "number", endY, "number", autoSize, "boolean", size, "number")
2123 if not bValidArgs then
2124 return false, sErrMsg
2125 end
2126
2127 -- Check widget bounds
2128 if autoSize == true or size < endY - startY + 1 then
2129 size = endY - startY + 1
2130 end
2131
2132 -- Creating the widget
2133 tWidget = {}
2134 tWidget["startX"] = startX
2135 tWidget["startY"] = startY
2136 tWidget["endX"] = endX
2137 tWidget["endY"] = endY
2138 tWidget["height"] = endY - startY
2139 tWidget["size"] = size
2140 tWidget["autosize"] = autoSize
2141 tWidget["valuesShownMin"] = 1
2142 tWidget["valuesShownMax"] = 0
2143 tWidget["linkedWidgets"] = {}
2144 tWidget["enabled"] = false
2145 tWidget["type"] = "widget_scrollmanager"
2146 tWidget["id"] = id
2147
2148 -- Returning the widget
2149 return tWidget
2150end
2151
2152local function drawGenericWidget(tWidget, errMsg)
2153 if type(tWidget) == "table" then
2154 widgets[tWidget["id"]] = tWidget
2155 setProperty(tWidget["id"], "enabled", true)
2156
2157 return true
2158 else
2159 return tWidget, errMsg
2160 end
2161end
2162
2163function drawButton(...)
2164 return drawGenericWidget(createButton(...))
2165end
2166
2167function drawProgress(...)
2168 return drawGenericWidget(createProgress(...))
2169end
2170
2171function drawMarqueeProgress(...)
2172 return drawGenericWidget(createMarqueeProgress(...))
2173end
2174
2175function drawLabel(...)
2176 return drawGenericWidget(createLabel(...))
2177end
2178
2179function drawTextbox(...)
2180 return drawGenericWidget(createTextbox(...))
2181end
2182
2183function drawSwitch(...)
2184 return drawGenericWidget(createSlider(...))
2185end
2186
2187function drawNumeric(...)
2188 return drawGenericWidget(createNumeric(...))
2189end
2190
2191function drawList(...)
2192 return drawGenericWidget(createList(...))
2193end
2194
2195function drawNumericSlider(...)
2196 return drawGenericWidget(createNumericSlider(...))
2197end
2198
2199function drawScrollManager(...)
2200 return drawGenericWidget(createScrollManager(...))
2201end
2202
2203function createSlider(...)
2204 error("arcUI:\nSliders have been renamed to Switches.\nPlease update your function calls and event filters accordingly.\nCheck the documentation for more details.", 0)
2205end
2206
2207function drawSlider(...)
2208 createSlider(...)
2209end
2210
2211function openDialog(id, sTitle, sText, sLeftButtonText, sRightButtonText, bgColor, textColor, leftButtonColor, rightButtonColor, leftButtonTextColor, rightButtonTextColor, bHideCancelButton)
2212 if not sText then
2213 return false
2214 end
2215
2216 local sLeftButtonText = sLeftButtonText or "Cancel"
2217 local sRightButtonText = sRightButtonText or "OK"
2218 local bgColor = bgColor or colors.white
2219 local textColor = textColor or colors.black
2220 local leftButtonColor = leftButtonColor or colors.gray
2221 local rightButtonColor = rightButtonColor or colors.gray
2222 local leftButtonTextColor = leftButtonTextColor or colors.lightGray
2223 local rightButtonTextColor = rightButtonTextColor or colors.lightBlue
2224 local hideCancelButton = false
2225
2226 if bHideCancelButton == true then
2227 hideCancelButton = true
2228 end
2229
2230 local results = false
2231 local click = {}
2232 bDialogRunning = true
2233 bCanButtonAnim = false
2234
2235 local monX, monY = term.getSize()
2236
2237 local dialogWindow = window.create(term.current(), 2, monY + 1, monX - 2, math.ceil(monY / 2))
2238 local oldTarget = term.redirect(dialogWindow)
2239
2240 local dialogX, dialogY = term.getSize()
2241
2242 term.setBackgroundColor(bgColor)
2243 term.clear()
2244 term.setTextColor(textColor)
2245
2246 term.setCursorPos(dialogX / 2 - string.len(sTitle) / 2, 1)
2247 term.write(sTitle)
2248 term.setCursorPos(2, 3)
2249 print(sText)
2250
2251 if not hideCancelButton then
2252 paintutils.drawFilledBox(1, dialogY - 1, math.floor(dialogX / 2) - 1, dialogY, leftButtonColor)
2253 paintutils.drawFilledBox(math.floor(dialogX / 2) + 1, dialogY - 1, dialogX, dialogY, rightButtonColor)
2254
2255 term.setBackgroundColor(leftButtonColor)
2256 term.setTextColor(leftButtonTextColor)
2257 term.setCursorPos((dialogX / 4) - (string.len(sLeftButtonText) / 2), dialogY)
2258 term.write(sLeftButtonText)
2259
2260 term.setBackgroundColor(rightButtonColor)
2261 term.setTextColor(rightButtonTextColor)
2262 term.setCursorPos(math.ceil(dialogX * 3 / 4) - (string.len(sRightButtonText) / 2), dialogY)
2263 term.write(sRightButtonText)
2264 else
2265 paintutils.drawFilledBox(1, dialogY - 1, dialogX, dialogY, rightButtonColor)
2266
2267 term.setBackgroundColor(rightButtonColor)
2268 term.setTextColor(rightButtonTextColor)
2269 term.setCursorPos(math.ceil(dialogX / 2) - (string.len(sRightButtonText) / 2), dialogY)
2270 term.write(sRightButtonText)
2271 end
2272
2273 for i = monY, monY / 2, -2 do
2274 dialogWindow.reposition(2, i)
2275 sleep(0.01)
2276 end
2277 if monY % 2 == 1 then
2278 dialogWindow.reposition(2, math.ceil(monY / 2))
2279 else
2280 dialogWindow.reposition(2, math.ceil(monY / 2) + 1)
2281 end
2282
2283 local windowPosX, windowPosY = dialogWindow.getPosition()
2284 while true do
2285 local event, button, mousex, mousey = os.pullEvent("mouse_click")
2286
2287 if mousex and mousey then
2288 mousex = mousex - windowPosX + 1
2289 mousey = mousey - windowPosY + 1
2290
2291 if mousex >= 1 and mousex < math.floor(dialogX / 2) and mousey >= dialogY - 1 and mousey <= dialogY and not hideCancelButton then
2292 results = false
2293 click = {mousex, mousey}
2294
2295 break
2296 elseif ((mousex >= 1 and hideCancelButton) or (mousex > math.floor(dialogX / 2) and not hideCancelButton)) and mousex <= dialogX and mousey >= dialogY - 1 and mousey <= dialogY then
2297 results = true
2298 click = {mousex, mousey}
2299
2300 break
2301 end
2302 end
2303 end
2304
2305 term.redirect(oldTarget)
2306 dialogWindow = nil
2307
2308 term.setTextColor(colors.white)
2309
2310 bDialogRunning = false
2311 bCanButtonAnim = true
2312
2313 if widgets["window"] then
2314 redrawWindow()
2315 end
2316
2317 return results, click
2318end
2319
2320local function completer(sText, sWidget)
2321 local tComplete = {}
2322
2323 for k, v in pairs(widgets[sWidget]["tCompletion"]) do
2324 if string.sub(v, 1, #sText) == sText then
2325 table.insert(tComplete, string.sub(v, #sText + 1, #v))
2326 end
2327 end
2328
2329 return tComplete
2330end
2331
2332local function scrollbarCompute(k, a1, a2, a3, widgetWorkingHeight)
2333 -- Scrollbar selected
2334 local selectedValue = a3 - widgets[k]["startY"]
2335 local navBarWorkingLength = (widgets[k]["endY"] - widgets[k]["startY"]) - 1
2336
2337 if selectedValue < 1 then
2338 selectedValue = 1
2339 elseif selectedValue > widgets[k]["height"] + 1 then
2340 selectedValue = widgets[k]["height"] + 1
2341 end
2342
2343 local indexSelectedValue = math.floor(selectedValue * widgetWorkingHeight / navBarWorkingLength)
2344 if selectedValue == 1 then
2345 indexSelectedValue = 1
2346 elseif selectedValue == navBarWorkingLength then
2347 indexSelectedValue = widgetWorkingHeight
2348 end
2349
2350 if indexSelectedValue > widgetWorkingHeight - math.floor((widgets[k]["endY"] - widgets[k]["startY"] + 1) / 2) then
2351 indexSelectedValue = widgetWorkingHeight - math.floor((widgets[k]["endY"] - widgets[k]["startY"] + 1) / 2)
2352 if (widgets[k]["endY"] - widgets[k]["startY"] + 1) % 2 == 0 then
2353 indexSelectedValue = indexSelectedValue + 1
2354 end
2355 end
2356
2357 local newvaluesShownMin = indexSelectedValue - math.floor((widgets[k]["endY"] - widgets[k]["startY"] + 1) / 2)
2358 if newvaluesShownMin < 1 then
2359 newvaluesShownMin = 1
2360 end
2361
2362 if newvaluesShownMin ~= widgets[k]["valuesShownMin"] then
2363 return newvaluesShownMin
2364 else
2365 return false
2366 end
2367end
2368
2369function scrollScrollManager(scrollSize, k)
2370 scrollSize = tonumber(scrollSize)
2371 k = tostring(k)
2372 if type(scrollSize) ~= "number" then return false, "Argument #1: Expected number, got "..type(scrollSize) end
2373 if type(k) ~= "string" then return false, "Argument #2: Expected string, got "..type(k) end
2374
2375 if not widgets[k] or widgets[k]["type"] ~= "widget_scrollmanager" then
2376 return false, "Unknown ScrollManager"
2377 end
2378
2379 local widgetStartX, widgetStartY, widgetEndX, widgetEndY, _, widgetLossTopY, widgetLossBotY = getWidgetHitbox(k)
2380 local monX, monY = term.getSize()
2381
2382 local scrollManagerTopPadding = widgetStartY - 1
2383 local scrollManagerBotPadding = monY - widgetEndY
2384
2385 if scrollSize < 0 then
2386 --Moins
2387 if widgets[k]["valuesShownMin"] + scrollSize < 1 then
2388 scrollSize = 1 - widgets[k]["valuesShownMin"]
2389 end
2390
2391 if widgets[k]["valuesShownMin"] > 1 then
2392 for i = 1, #widgets[k]["linkedWidgets"] do
2393 local actualWidgetStartY = widgets[widgets[k]["linkedWidgets"][i]]["startY"]
2394 local actualWidgetEndY = widgets[widgets[k]["linkedWidgets"][i]]["endY"]
2395 local computedWidgetStartY = actualWidgetStartY - scrollSize
2396 local computedWidgetEndY = actualWidgetEndY - scrollSize
2397
2398 if actualWidgetEndY < 1 and computedWidgetEndY >= 1 then
2399 computedWidgetStartY = computedWidgetStartY + scrollManagerTopPadding
2400 computedWidgetEndY = computedWidgetEndY + scrollManagerTopPadding
2401 end
2402 if actualWidgetStartY <= widgetEndY and computedWidgetStartY > widgetEndY then
2403 computedWidgetStartY = computedWidgetStartY + scrollManagerBotPadding
2404 computedWidgetEndY = computedWidgetEndY + scrollManagerBotPadding
2405 end
2406
2407 widgets[widgets[k]["linkedWidgets"][i]]["startY"] = computedWidgetStartY
2408 widgets[widgets[k]["linkedWidgets"][i]]["endY"] = computedWidgetEndY
2409 end
2410
2411 setProperty(k, "valuesShownMin", widgets[k]["valuesShownMin"] + scrollSize)
2412 end
2413 elseif scrollSize > 0 then
2414 --Plus
2415 if widgets[k]["valuesShownMin"] + scrollSize > widgets[k]["size"] - widgets[k]["height"] then
2416 scrollSize = widgets[k]["size"] - widgets[k]["height"] - widgets[k]["valuesShownMin"]
2417 end
2418
2419 if widgets[k]["valuesShownMax"] < widgets[k]["size"] then
2420 for i = 1, #widgets[k]["linkedWidgets"] do
2421 local actualWidgetStartY = widgets[widgets[k]["linkedWidgets"][i]]["startY"]
2422 local actualWidgetEndY = widgets[widgets[k]["linkedWidgets"][i]]["endY"]
2423 local computedWidgetStartY = actualWidgetStartY - scrollSize
2424 local computedWidgetEndY = actualWidgetEndY - scrollSize
2425
2426 if actualWidgetStartY > monY and computedWidgetStartY <= monY then
2427 computedWidgetStartY = computedWidgetStartY - scrollManagerBotPadding
2428 computedWidgetEndY = computedWidgetEndY - scrollManagerBotPadding
2429 end
2430 if actualWidgetEndY >= widgetStartY and computedWidgetEndY < widgetStartY then
2431 computedWidgetStartY = computedWidgetStartY - scrollManagerTopPadding
2432 computedWidgetEndY = computedWidgetEndY - scrollManagerTopPadding
2433 end
2434
2435 widgets[widgets[k]["linkedWidgets"][i]]["startY"] = computedWidgetStartY
2436 widgets[widgets[k]["linkedWidgets"][i]]["endY"] = computedWidgetEndY
2437 end
2438
2439 setProperty(k, "valuesShownMin", widgets[k]["valuesShownMin"] + scrollSize)
2440 end
2441 end
2442end
2443
2444function scrollScrollManagerTo(nScrollTo, sScrollManagerId)
2445 nScrollTo = tonumber(nScrollTo)
2446 sScrollManagerId = tostring(sScrollManagerId)
2447 if type(nScrollTo) ~= "number" then return false, "Argument #1: Expected number, got "..type(nScrollTo) end
2448 if type(sScrollManagerId) ~= "string" then return false, "Argument #2: Expected string, got "..type(sScrollManagerId) end
2449
2450 if not widgets[sScrollManagerId] or widgets[sScrollManagerId]["type"] ~= "widget_scrollmanager" then
2451 return false, "Unknown ScrollManager"
2452 end
2453
2454 if nScrollTo < 1 or nScrollTo > widgets[sScrollManagerId]["size"] then
2455 return false, "Argument #1: Out of Bounds Error"
2456 end
2457
2458 scrollScrollManager(nScrollTo - widgets[sScrollManagerId]["valuesShownMin"], sScrollManagerId)
2459end
2460
2461local function useTextbox(textboxId, widgetStartX, widgetStartY, widgetEndX, widgetEndY)
2462 local runLoop = true
2463
2464 while runLoop do
2465 local oldColor = term.getBackgroundColor()
2466 local oldTextColor = term.getTextColor()
2467
2468 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, widgets[textboxId]["activeColor"])
2469 term.setCursorPos(widgetStartX, widgetStartY)
2470 term.setTextColor(widgets[textboxId]["textActiveColor"])
2471 local rep, lastUsedKey = custRead(widgets[textboxId]["width"] + widgetStartX, widgets[textboxId]["charReplace"], widgets[textboxId]["tHistory"], completer, textboxId)
2472 term.setTextColor(colors.white)
2473
2474 if widgets[textboxId]["bHistory"] then
2475 table.insert(widgets[textboxId]["tHistory"], rep)
2476 end
2477
2478 if widgets[textboxId]["value"] ~= rep then
2479 widgets[textboxId]["value"] = rep
2480 os.queueEvent("textbox_text", textboxId, rep, lastUsedKey == 28)
2481 end
2482
2483 if widgets[textboxId]["value"] == "" then
2484 paintutils.drawLine(widgetStartX, widgetStartY, widgetEndX, widgetStartY, oldColor)
2485
2486 paintutils.drawLine(widgetStartX + 1, widgetStartY, widgetEndX - 1, widgetStartY, widgets[textboxId]["inactiveColor"])
2487
2488 if widgets[textboxId]["text"] then
2489 term.setCursorPos(widgetStartX + 1, widgetStartY)
2490 term.setTextColor(widgets[textboxId]["textInactiveColor"])
2491 term.write(widgets[textboxId]["text"])
2492 term.setTextColor(colors.white)
2493 end
2494 end
2495
2496 term.setBackgroundColor(oldColor)
2497 term.setTextColor(oldTextColor)
2498
2499 if lastUsedKey == 15 and widgets[textboxId]["group"] then
2500 local bNextFound = false
2501 local bLoopBroken = false
2502 local firstTextbox = ""
2503
2504 for k, v in pairs(textboxGroup[widgets[textboxId]["group"]]) do
2505 if firstTextbox == "" then
2506 firstTextbox = v
2507 end
2508
2509 if bNextFound then
2510 textboxId = v
2511 widgetStartX, widgetStartY, widgetEndX, widgetEndY = getWidgetHitbox(textboxId)
2512 bLoopBroken = true
2513 break
2514 end
2515
2516 if v == textboxId then
2517 bNextFound = true
2518 end
2519 end
2520
2521 if not bLoopBroken then
2522 textboxId = firstTextbox
2523 widgetStartX, widgetStartY, widgetEndX, widgetEndY = getWidgetHitbox(textboxId)
2524 end
2525 else
2526 runLoop = false
2527 end
2528 end
2529end
2530
2531function eventHandler()
2532 local mouseDragUtility = ""
2533
2534 while bRun do
2535 event, a1, a2, a3, a4, a5 = os.pullEvent()
2536
2537 local monX, monY = term.getSize()
2538
2539 if bDialogRunning == false then
2540 if event == "mouse_click" or (event == "monitor_touch" and monX ~= 51 and monY ~= 19) then
2541 if getTabCount() > 0 and ((a3 == 2 and not widgets["window"]["tabsBottom"]) or (a3 == monY and widgets["window"]["tabsBottom"])) then
2542 if a2 == monX and getTabPageCount() > 1 then
2543 -- Next button clicked
2544 if activeTabPage < getTabPageCount() then
2545 activeTabPage = activeTabPage + 1
2546 updateTabs()
2547 end
2548 elseif a2 == monX - 1 and getTabPageCount() > 1 then
2549 -- Previous button clicked
2550 if activeTabPage > 1 then
2551 activeTabPage = activeTabPage - 1
2552 updateTabs()
2553 end
2554 else
2555 local activeTabPageIDs = tabsPage[activeTabPage]
2556 local tabBefore = getTabPageCountBeforeActive()
2557
2558 local oldPosX = 0
2559 local posX = 0
2560
2561 for i = 1, #activeTabPageIDs do
2562 local tabIndex = tabBefore + i
2563 oldPosX = posX + 1
2564 posX = posX + tabs[activeTabPageIDs[i]]["name"]:len() + 2
2565
2566 if a2 >= oldPosX and a2 <= posX then
2567 if activeTabData == tabs[activeTabPageIDs[i]] then
2568 break
2569 end
2570
2571 activeTab = tabIndex
2572 oldActiveTabData = activeTabData
2573 activeTabData = tabs[activeTabPageIDs[i]]
2574
2575 updateTabs()
2576 os.queueEvent("tab_changed", activeTabData["id"], a1, a2, a3)
2577
2578 break
2579 end
2580 end
2581 end
2582 else
2583 for k, v in pairs(widgets) do
2584 local widgetStartX, widgetStartY, widgetEndX, widgetEndY, _, widgetLossTopY, widgetLossBotY = getWidgetHitbox(k)
2585
2586 if widgets[k]["enabled"] == true and ((widgets[k]["tab"] and widgets[k]["tab"] == activeTabData["id"]) or type(widgets[k]["tab"]) == "nil") then
2587 if widgets[k]["type"] == "widget_button" and a2 <= widgetEndX and a2 >= widgetStartX and a3 <= widgetEndY and a3 >= widgetStartY then
2588 os.queueEvent("button_clicked", k, a1, a2, a3)
2589 if widgets[k]["animation"] == true then
2590 os.queueEvent("button_anim", k)
2591 end
2592 elseif widgets[k]["type"] == "widget_close_button" and a2 <= widgetEndX and a2 >= widgetStartX and a3 <= widgetEndY and a3 >= widgetStartY then
2593 os.queueEvent("button_close_clicked")
2594 bRun = false
2595 elseif widgets[k]["type"] == "widget_textbox" and a2 <= widgetEndX and a2 >= widgetStartX and a3 == widgetStartY then
2596 useTextbox(k, widgetStartX, widgetStartY, widgetEndX, widgetEndY)
2597 elseif widgets[k]["type"] == "widget_switch" and a2 <= widgetStartX + 3 and a2 >= widgetStartX and a3 == widgetStartY then
2598 local oldColor = term.getBackgroundColor()
2599
2600 if widgets[k]["state"] == true then
2601 widgets[k]["state"] = false
2602
2603 paintutils.drawPixel(widgetStartX + 3, widgetStartY, colors.gray)
2604 sleep(0.05)
2605 paintutils.drawPixel(widgetStartX + 3, widgetStartY, colors.white)
2606 paintutils.drawPixel(widgetStartX + 2, widgetStartY, colors.gray)
2607 sleep(0.05)
2608 paintutils.drawPixel(widgetStartX + 2, widgetStartY, colors.white)
2609 paintutils.drawPixel(widgetStartX + 1, widgetStartY, colors.gray)
2610 sleep(0.05)
2611 paintutils.drawPixel(widgetStartX + 1, widgetStartY, colors.white)
2612 paintutils.drawPixel(widgetStartX, widgetStartY, colors.gray)
2613 sleep(0.05)
2614 elseif widgets[k]["state"] == false then
2615 widgets[k]["state"] = true
2616
2617 paintutils.drawPixel(widgetStartX, widgetStartY, colors.lime)
2618 sleep(0.05)
2619 paintutils.drawPixel(widgetStartX, widgetStartY, colors.green)
2620 paintutils.drawPixel(widgetStartX + 1, widgetStartY, colors.lime)
2621 sleep(0.05)
2622 paintutils.drawPixel(widgetStartX + 1, widgetStartY, colors.green)
2623 paintutils.drawPixel(widgetStartX + 2, widgetStartY, colors.lime)
2624 sleep(0.05)
2625 paintutils.drawPixel(widgetStartX + 2, widgetStartY, colors.green)
2626 paintutils.drawPixel(widgetStartX + 3, widgetStartY, colors.lime)
2627 sleep(0.05)
2628 end
2629
2630 term.setBackgroundColor(oldColor)
2631 os.queueEvent("switch_state", k, widgets[k]["state"], a1, a2, a3)
2632 elseif widgets[k]["type"] == "widget_numeric" and a2 >= widgetStartX and a2 <= widgetEndX and a3 == widgetStartY then
2633 local oldValue = widgets[k]["value"]
2634
2635 if a2 == widgetStartX and a3 == widgetStartY then
2636 --Moins
2637 if widgets[k]["value"] - widgets[k]["increment"] > widgets[k]["minValue"] then
2638 widgets[k]["value"] = widgets[k]["value"] - widgets[k]["increment"]
2639 else
2640 widgets[k]["value"] = widgets[k]["minValue"]
2641 end
2642 elseif a2 == widgetEndX and a3 == widgetStartY then
2643 --Plus
2644 if widgets[k]["value"] + widgets[k]["increment"] < widgets[k]["maxValue"] then
2645 widgets[k]["value"] = widgets[k]["value"] + widgets[k]["increment"]
2646 else
2647 widgets[k]["value"] = widgets[k]["maxValue"]
2648 end
2649 elseif a2 > widgetStartX and a2 < widgetEndX and a3 == widgetStartY then
2650 -- Bar
2651 local oldColor = term.getBackgroundColor()
2652 local oldTextColor = term.getTextColor()
2653
2654 paintutils.drawLine(widgetStartX + 1, widgetStartY, widgetEndX - 1, widgetStartY, colors.blue)
2655 term.setCursorPos(widgetStartX + 1, widgetStartY)
2656 term.setTextColor(colors.white)
2657 local rep = custRead(widgets[k]["width"] + widgetStartX - 1, nil, nil, nil, k)
2658
2659 if tonumber(rep) then
2660 rep = tonumber(rep)
2661
2662 if rep < widgets[k]["minValue"] then
2663 rep = widgets[k]["minValue"]
2664 elseif rep > widgets[k]["maxValue"] then
2665 rep = widgets[k]["maxValue"]
2666 end
2667
2668 widgets[k]["value"] = rep
2669 end
2670
2671 term.setBackgroundColor(oldColor)
2672 term.setTextColor(oldTextColor)
2673 end
2674
2675 widgetUpdate(k)
2676
2677 if oldValue ~= widgets[k]["value"] then
2678 os.queueEvent("numeric_value", k, widgets[k]["value"], a1, a2, a3)
2679 end
2680 elseif widgets[k]["type"] == "widget_list" then
2681 local oldValue = widgets[k]["selectedValue"]
2682
2683 if a2 == widgetEndX and a3 - widgetLossTopY == widgetStartY then
2684 --Moins
2685 if widgets[k]["valuesShownMin"] > 1 then
2686 setProperty(k, "valuesShownMin", widgets[k]["valuesShownMin"] - 1)
2687 end
2688 elseif a2 == widgetEndX and a3 + widgetLossBotY == widgetEndY then
2689 --Plus
2690 if widgets[k]["valuesShownMax"] < #widgets[k]["values"] then
2691 setProperty(k, "valuesShownMin", widgets[k]["valuesShownMin"] + 1)
2692 end
2693 elseif a2 == widgetEndX and a3 >= widgetStartY and a3 <= widgetEndY then
2694 -- Scrollbar selected
2695 mouseDragUtility = k
2696
2697 local scrollbarResult = scrollbarCompute(k, a1, a2, a3, #widgets[k]["values"])
2698 if scrollbarResult then
2699 setProperty(k, "valuesShownMin", scrollbarResult)
2700 end
2701 elseif a2 <= widgetEndX - 1 and a2 >= widgetStartX and a3 <= widgetEndY and a3 >= widgetStartY then
2702 --Item selected
2703 local computedSelectedIndex = (a3 - (widgetStartY - 1)) + (widgets[k]["valuesShownMin"] - 1) + widgetLossTopY
2704
2705 if computedSelectedIndex <= #widgets[k]["values"] and computedSelectedIndex ~= oldValue then
2706 setProperty(k, "selectedValue", computedSelectedIndex)
2707 os.queueEvent("list_anim", k)
2708 os.queueEvent("list_selected", k, widgets[k]["valuesShown"][a3 - (widgetStartY - 1)], a1, a2, a3)
2709 end
2710 end
2711 elseif widgets[k]["type"] == "widget_numericslider" and a2 >= widgetStartX and a2 <= widgetEndX and a3 == widgetStartY then
2712 local oldValue = widgets[k]["value"]
2713
2714 mouseDragUtility = k
2715
2716 local nNumericSliderLength = widgetEndX - widgetStartX
2717 local nClickPosition = a2 - widgetStartX
2718 local nNewValue = nClickPosition * (widgets[k]["maxValue"] - widgets[k]["minValue"]) / nNumericSliderLength
2719 nNewValue = nNewValue + widgets[k]["minValue"]
2720
2721 widgets[k]["value"] = nNewValue
2722 widgetUpdate(k)
2723
2724 if widgets[k]["value"] ~= oldValue then
2725 os.queueEvent("numericslider_value", k, widgets[k]["value"], a1, a2, a3)
2726 end
2727 elseif widgets[k]["type"] == "widget_scrollmanager" then
2728 if a2 == widgetEndX and a3 == widgetStartY then
2729 --Moins
2730 scrollScrollManager(-1, k)
2731 elseif a2 == widgetEndX and a3 == widgetEndY then
2732 --Plus
2733 scrollScrollManager(1, k)
2734 elseif a2 == widgetEndX and a3 >= widgetStartY and a3 <= widgetEndY then
2735 -- Scrollbar selected
2736 mouseDragUtility = k
2737
2738 local scrollbarResult = scrollbarCompute(k, a1, a2, a3, widgets[k]["size"])
2739 if scrollbarResult then
2740 scrollScrollManagerTo(scrollbarResult, k)
2741 end
2742 end
2743 end
2744 end
2745 end
2746 end
2747 elseif event == "mouse_drag" then
2748 for k, v in pairs(widgets) do
2749 local widgetStartX, widgetStartY, widgetEndX, widgetEndY = getWidgetHitbox(k)
2750
2751 if widgets[k]["enabled"] == true then
2752 if widgets[k]["type"] == "widget_numericslider" and mouseDragUtility == k then
2753 local oldValue = widgets[k]["value"]
2754
2755 local nDragX = a2
2756 if nDragX < widgetStartX then
2757 nDragX = widgetStartX
2758 elseif nDragX > widgetEndX then
2759 nDragX = widgetEndX
2760 end
2761
2762 local nNumericSliderLength = widgetEndX - widgetStartX
2763 local nClickPosition = nDragX - widgetStartX
2764 local nNewValue = nClickPosition * (widgets[k]["maxValue"] - widgets[k]["minValue"]) / nNumericSliderLength
2765 nNewValue = nNewValue + widgets[k]["minValue"]
2766
2767 if nNewValue ~= widgets[k]["value"] then
2768 widgets[k]["value"] = nNewValue
2769 widgetUpdate(k)
2770 end
2771
2772 if widgets[k]["value"] ~= oldValue then
2773 os.queueEvent("numericslider_value", k, widgets[k]["value"], a1, a2, a3)
2774 end
2775 elseif widgets[k]["type"] == "widget_list" and mouseDragUtility == k and a3 >= widgetStartY and a3 <= widgetEndY then
2776 -- Scrollbar selected
2777 local scrollbarResult = scrollbarCompute(k, a1, a2, a3, #widgets[k]["values"])
2778 if scrollbarResult then
2779 setProperty(k, "valuesShownMin", scrollbarResult)
2780 end
2781 elseif widgets[k]["type"] == "widget_scrollmanager" and mouseDragUtility == k and a3 >= widgetStartY and a3 <= widgetEndY then
2782 -- Scrollbar selected
2783 local scrollbarResult = scrollbarCompute(k, a1, a2, a3, widgets[k]["size"])
2784 if scrollbarResult then
2785 scrollScrollManagerTo(scrollbarResult, k)
2786 end
2787 end
2788 end
2789 end
2790 elseif event == "mouse_up" then
2791 -- Reset mouse_drag event filters
2792 mouseDragUtility = ""
2793 elseif event == "mouse_scroll" then
2794 for k, v in pairs(widgets) do
2795 local widgetStartX, widgetStartY, widgetEndX, widgetEndY = getWidgetHitbox(k)
2796
2797 if widgets[k]["enabled"] == true then
2798 if widgets[k]["type"] == "widget_list" and a2 <= widgetEndX and a2 >= widgetStartX and a3 <= widgetEndY and a3 >= widgetStartY then
2799 if widgets[k]["valuesShownMin"] > 1 and a1 == -1 then
2800 setProperty(k, "valuesShownMin", widgets[k]["valuesShownMin"] - 1)
2801 elseif widgets[k]["valuesShownMax"] < #widgets[k]["values"] and a1 == 1 then
2802 setProperty(k, "valuesShownMin", widgets[k]["valuesShownMin"] + 1)
2803 end
2804 elseif widgets[k]["type"] == "widget_scrollmanager" and a2 <= widgetEndX and a2 >= widgetStartX and a3 <= widgetEndY and a3 >= widgetStartY then
2805 scrollScrollManager(a1, k)
2806 end
2807 end
2808 end
2809 end
2810 end
2811 end
2812end
2813
2814function marqueeAnim()
2815 while bRun do
2816 if not bDialogRunning then
2817 for k, v in pairs(widgets) do
2818 if widgets[k]["enabled"] == true then
2819 if widgets[k]["type"] == "widget_progress_marquee" then
2820 if widgets[k]["maxGreen"] == widgets[k]["endX"] then
2821 widgets[k]["animDirection"] = "-"
2822 elseif widgets[k]["minGreen"] == widgets[k]["startX"] then
2823 widgets[k]["animDirection"] = "+"
2824 end
2825
2826 if widgets[k]["animDirection"] == "+" then
2827 widgets[k]["minGreen"] = widgets[k]["minGreen"] + 1
2828 widgets[k]["maxGreen"] = widgets[k]["maxGreen"] + 1
2829 elseif widgets[k]["animDirection"] == "-" then
2830 widgets[k]["minGreen"] = widgets[k]["minGreen"] - 1
2831 widgets[k]["maxGreen"] = widgets[k]["maxGreen"] - 1
2832 end
2833
2834 local oldColor = term.getBackgroundColor()
2835 local curX, curY = term.getCursorPos()
2836
2837 paintutils.drawLine(widgets[k]["startX"], widgets[k]["startY"], widgets[k]["endX"], widgets[k]["startY"], colors.cyan)
2838 paintutils.drawLine(widgets[k]["minGreen"], widgets[k]["startY"], widgets[k]["maxGreen"], widgets[k]["startY"], colors.green)
2839
2840 term.setBackgroundColor(oldColor)
2841 term.setCursorPos(curX, curY)
2842 end
2843 end
2844 end
2845 end
2846
2847 sleep(0.05)
2848 end
2849end
2850
2851function progressAnim()
2852 while bRun do
2853 local oldColor = term.getBackgroundColor()
2854 local curX, curY = term.getCursorPos()
2855
2856 if not bDialogRunning then
2857 for k, v in pairs(widgets) do
2858 if widgets[k]["enabled"] == true then
2859 if widgets[k]["type"] == "widget_progress" and widgets[k]["value"] > 0 then
2860 if widgets[k]["anim_health"] == 1 then
2861 if widgets[k]["anim_pixel"] >= widgets[k]["value_width"] then
2862 widgets[k]["anim_pixel"] = widgets[k]["value_width"]
2863 paintutils.drawPixel(widgets[k]["anim_pixel"] - 1, widgets[k]["startY"], colors.blue)
2864 paintutils.drawPixel(widgets[k]["anim_pixel"], widgets[k]["startY"], colors.lightBlue)
2865 term.setBackgroundColor(oldColor)
2866 term.setCursorPos(curX, curY)
2867
2868 sleep(0.01)
2869
2870 paintutils.drawPixel(widgets[k]["anim_pixel"], widgets[k]["startY"], colors.blue)
2871 term.setBackgroundColor(oldColor)
2872 term.setCursorPos(curX, curY)
2873
2874 widgets[k]["anim_health"] = 100
2875 widgets[k]["anim_pixel"] = widgets[k]["startX"]
2876 else
2877 paintutils.drawPixel(widgets[k]["startX"], widgets[k]["startY"], colors.blue)
2878 paintutils.drawPixel(widgets[k]["anim_pixel"], widgets[k]["startY"], colors.lightBlue)
2879 term.setBackgroundColor(oldColor)
2880 term.setCursorPos(curX, curY)
2881
2882 if widgets[k]["anim_pixel"] > widgets[k]["startX"] then
2883 paintutils.drawPixel(widgets[k]["anim_pixel"] - 1, widgets[k]["startY"], colors.blue)
2884 term.setBackgroundColor(oldColor)
2885 term.setCursorPos(curX, curY)
2886 end
2887
2888 widgets[k]["anim_pixel"] = widgets[k]["anim_pixel"] + 1
2889 end
2890 else
2891 widgets[k]["anim_health"] = widgets[k]["anim_health"] - 1
2892 end
2893 end
2894 end
2895 end
2896 end
2897
2898 term.setBackgroundColor(oldColor)
2899 term.setCursorPos(curX, curY)
2900 sleep(0.01)
2901 end
2902end
2903
2904function buttonAnim()
2905 while bRun do
2906 local event, param1 = os.pullEvent()
2907
2908 if event == "button_anim" and bCanButtonAnim then
2909 for i = 1, 4 do
2910 setProperty(param1, "btnColor", term.getBackgroundColor())
2911 sleep(0.05)
2912 local oldButtonColor = widgets[param1]["oldBtnColor"]
2913 setProperty(param1, "btnColor", oldButtonColor)
2914 sleep(0.05)
2915 end
2916 end
2917 end
2918end
2919
2920function listAnim()
2921 while bRun do
2922 local event, param1 = os.pullEvent()
2923
2924 if event == "list_anim" then
2925 for i = 1, 4 do
2926 setProperty(param1, "selectedColor", colors.lightBlue)
2927 sleep(0.05)
2928 local oldSelectedColor = widgets[param1]["oldSelectedColor"]
2929 setProperty(param1, "selectedColor", oldSelectedColor)
2930 sleep(0.05)
2931 end
2932 end
2933 end
2934end