Arc13

arcUI

Jul 16th, 2016
357
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 101.84 KB | None | 0 0
  1. widgets = {}
  2. lastSessionWidgets = {}
  3.  
  4. tabs = {}
  5. lastSessionTabs = {}
  6. tabsPage = {}
  7. activeTab = 0
  8. oldActiveTabData = {}
  9. activeTabData = {}
  10. activeTabPage = 1
  11.  
  12. textboxGroup = {}
  13.  
  14. bRun = true
  15. bDialogRunning = false
  16. bCanButtonAnim = true
  17.  
  18. --[[
  19.  
  20. Events :
  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.  
  31. Parallel functions :
  32. parallel.waitForAll(arcui.eventHandler, arcui.marqueeAnim)
  33.  
  34. --> Main loop
  35. eventHandler : Make buttons, textboxes and sliders work
  36.  
  37. --> Required animations
  38. marqueeAnim : Enable animation for widget_progress_marquee
  39.  
  40. --> Optional animations
  41. progressAnim : Enable animation for widget_progress (still in beta)
  42. buttonAnim   : Enable animation for widget_button (still in beta)
  43. listAnim     : Enable animation for widget_list (still in beta)
  44.  
  45. Functions :
  46. --> Window
  47. drawWindow(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
  48. redrawWindow() : Quickly redraw the window and it's contents
  49. closeWindow(boolean skip_animation) : Close the window, to use before closing the app
  50. openDialog(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)
  53. deleteWidget(string widget_id) : Disable and delete a widget
  54. deleteAllWidgets() : Delete all the widgets in the screen
  55. widgetUpdate(string widget_id) : Redraw a widget
  56. getWidgetList() : Returns a list of widget with their types
  57. getWidgetListByType(string widget_type) : Returns a list of widgets filtered by the type
  58. setProperty(string widget_id, string value_index, string value) : Change a property of a widget
  59. setProperties(string widget_id, string first_value_index, string first_value, ...) : Change multiple properties of a widget
  60. getProperty(string widget_id, string value_index) : Get a property of a widget
  61.  
  62. drawButton(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
  63. drawProgress(string widget_id, number start_x, number y, number end_x, number max_value, number value) : Add a progress bar to the screen
  64. drawMarqueeProgress(string widget_id, number start_x, number y, number end_x) : Add a progress bar with an undetermined value to the screen
  65. drawLabel(string widget_id, number x, number y, string label_text[, number background_color, number text_color]) : Add an editable label to the screen
  66. drawTextbox(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
  67. drawSwitch(string widget_id, number x, number y, string slider_text[, boolean default_state]) : Add a 4x1 switch to the screen
  68. drawNumeric(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
  69. drawList(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
  70. drawNumericSlider(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
  71. drawScrollManager(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
  74. addTab(string tab_id, string tab_name[, number tab_position]) : Add a tab to the tab manager
  75. removeTab(string tab_id) : Remove a tab from the tab manager
  76.  
  77. linkToTab(string tab_id, table widget) : Link a widget to a tab (widget is like linkToScrollManager())
  78. unlinkFromTab(string widget_id) : Unlink a widget from it's tab
  79.  
  80. getTabCount()      : Returns the tab count
  81. getTabPageCount()  : Return the tab page count
  82. getTabPageCountBeforeActive() : Return the number of tabs before the active page
  83.  
  84. --> ScrollManager
  85. linkToScrollManager(string scrollmanager_id, table widget) : Manage a widget with a ScrollManager
  86. autoResizeScrollManager(string scrollmanager_id) : Resize automatically a ScrollManager
  87. scrollScrollManager(number relative_scroll, string scrollmanager_id) : Scroll a ScrollManager relatively
  88. scrollScrollManagerTo(number absolute_scroll, string scrollmanager_id) : Scroll a ScrollManager with an absolute value
  89. Ex: arcui.linkToScrollManager("scrlmanid", arcui.createNumericSlider("numslid", 2, 2, 20, 50))
  90.  
  91. --> Textbox groups
  92. createTextboxGroup(string group_id)                   : Create a new textbox group
  93. removeTextboxGroup(string group_id)                   : Remove a textbox group
  94. getTextboxGroup(string group_id)                      : Get the textbox(es) in a group
  95. addTextboxToGroup(string group_id, string textbox_id) : Add a textbox to a group
  96. addTextboxesToGroup(string group_id, string first_textbox_id, ...) : Add multiple textboxes to a group
  97.  
  98. --> Utils
  99. columnFormatter(string first_column, number first_column_length, ...) : Format a string to mimic columns (Each column must have a length of 4 chars minimum)
  100. writeFormatted("string formatted_string") : Write a text with dynamic color changes (See "String formatting" section)
  101.  
  102. String formatting :
  103. Every string in arcUI can be formatted to change the text and background color dynamically.
  104. Example: 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.  
  109. Credits :
  110. Made by Kuruyia (formerly known as arc13)
  111.  
  112. The MIT License (MIT)
  113.  
  114. Copyright (c) 2017-2018 Kuruyia
  115.  
  116. Permission is hereby granted, free of charge, to any person obtaining a copy of
  117. this software and associated documentation files (the "Software"), to deal in
  118. the Software without restriction, including without limitation the rights to
  119. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  120. the Software, and to permit persons to whom the Software is furnished to do so,
  121. subject to the following conditions:
  122.  
  123. The above copyright notice and this permission notice shall be included in all
  124. copies or substantial portions of the Software.
  125.  
  126. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  127. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  128. FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  129. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  130. IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  131. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  132.  
  133. ]]
  134.  
  135. local 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)
  151. end
  152.  
  153. local 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
  449. end
  450.  
  451. local cx, cy = term.getCursorPos()
  452. term.setCursorBlink( false )
  453. term.setCursorPos( w + 1, cy )
  454. print()
  455.  
  456. return sLine, nLastUsedKey
  457. end
  458.  
  459. local function tostringn(value)
  460.   if value == nil then
  461.     return nil
  462.   else
  463.     return tostring(value)
  464.   end
  465. end
  466.  
  467. local 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
  484. end
  485.  
  486. local function reverseTwoArgs(arg1, arg2)
  487.   return arg2, arg1
  488. end
  489.  
  490. function 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
  504. end
  505.  
  506. function 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
  524. end
  525.  
  526. function 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]
  539. end
  540.  
  541. function 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
  562. end
  563.  
  564. function 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
  592. end
  593.  
  594. function 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
  647. end
  648.  
  649. local 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
  686. end
  687.  
  688. function 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
  841. end
  842.  
  843. function 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]
  1141. end
  1142.  
  1143. function 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
  1206. end
  1207.  
  1208. function setProperty(sId, sValueIndex, sNewValue)
  1209.   return setProperties(sId, sValueIndex, sNewValue)
  1210. end
  1211.  
  1212. function changeValue(sId, sValueIndex, sNewValue)
  1213.   return setProperty(sId, sValueIndex, sNewValue)
  1214. end
  1215.  
  1216. function 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]
  1224. end
  1225.  
  1226. local 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
  1252. end
  1253.  
  1254. local 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()
  1303. end
  1304.  
  1305. local 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)
  1357. end
  1358.  
  1359. function getTabCount()
  1360.   local tabCount = 0
  1361.  
  1362.   for k in pairs(tabs) do
  1363.     tabCount = tabCount + 1
  1364.   end
  1365.  
  1366.   return tabCount
  1367. end
  1368.  
  1369. function getTabPageCount()
  1370.   return #tabsPage
  1371. end
  1372.  
  1373. function getTabPageCountBeforeActive()
  1374.   local tabBefore = 0
  1375.  
  1376.   for i = 1, activeTabPage - 1 do
  1377.     tabBefore = tabBefore + #tabsPage[i]
  1378.   end
  1379.  
  1380.   return tabBefore
  1381. end
  1382.  
  1383. function 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
  1411. end
  1412.  
  1413. function 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
  1426. end
  1427.  
  1428. function 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]
  1443. end
  1444.  
  1445. function 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
  1460. end
  1461.  
  1462. function deleteAllTabs()
  1463.   lastSessionTabs = tabs
  1464.   tabs = {}
  1465. end
  1466.  
  1467. function 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
  1482. end
  1483.  
  1484. function deleteAllWidgets()
  1485.   lastSessionWidgets = widgets
  1486.   widgets = {}
  1487. end
  1488.  
  1489. function getWidgetList()
  1490.   local widgetList = {}
  1491.  
  1492.   for k, v in pairs(widgets) do
  1493.     widgetList[k] = v["type"]
  1494.   end
  1495.  
  1496.   return widgetList
  1497. end
  1498.  
  1499. function 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
  1513. end
  1514.  
  1515. function 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
  1584. end
  1585.  
  1586. function redrawWindow()
  1587.   return drawWindow(nil, nil, true)
  1588. end
  1589.  
  1590. function 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
  1615. end
  1616.  
  1617. function 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
  1664. end
  1665.  
  1666. function 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)
  1712. end
  1713.  
  1714. function 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
  1762. end
  1763.  
  1764. function 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
  1797. end
  1798.  
  1799. function 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
  1829. end
  1830.  
  1831. function 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
  1867. end
  1868.  
  1869. function 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
  1927. end
  1928.  
  1929. function 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
  1967. end
  1968.  
  1969. function 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
  2016. end
  2017.  
  2018. function 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
  2064. end
  2065.  
  2066. function 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
  2105. end
  2106.  
  2107. function 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
  2150. end
  2151.  
  2152. local 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
  2161. end
  2162.  
  2163. function drawButton(...)
  2164.   return drawGenericWidget(createButton(...))
  2165. end
  2166.  
  2167. function drawProgress(...)
  2168.   return drawGenericWidget(createProgress(...))
  2169. end
  2170.  
  2171. function drawMarqueeProgress(...)
  2172.   return drawGenericWidget(createMarqueeProgress(...))
  2173. end
  2174.  
  2175. function drawLabel(...)
  2176.   return drawGenericWidget(createLabel(...))
  2177. end
  2178.  
  2179. function drawTextbox(...)
  2180.   return drawGenericWidget(createTextbox(...))
  2181. end
  2182.  
  2183. function drawSwitch(...)
  2184.   return drawGenericWidget(createSlider(...))
  2185. end
  2186.  
  2187. function drawNumeric(...)
  2188.   return drawGenericWidget(createNumeric(...))
  2189. end
  2190.  
  2191. function drawList(...)
  2192.   return drawGenericWidget(createList(...))
  2193. end
  2194.  
  2195. function drawNumericSlider(...)
  2196.   return drawGenericWidget(createNumericSlider(...))
  2197. end
  2198.  
  2199. function drawScrollManager(...)
  2200.   return drawGenericWidget(createScrollManager(...))
  2201. end
  2202.  
  2203. function 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)
  2205. end
  2206.  
  2207. function drawSlider(...)
  2208.   createSlider(...)
  2209. end
  2210.  
  2211. function 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
  2318. end
  2319.  
  2320. local 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
  2330. end
  2331.  
  2332. local 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
  2367. end
  2368.  
  2369. function 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
  2442. end
  2443.  
  2444. function 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)
  2459. end
  2460.  
  2461. local 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
  2529. end
  2530.  
  2531. function 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
  2812. end
  2813.  
  2814. function 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
  2849. end
  2850.  
  2851. function 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
  2902. end
  2903.  
  2904. function 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
  2918. end
  2919.  
  2920. function 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
  2934. end
Add Comment
Please, Sign In to add comment