Advertisement
kaibochan

gui.lua

Mar 19th, 2023 (edited)
296
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 61.72 KB | None | 0 0
  1. --[[
  2.     alignment options for text elements and derivatives
  3. ]]
  4. align = {
  5.     top = "top",
  6.     left = "left",
  7.     center = "center",
  8.     right = "right",
  9.     bottom = "bottom",
  10. }
  11.  
  12. --[[
  13.     global flags for shift and control being held
  14. ]]
  15. shiftHeld = false
  16. ctrlHeld = false
  17.  
  18. --[[
  19.     currently selected element
  20. ]]
  21. selectedElement = nil
  22.  
  23. --[[
  24.     base level elements, used in handleInputEvents to get selected elements
  25. ]]
  26. buffers = {}
  27.  
  28. --------------------------------
  29. --Callbacks
  30. --------------------------------
  31.  
  32. --[[
  33.     registered callbacks to happen no matter which element is selected
  34. ]]
  35. globalCallbacks = {
  36.     ["monitor_touch"] = {},
  37.     ["mouse_click"] = {},
  38.     ["mouse_drag"] = {},
  39.     ["mouse_scroll"] = {},
  40.     ["mouse_up"] = {},
  41.     ["key"] = {},
  42.     ["key_up"] = {},
  43.     ["char"] = {},
  44.     ["paste"] = {},
  45. }
  46. --[[
  47.     register callback functions for various mouse and keyboard events
  48. ]]
  49. function registerGlobalCallback(event, element, callback, callbackName)
  50.     table.insert(globalCallbacks[event], {element = element, callback = callback, callbackName = callbackName})
  51. end
  52.  
  53. function removeGlobalCallback(event, element, callbackName)
  54.     for index, callback in ipairs(globalCallbacks[event]) do
  55.         if callback.element.name == element.name and (callback.callbackName == callbackName or not callback.callbackName) then
  56.             table.remove(globalCallbacks[event], index)
  57.             break
  58.         end
  59.     end
  60. end
  61.  
  62. --[[
  63.     registered callbacks for each user input event, only occurs if element is selected
  64. ]]
  65. selectionCallbacks = {
  66.     ["monitor_touch"] = {},
  67.     ["mouse_click"] = {},
  68.     ["mouse_drag"] = {},
  69.     ["mouse_scroll"] = {},
  70.     ["mouse_up"] = {},
  71.     ["key"] = {},
  72.     ["key_up"] = {},
  73.     ["char"] = {},
  74.     ["paste"] = {},
  75. }
  76.  
  77. --[[
  78.     register callback functions for various mouse and keyboard events for a given element
  79. ]]
  80. function registerSelectionCallback(event, element, callback, callbackName)
  81.     table.insert(selectionCallbacks[event], {elementName = element.name, callback = callback, callbackName = callbackName})
  82. end
  83.  
  84. function removeSelectionCallback(event, element, callbackName)
  85.     for index, callback in ipairs(selectionCallbacks[event]) do
  86.         if callback.elementName == element.name and (callback.callbackName == callbackName or not callback.callbackName) then
  87.             table.remove(selectionCallbacks[event], index)
  88.             break
  89.         end
  90.     end
  91. end
  92.  
  93. --[[
  94.     register key events for shift and control other elements to reference
  95. ]]
  96. registerGlobalCallback("key", nil, function(_, event, key, isHeld)
  97.     local keyName = keys.getName(key)
  98.  
  99.     if keyName == "leftShift" or keyName == "rightShift" then
  100.         shiftHeld = true
  101.     elseif keyName == "leftCtrl" or keyName == "rightCtrl" then
  102.         ctrlHeld = true
  103.     end
  104. end, "pressShiftControl")
  105.  
  106. registerGlobalCallback("key_up", nil, function(_, event, key)
  107.     local keyName = keys.getName(key)
  108.  
  109.     if keyName == "leftShift" or keyName == "rightShift" then
  110.         shiftHeld = false
  111.     elseif keyName == "leftCtrl" or keyName == "rightCtrl" then
  112.         ctrlHeld = false
  113.     end
  114. end, "releaseShiftControl")
  115.  
  116. function handleMouseClick(buffer, button, x, y)
  117.     return getSelectedElement(buffer, x, y)
  118. end
  119.  
  120. function handleMonitorTouch(buffer, side, x, y)
  121.     if buffer.display.side == side then
  122.         return getSelectedElement(buffer, x, y)
  123.     end
  124. end
  125.  
  126. --[[
  127.     depth first search to find selected element given an x and y
  128. ]]
  129. function getSelectedElement(buffer, x, y)
  130.     local selectedElement
  131.  
  132.     --iterate over elements backwards to grab elements drawn on top first
  133.     for i = #buffer.children, 1, -1 do
  134.         selectedElement = getSelectedElement(buffer.children[i], x, y)
  135.  
  136.         if selectedElement then
  137.             return selectedElement
  138.         end
  139.     end
  140.  
  141.     if buffer:selected(x, y) then
  142.         return buffer
  143.     end
  144. end
  145.  
  146. function handleInputEvents()
  147.     local event, data1, data2, data3 = os.pullEvent()
  148.     if globalCallbacks[event] or selectionCallbacks[event] then
  149.         if event == "mouse_click" then
  150.             for _, buffer in ipairs(buffers) do
  151.                 selectedElement = handleMouseClick(buffer, data1, data2, data3)
  152.                 if selectedElement then
  153.                     break
  154.                 end
  155.             end
  156.         elseif event == "monitor_touch" then
  157.             for _, buffer in ipairs(buffers) do
  158.                 selectedElement = handleMonitorTouch(buffer, data1, data2, data3)
  159.                 if selectedElement then
  160.                     break
  161.                 end
  162.             end
  163.         end
  164.  
  165.         for _, callback in ipairs(globalCallbacks[event]) do
  166.             callback.callback(callback.element, event, data1, data2, data3)
  167.         end
  168.  
  169.         if selectedElement and selectionCallbacks[event] then
  170.             for _, callback in ipairs(selectionCallbacks[event]) do
  171.                 if callback.elementName == selectedElement.name and callback.callback then
  172.                     callback.callback(selectedElement, event, data1, data2, data3)
  173.                 end
  174.             end
  175.         end
  176.     end
  177. end
  178.  
  179. --------------------------------
  180. --Display
  181. --------------------------------
  182.  
  183. Display = {
  184.     device = nil,
  185.     isMonitor = false,
  186.     side = nil,
  187.     width = 1,
  188.     height = 1,
  189. }
  190.  
  191. function Display:new(o)
  192.     o = o or {}
  193.     setmetatable(o, self)
  194.     self.__index = self
  195.     return o
  196. end
  197.  
  198. --------------------------------
  199. --Cell
  200. --------------------------------
  201.  
  202. Cell = {
  203.     backgroundColor = colors.black,
  204.     textColor = colors.white,
  205.     character = " ",
  206. }
  207.  
  208. function Cell:new (o)
  209.     o = o or {}
  210.     setmetatable(o, self)
  211.     self.__index = self
  212.     return o
  213. end
  214.  
  215. --------------------------------
  216. --Buffer
  217. --------------------------------
  218.  
  219. Buffer = {
  220.     class = "Buffer",
  221.     display = nil,
  222.     children = {},
  223.     globalX = 1,
  224.     globalY = 1,
  225.     width = 1,
  226.     height = 1,
  227.     backgroundColor = colors.black,
  228.     cells = {},
  229. }
  230.    
  231. function Buffer:new (o)
  232.     o = o or {}
  233.  
  234.     --tables are passed by reference so new ones must be created
  235.     o.cells = o.cells or {}
  236.     o.children = o.children or {}
  237.  
  238.     setmetatable(o, self)
  239.     self.__index = self
  240.  
  241.     o:initializeCells()
  242.  
  243.     return o
  244. end
  245.  
  246. function Buffer:initializeCells()
  247.     local cell
  248.  
  249.     for x = 1, self.width do
  250.         for y = 1, self.height do
  251.             cell = Cell:new { backgroundColor = self.backgroundColor }
  252.             if not self.cells[x] then
  253.                 table.insert(self.cells, x, {[y] = cell})
  254.             else
  255.                 table.insert(self.cells[x], y, cell)
  256.             end
  257.         end
  258.     end
  259. end
  260.  
  261. --[[
  262.     draw children, then draw to parent's buffer if parent exists, otherwise draw to display
  263. ]]
  264. function Buffer:draw()
  265.  
  266.     --reset buffer for drawing to
  267.     for x = 1, self.width do
  268.         for y = 1, self.height do
  269.             self.cells[x][y].backgroundColor = self.backgroundColor
  270.             self.cells[x][y].character = " "
  271.         end
  272.     end
  273.  
  274.     --draw all children to buffer cells
  275.     for _, child in ipairs(self.children) do
  276.         child:draw()
  277.     end
  278.  
  279.     --draw buffer cells to the screen
  280.     for y = 1, self.height do
  281.         local characters = ""
  282.         local textColors = ""
  283.         local backgroundColors = ""
  284.  
  285.         for x = 1, self.width do
  286.             characters = characters .. self.cells[x][y].character
  287.             textColors = textColors .. colors.toBlit(self.cells[x][y].textColor)
  288.             backgroundColors = backgroundColors ..  colors.toBlit(self.cells[x][y].backgroundColor)
  289.         end
  290.  
  291.         self.display.device.setCursorPos(self.globalX, self.globalY + y - 1)
  292.         self.display.device.blit(characters, textColors, backgroundColors)
  293.     end
  294. end
  295.  
  296. --[[
  297.     converts to global coordinates and then test for if x and y are within bounds
  298. ]]
  299. function Buffer:selected(x, y)
  300.     return x >= self.globalX and x < self.globalX + self.width and y >= self.globalY and y < self.globalY + self.height
  301. end
  302.  
  303. --------------------------------
  304. --Element
  305. --------------------------------
  306.  
  307. Element = Buffer:new {
  308.     class = "Element",
  309.     buffer = nil,
  310.     parent = nil,
  311.     x = 1,
  312.     y = 1,
  313.     visible = true,
  314.     transparentBackground = false,
  315.     isBuffer = false,
  316. }
  317.    
  318. function Element:new (o)
  319.     o = o or {}
  320.  
  321.     local gx = o.globalX
  322.     local gy = o.globalY
  323.  
  324.     o = Buffer:new(o)
  325.  
  326.     --tables are passed by reference so new ones must be created if not passed in
  327.     o.cells = o.cells or {}
  328.     o.children = o.children or {}
  329.  
  330.     setmetatable(o, self)
  331.     self.__index = self
  332.  
  333.     if o.buffer then
  334.         table.insert(o.buffer.children, o)
  335.     end
  336.  
  337.     if o.parent and o.parent ~= o.buffer then
  338.         table.insert(o.parent.children, o)
  339.     end
  340.    
  341.     if not gx and not gy then
  342.         o.globalX, o.globalY = o:getGlobalPos(o.x, o.y)
  343.     else
  344.         o:setGlobalPos(o.globalX, o.globalY)
  345.     end
  346.  
  347.     return o
  348. end
  349.  
  350. function Element:setWidth(newWidth)
  351.     self.width = newWidth
  352.    
  353.     self.cells = {}
  354.  
  355.     self:initializeCells()
  356. end
  357.  
  358. function Element:setHeight(newHeight)
  359.     self.height = newHeight
  360.  
  361.     self.cells = {}
  362.  
  363.     self:initializeCells()
  364. end
  365.  
  366. --[[
  367.     remove element from previous parent and add to new parent, can also be nil to unparent entirely
  368. ]]
  369. function Element:setParent(parent)
  370.     self.globalX, self.globalY = self:getGlobalPos(self.x, self.y)
  371.  
  372.     if self.parent then
  373.         for index, child in self.parent.children do
  374.             if child == self then
  375.                 table.remove(self.parent.children, index)
  376.                 break
  377.             end
  378.         end
  379.     end
  380.  
  381.     if parent then
  382.         self.parent = parent
  383.         table.insert(self.parent.children, self)
  384.     end
  385.    
  386.     self:setGlobalPos(self.globalX, self.globalY)
  387. end
  388.  
  389. function Element:setGlobalPos(x, y)
  390.     self.globalX = x
  391.     self.globalY = y
  392.  
  393.     if self.parent then
  394.         self.x = self.globalX - self.parent.globalX + 1
  395.         self.y = self.globalY - self.parent.globalY + 1
  396.     else
  397.         self.x = self.globalX
  398.         self.y = self.globalY
  399.     end
  400.  
  401.     for _, child in ipairs(self.children) do
  402.         child:setPos(child.x, child.y)
  403.     end
  404. end
  405.  
  406. --[[
  407.     sets new position of element relative to parent, children are moved accordingly
  408. ]]
  409. function Element:setPos(x, y)
  410.     self.x = x
  411.     self.y = y
  412.  
  413.     self.globalX, self.globalY = self:getGlobalPos(x, y)
  414.  
  415.     for _, child in ipairs(self.children) do
  416.         child:setPos(child.x, child.y)
  417.     end
  418. end
  419.  
  420. --[[
  421.     gets displacement of point x, y relative to global coordinates
  422. ]]
  423. function Element:getLocalPos(x, y)
  424.     local lx = 1 + x - self.globalX
  425.     local ly = 1 + y - self.globalY
  426.  
  427.     return lx, ly
  428. end
  429.  
  430. --[[
  431.     gets the local coordinates of an element in relation to it's parent
  432. ]]
  433. function Element:getParentLocalPos(x, y)
  434.     --global x, y
  435.     local lx = self.globalX - self.parent.globalX + 1
  436.     local ly = self.globalY - self.parent.globalY + 1
  437.  
  438.     return lx, ly
  439. end
  440.  
  441. --[[
  442.     gets the global coordinates of an element
  443. ]]
  444. function Element:getGlobalPos(x, y)
  445.     if self.parent then
  446.         return self.parent.globalX + self.x - 1, self.parent.globalY + self.y - 1
  447.     end
  448.  
  449.     return self.x, self.y
  450. end
  451.  
  452. --[[
  453.     converts to global coordinates and then test for if x and y are within bounds
  454. ]]
  455. function Element:selected(x, y)
  456.     if not self.visible then
  457.         return false
  458.     end
  459.  
  460.     return x >= self.globalX and x < self.globalX + self.width and y >= self.globalY and y < self.globalY + self.height
  461. end
  462.  
  463. --[[
  464.     set background color for element
  465. ]]
  466. function Element:setBackgroundColor(color)
  467.     self.backgroundColor = color
  468.     for x = 1, self.width do
  469.         for y = 1, self.height do
  470.             self.cells[x][y].backgroundColor = color
  471.         end
  472.     end
  473. end
  474.  
  475. --[[
  476.     set whether an element is visible or not
  477. ]]
  478. function Element:setVisibility(isVisible)
  479.     self.visible = isVisible
  480. end
  481.  
  482. --[[
  483.     draw children, then draw to parent's buffer if parent exists, otherwise draw to display
  484. ]]
  485. function Element:draw()
  486.     if not self.visible then
  487.         return
  488.     end
  489.  
  490.     if self.isBuffer then
  491.         for ly = 1, self.height do
  492.             for lx = 1, self.width do
  493.                 self.cells[lx][ly].character = " "
  494.                 self.cells[lx][ly].backgroundColor = self.backgroundColor
  495.                 self.cells[lx][ly].textColor = colors.white
  496.             end
  497.         end
  498.  
  499.         for _, child in ipairs(self.children) do
  500.             child:draw()
  501.         end
  502.     end
  503.  
  504.     --lx and ly are local x, y within an element
  505.     for ly = 1, self.height do        
  506.         for lx = 1, self.width do
  507.             local by = ly + self.globalY - self.buffer.globalY
  508.             local bx = lx + self.globalX - self.buffer.globalX
  509.  
  510.             if self.cells[lx][ly].backgroundColor ~= 0 and bx >= 1 and bx <= self.buffer.width and by >= 1 and by <= self.buffer.height then
  511.                 self.buffer.cells[bx][by].character = self.cells[lx][ly].character
  512.                 self.buffer.cells[bx][by].textColor = self.cells[lx][ly].textColor
  513.                 self.buffer.cells[bx][by].backgroundColor = self.cells[lx][ly].backgroundColor
  514.             end
  515.         end
  516.     end
  517.  
  518.     if not self.isBuffer then
  519.         for _, child in ipairs(self.children) do
  520.             child:draw()
  521.         end
  522.     end
  523. end
  524.  
  525. --------------------------------
  526. --Outline
  527. --------------------------------
  528.  
  529. Outline = Element:new {
  530.     class = "Outline",
  531. }
  532.  
  533. function Outline:new(o)
  534.     o = o or {}
  535.     o = Element:new(o)
  536.  
  537.     setmetatable(o, self)
  538.     self.__index = self
  539.  
  540.     return o
  541. end
  542.  
  543. function Outline:initializeCells()
  544.     local cell
  545.  
  546.     for x = 1, self.width do
  547.         for y = 1, self.height do
  548.             if x == 1 or x == self.width or y == 1 or y == self.height then
  549.                 cell = Cell:new { backgroundColor = self.backgroundColor }
  550.             else
  551.                 cell = Cell:new { backgroundColor = 0 }
  552.             end
  553.             if not self.cells[x] then
  554.                 table.insert(self.cells, x, {[y] = cell})
  555.             else
  556.                 table.insert(self.cells[x], y, cell)
  557.             end
  558.         end
  559.     end
  560. end
  561.  
  562. --------------------------------
  563. --Canvas
  564. --------------------------------
  565.  
  566. Canvas = Element:new {
  567.     class = "Canvas",
  568.     backgroundColor = colors.white,
  569.     currentColor = colors.black,
  570.     maxUndo = 100,
  571.     cellsHistory = {},
  572.     historyIndex = 1,
  573.     currentDrawAction = nil,
  574.     selectionBox = nil,
  575.     copiedCells = nil,
  576.     copiedCellsWidth = 0,
  577.     copiedCellsHeght = 0,
  578. }
  579.  
  580. function Canvas:new(o)
  581.     o = o or {}
  582.  
  583.     if not o.backgroundColor then
  584.         o.backgroundColor = Canvas.backgroundColor
  585.     end
  586.  
  587.     o = Element:new(o)
  588.  
  589.     setmetatable(o, self)
  590.     self.__index = self
  591.  
  592.     o:addNewHistory()
  593.  
  594.     for x = 1, o.width do
  595.         for y = 1, o.height do
  596.             table.insert(o.cellsHistory[o.historyIndex], {["x"] = x, ["y"] = y, ["backgroundColor"] = o.backgroundColor})
  597.         end
  598.     end
  599.  
  600.     o.selectionBox = Outline:new {
  601.         name = o.name.."_selectionBox",
  602.         parent = o,
  603.         buffer = o.buffer,
  604.         visible = false,
  605.     }
  606.  
  607.     o.currentDrawAction = Canvas.pen
  608.  
  609.     if not o.buffer.display.isMonitor then
  610.         registerSelectionCallback("mouse_click", o, Canvas.mouseClick, "mouseClick")
  611.         registerSelectionCallback("mouse_drag", o, Canvas.mouseDrag, "mouseDrag")
  612.         registerSelectionCallback("mouse_up", o, Canvas.mouseUp, "mouseUp")
  613.         registerSelectionCallback("key", o, Canvas.keyPressed, "keyPressed")
  614.         registerSelectionCallback("paste", o, Canvas.paste, "paste")
  615.     else
  616.         registerSelectionCallback("monitor_touch", o, Canvas.monitorTouch, "monitorTouch")
  617.     end
  618.  
  619.     return o
  620. end
  621.  
  622. --[[
  623.     sets a specific cell to a given color
  624.     x, y are local x and y values
  625. ]]
  626. function Canvas:setCell(x, y, color)
  627.     if self.cells[x][y].backgroundColor == color then
  628.         return
  629.     end
  630.  
  631.     table.insert(self.cellsHistory[self.historyIndex + 1], {["x"] = x, ["y"] = y, ["backgroundColor"] = self.cells[x][y].backgroundColor})
  632.     self.cells[x][y].backgroundColor = color
  633.     table.insert(self.cellsHistory[self.historyIndex], {["x"] = x, ["y"] = y, ["backgroundColor"] = self.cells[x][y].backgroundColor})
  634. end
  635.  
  636. --[[
  637.     add new undo history to the history table
  638. ]]
  639. function Canvas:addNewHistory()
  640.     table.insert(self.cellsHistory, self.historyIndex, {})
  641.     if #self.cellsHistory > self.maxUndo then
  642.         table.remove(self.cellsHistory, self.maxUndo + 1)
  643.     end
  644.  
  645.     for i = 1, self.historyIndex - 1 do
  646.         table.remove(self.cellsHistory, 1)
  647.     end
  648.  
  649.     self.historyIndex = 1
  650. end
  651.  
  652. --[[
  653.     utility function used for setting values in 2D arrays such as nodes in fill
  654. ]]
  655. function Canvas.setValue(tab, i, j, value)
  656.     if not tab[i] then
  657.         table.insert(tab, i, {[j] = value})
  658.     else
  659.         table.insert(tab[i], j, value)
  660.     end
  661. end
  662.  
  663. --[[
  664.     utility function used for getting values from 2D arrays such as nodes in fill
  665. ]]
  666. function Canvas.getValue(tab, i, j)
  667.     if tab and tab[i] and tab[i][j] then
  668.         return tab[i][j]
  669.     end
  670. end
  671.  
  672. --[[
  673.     changes one cell on the canvas to a different color
  674.     x, y are local x and y values
  675. ]]
  676. function Canvas:pen(x, y)
  677.     if x < 1 or x > self.width or y < 1 or y > self.height
  678.     or self.cells[x][y].backgroundColor == self.currentColor then
  679.         return
  680.     end
  681.  
  682.     self:addNewHistory()
  683.     self:setCell(x, y, self.currentColor)
  684. end
  685.  
  686. --[[
  687.     uses a flood fill algorithm to fill cells with a given color
  688.     x, y are local x and y values
  689. ]]
  690. function Canvas:fill(startX, startY)
  691.     if startX < 1 or startX > self.width or startY < 1 or startY > self.height
  692.     or self.cells[startX][startY].backgroundColor == self.currentColor then
  693.         return
  694.     end
  695.  
  696.     local colorToReplace = self.cells[startX][startY].backgroundColor
  697.  
  698.     --toProcess contains potential cells to be filled
  699.     local toProcess = {}
  700.     table.insert(toProcess, {["x"] = startX, ["y"] = startY})
  701.    
  702.     --nodes contains all already processed cells to avoid processing again
  703.     local nodes = {}
  704.     Canvas.setValue(nodes, startX, startY, colorToReplace)
  705.  
  706.     local cellsToFill = {}
  707.     local x, y
  708.  
  709.     while #toProcess ~= 0 do
  710.         x = toProcess[1].x
  711.         y = toProcess[1].y
  712.  
  713.         --[[
  714.             if current cell is of the same color as the cell selected then add all
  715.             it's adjacent neighbors to toProcess to be processed in another iteration
  716.         ]]
  717.         if self.cells[x][y].backgroundColor == colorToReplace then
  718.             table.insert(cellsToFill, {["x"] = x, ["y"] = y})
  719.  
  720.             --add adjacent, unprocessed pixels to toProcess
  721.             if x + 1 <= self.width and not Canvas.getValue(nodes, x + 1, y) then
  722.                 table.insert(toProcess, {["x"] = x + 1, ["y"] = y})
  723.                 Canvas.setValue(nodes, x + 1, y, colorToReplace)
  724.             end
  725.  
  726.             if x - 1 >= 1 and not Canvas.getValue(nodes, x - 1, y) then
  727.                 table.insert(toProcess, {["x"] = x - 1, ["y"] = y})
  728.                 Canvas.setValue(nodes, x - 1, y, colorToReplace)
  729.             end
  730.  
  731.             if y + 1 <= self.height and not Canvas.getValue(nodes, x, y + 1) then
  732.                 table.insert(toProcess, {["x"] = x, ["y"] = y + 1})
  733.                 Canvas.setValue(nodes, x, y + 1, colorToReplace)
  734.             end
  735.  
  736.             if y - 1 >= 1 and not Canvas.getValue(nodes, x, y - 1) then
  737.                 table.insert(toProcess, {["x"] = x, ["y"] = y - 1})
  738.                 Canvas.setValue(nodes, x, y - 1, colorToReplace)
  739.             end
  740.         end
  741.  
  742.         table.remove(toProcess, 1)
  743.     end
  744.  
  745.     self:addNewHistory()
  746.  
  747.     --set all cells to be filled to the currently selected color
  748.     for _, location in ipairs(cellsToFill) do
  749.         self:setCell(location.x, location.y, self.currentColor)
  750.     end
  751. end
  752.  
  753. function Canvas:clear()
  754.     self:addNewHistory()
  755.  
  756.     for x = 1, self.width do
  757.         for y = 1, self.height do
  758.             self:setCell(x, y, self.backgroundColor)
  759.         end
  760.     end
  761. end
  762.  
  763. --[[
  764.     undo previous drawing actions
  765. ]]
  766. function Canvas:undo()
  767.     if self.historyIndex >= #self.cellsHistory then
  768.         return
  769.     end
  770.  
  771.     self.historyIndex = self.historyIndex + 1
  772.  
  773.     for _, cell in ipairs(self.cellsHistory[self.historyIndex]) do
  774.         self.cells[cell.x][cell.y].backgroundColor = cell.backgroundColor
  775.     end
  776. end
  777.  
  778. --[[
  779.     redo previous drawing actions
  780. ]]
  781. function Canvas:redo()
  782.     if self.historyIndex <= 1 then
  783.         return
  784.     end
  785.  
  786.     self.historyIndex = self.historyIndex - 1
  787.  
  788.     for _, cell in ipairs(self.cellsHistory[self.historyIndex]) do
  789.         self.cells[cell.x][cell.y].backgroundColor = cell.backgroundColor
  790.     end
  791. end
  792.  
  793. --[[
  794.     copy selected region (indicated by selectionBox)
  795. ]]
  796. function Canvas:copySelection()
  797.     if not self.selectionBox.visible then
  798.         return
  799.     end
  800.  
  801.     self.copiedCells = {}
  802.  
  803.     local lx, ly = self.selectionBox:getParentLocalPos()
  804.     lx = lx
  805.     ly = ly
  806.  
  807.     local xBound = math.min(self.width, lx + self.selectionBox.width - 1)
  808.     local yBound = math.min(self.height, ly + self.selectionBox.height - 1)
  809.  
  810.     self.copiedCellsWidth = 1 + xBound - lx
  811.     self.copiedCellsHeight = 1 + yBound - ly
  812.  
  813.     local i, j
  814.  
  815.     i = 1
  816.     for x = lx, xBound do
  817.         j = 1
  818.         for y = ly, yBound do
  819.             Canvas.setValue(self.copiedCells, i, j, self.cells[x][y].backgroundColor)
  820.             j = j + 1
  821.         end
  822.         i = i + 1
  823.     end
  824. end
  825.  
  826. --[[
  827.     paste copiied selection to location of selectionBox x and y
  828. ]]
  829. function Canvas:pasteSelection()
  830.     if #self.copiedCells == 0 then
  831.         return
  832.     end
  833.  
  834.     self:addNewHistory()
  835.  
  836.     local lx, ly = self.selectionBox:getParentLocalPos()
  837.     lx = lx
  838.     ly = ly
  839.  
  840.     local xBound = math.min(self.width, lx + self.copiedCellsWidth - 1)
  841.     local yBound = math.min(self.height, ly + self.copiedCellsHeight - 1)
  842.  
  843.     local i, j
  844.  
  845.     i = 1
  846.     for x = lx, xBound do
  847.         j = 1
  848.         for y = ly, yBound do
  849.             self:setCell(x, y, Canvas.getValue(self.copiedCells, i, j))
  850.             j = j + 1
  851.         end
  852.         i = i + 1
  853.     end
  854. end
  855.  
  856. function Canvas.keyPressed(cnv, event, key, isHeld)
  857.     local keyName = keys.getName(key)
  858.     if ctrlHeld then
  859.         if keyName == "z" then
  860.             cnv:undo()
  861.         elseif keyName == "y" then
  862.             cnv:redo()
  863.         elseif keyName == "c" then
  864.             cnv:copySelection()
  865.         end
  866.     end
  867. end
  868.  
  869. function Canvas.monitorTouch(cnv, event, side, x, y)
  870.     cnv.mouseClick(cnv, "mouse_click", 1, x, y)
  871. end
  872.  
  873. function Canvas.mouseClick(cnv, event, button, x, y)
  874.     if button == 1 then
  875.         local lx = 1 + x - cnv.globalX
  876.         local ly = 1 + y - cnv.globalY
  877.  
  878.         cnv:currentDrawAction(lx, ly)
  879.     elseif button == 2 then
  880.         cnv.selectionBox.visible = false
  881.         cnv.selectionBox:setGlobalPos(x, y)
  882.         cnv.selectionBox:setWidth(1)
  883.         cnv.selectionBox:setHeight(1)
  884.     end
  885. end
  886.  
  887. function Canvas.mouseDrag(cnv, event, button, x, y)
  888.     if button == 1 then
  889.         Canvas.mouseClick(cnv, "mouse_click", 1, x, y)
  890.     elseif button == 2 then
  891.         cnv.selectionBox.visible = true
  892.  
  893.         if x >= cnv.selectionBox.globalX then
  894.             cnv.selectionBox:setWidth(1 + x - cnv.selectionBox.globalX)
  895.         end
  896.  
  897.         if y >= cnv.selectionBox.globalY then
  898.             cnv.selectionBox:setHeight(1 + y - cnv.selectionBox.globalY)
  899.         end
  900.     end
  901. end
  902.  
  903. function Canvas.mouseUp(cnv, event, button, x, y)
  904.     if button == 2 then
  905.        
  906.     end
  907. end
  908.  
  909. function Canvas.paste(cnv, event, paste)
  910.     cnv:pasteSelection()
  911. end
  912.  
  913. --------------------------------
  914. --Text
  915. --------------------------------
  916.  
  917. RowString = {
  918.     text = "",
  919.     newLineTerm = 0,
  920.     __index = function(tab, k)
  921.         if k > 0 and k <= #tab.text then
  922.             return tab.text:sub(k, k)
  923.         end
  924.         return " "
  925.     end,
  926. }
  927. setmetatable(RowString, RowString)
  928.  
  929. function RowString:new(o)
  930.     o = o or {}
  931.     setmetatable(o, self)
  932.     return o
  933. end
  934.  
  935. TextRows = {
  936.     __index = function(tab, k)
  937.         return RowString
  938.     end
  939. }
  940.  
  941. function TextRows:new(o)
  942.     o = o or {}
  943.     setmetatable(o, self)
  944.     return o
  945. end
  946.  
  947. Text = Element:new {
  948.     class = "Text",
  949.     textColor = colors.white,
  950.     text = "",
  951.     textRows = {},
  952.     padding = 0,
  953.     horizontalAlignment = align.left,
  954.     verticalAlignment = align.top,
  955.     scrollable = false,
  956.     verticalScrollOffset = 0,
  957.     wrapText = true,
  958.     horizontalScrollOffset = 0,
  959.     longestRowLength = 0,
  960. }
  961.  
  962. function Text:new(o)
  963.     o = o or {}
  964.     o = Element:new(o)
  965.  
  966.     --tables are passed by reference so new ones must be created
  967.     o.textRows = TextRows:new()
  968.     o.horizontalOffsets = {}
  969.  
  970.     setmetatable(o, self)
  971.     self.__index = self
  972.  
  973.     for x = 1, o.width do
  974.         for y = 1, o.height do
  975.             o.cells[x][y] = Cell:new{
  976.                 backgroundColor = o.backgroundColor,
  977.                 textColor = o.textColor,
  978.             }
  979.         end
  980.     end
  981.  
  982.     o:setStartRowIndexFunction()
  983.     o:setStartSubstringIndexFunction()
  984.     o:setText(o.text)
  985.  
  986.     if o.name then
  987.         registerSelectionCallback("mouse_scroll", o, Text.textScroll, "textScroll")
  988.     end
  989.  
  990.     return o
  991. end
  992.  
  993. function Text:horizontalScroll(scrollDir)
  994.     self.horizontalScrollOffset = self.horizontalScrollOffset + scrollDir
  995.  
  996.     local maxHorizontalScroll = math.max(self.longestRowLength - self.width + 2 * self.padding, 0)
  997.  
  998.     if self.horizontalScrollOffset < 0 then
  999.         self.horizontalScrollOffset = 0
  1000.     elseif self.horizontalScrollOffset > maxHorizontalScroll then
  1001.         self.horizontalScrollOffset = maxHorizontalScroll
  1002.     end
  1003.     self:updateCells()
  1004. end
  1005.  
  1006. function Text:verticalScroll(scrollDir)
  1007.     self.verticalScrollOffset = self.verticalScrollOffset + scrollDir
  1008.  
  1009.     local maxVerticalScroll = math.max(#self.textRows - self.height + 2 * self.padding, 0)
  1010.  
  1011.     if self.verticalScrollOffset < 0 then
  1012.         self.verticalScrollOffset = 0
  1013.     elseif self.verticalScrollOffset > maxVerticalScroll then
  1014.         self.verticalScrollOffset = maxVerticalScroll
  1015.     end
  1016.     self:updateCells()
  1017. end
  1018.  
  1019. function Text.textScroll(txt, event, scrollDir, x, y)
  1020.     if shiftHeld then
  1021.         txt:horizontalScroll(scrollDir)
  1022.     else
  1023.         txt:verticalScroll(scrollDir)
  1024.     end
  1025. end
  1026.  
  1027. --[[
  1028.     black magic is used to compute the substringIndex and rowIndex offsets dependent on the alignment and current scroll offset
  1029. ]]
  1030. Text.computeIndex = {
  1031.     getStartSubstringIndexLeftAligned = function(txt, rowIndex)
  1032.         return 1 - txt.padding + txt.horizontalScrollOffset
  1033.     end,
  1034.     getStartSubstringIndexRightAligned = function(txt, rowIndex)
  1035.         return 1 + txt.padding + #txt.textRows[rowIndex].text - txt.width + txt.horizontalScrollOffset
  1036.         + math.min(0, -txt.longestRowLength + txt.width - 2 * txt.padding)
  1037.     end,
  1038.     getStartSubstringIndexCenterAligned = function(txt, rowIndex)
  1039.         return 1 - txt.padding + math.ceil((#txt.textRows[rowIndex].text - txt.width + 2 * txt.padding) / 2) + txt.horizontalScrollOffset
  1040.         + math.min(0, math.floor((-txt.longestRowLength + txt.width - 2 * txt.padding) / 2))
  1041.     end,
  1042.     getStartRowIndexTopAligned = function(txt)
  1043.         return 1 - txt.padding + txt.verticalScrollOffset
  1044.     end,
  1045.     getStartRowIndexBottomAligned = function(txt)
  1046.         return 1 + txt.padding + #txt.textRows - txt.height + txt.verticalScrollOffset
  1047.         + math.min(0, -#txt.textRows + txt.height - 2 * txt.padding)
  1048.     end,
  1049.     getStartRowIndexCenterAligned = function(txt)
  1050.         return 1 - txt.padding + math.ceil((#txt.textRows - txt.height + 2 * txt.padding) / 2) + txt.verticalScrollOffset
  1051.         + math.min(0, math.floor((-#txt.textRows + txt.height - 2 * txt.padding) / 2))
  1052.     end,
  1053. }
  1054.  
  1055. function Text:setStartSubstringIndexFunction()
  1056.     if self.horizontalAlignment == align.left then
  1057.         self.getStartSubstringIndex = Text.computeIndex.getStartSubstringIndexLeftAligned
  1058.     elseif self.horizontalAlignment == align.right then
  1059.         self.getStartSubstringIndex = Text.computeIndex.getStartSubstringIndexRightAligned
  1060.     elseif self.horizontalAlignment == align.center then
  1061.         self.getStartSubstringIndex = Text.computeIndex.getStartSubstringIndexCenterAligned
  1062.     end
  1063. end
  1064.  
  1065. function Text:setStartRowIndexFunction()
  1066.     if self.verticalAlignment == align.top then
  1067.         self.getStartRowIndex = Text.computeIndex.getStartRowIndexTopAligned
  1068.     elseif self.verticalAlignment == align.bottom then
  1069.         self.getStartRowIndex = Text.computeIndex.getStartRowIndexBottomAligned
  1070.     elseif self.verticalAlignment == align.center then
  1071.         self.getStartRowIndex = Text.computeIndex.getStartRowIndexCenterAligned
  1072.     end
  1073. end
  1074.  
  1075. --[[
  1076.     set the horizontal alignment and update cells accordingly
  1077. ]]
  1078. function Text:setHorizontalAlignment(alignment)
  1079.     self.horizontalAlignment = alignment
  1080.     self:setStartSubstringIndexFunction()
  1081.     self:updateCells()
  1082. end
  1083.  
  1084. --[[
  1085.     set the vertical alignment and update cells accordingly
  1086. ]]
  1087. function Text:setVerticalAlignment(alignment)
  1088.     self.verticalAlignment = alignment
  1089.     self:setStartRowIndexFunction()
  1090.     self:updateCells()
  1091. end
  1092.  
  1093. --[[
  1094.     update rows of text according to current text string and whether to wrap text or not
  1095. ]]
  1096. function Text:updateTextRows()
  1097.     self.textRows = TextRows:new()
  1098.  
  1099.     local stringBegin = 1
  1100.     local rowWidth = self.width - self.padding * 2
  1101.     local stringEnd = stringBegin + rowWidth - 1
  1102.     local newLineIndex = self.text:find("\n", stringBegin, true)
  1103.     local substring
  1104.     local index = 1
  1105.  
  1106.     self.longestRowLength = 0
  1107.  
  1108.     while stringBegin <= #self.text do
  1109.         if newLineIndex and newLineIndex < stringBegin then
  1110.             newLineIndex = self.text:find("\n", stringBegin, true)
  1111.         end
  1112.        
  1113.         if newLineIndex and (newLineIndex < stringEnd or not self.wrapText) then
  1114.             substring = RowString:new { text = self.text:sub(stringBegin, newLineIndex - 1), newLineTerm = 1 }
  1115.             stringBegin = newLineIndex + 1
  1116.         elseif not self.wrapText then
  1117.             substring = RowString:new { text = self.text:sub(stringBegin), newLineTerm = 0}
  1118.             stringBegin = stringBegin + #substring.text
  1119.         else
  1120.             substring = RowString:new { text = self.text:sub(stringBegin, stringEnd), newLineTerm = 0 }
  1121.             stringBegin = stringEnd + 1
  1122.         end
  1123.        
  1124.         stringEnd = stringBegin + rowWidth - 1
  1125.        
  1126.         table.insert(self.textRows, substring)
  1127.  
  1128.         if #self.textRows[index].text > self.longestRowLength then
  1129.             self.longestRowLength = #self.textRows[index].text
  1130.         end
  1131.  
  1132.         index = index + 1
  1133.     end
  1134. end
  1135.  
  1136. --[[
  1137.     write characters to cells data table according to scroll offsets and alignments
  1138. ]]
  1139. function Text:updateCells()
  1140.     local rowIndex = self:getStartRowIndex()
  1141.     local substringIndex
  1142.  
  1143.     for y = 1, self.height do
  1144.         substringIndex = self:getStartSubstringIndex(rowIndex)
  1145.  
  1146.         if y > self.padding and y < self.height - self.padding + 1 then
  1147.             for x = 1, self.width do
  1148.                 if x > self.padding and x < self.width - self.padding + 1 then
  1149.                     self.cells[x][y].character = self.textRows[rowIndex][substringIndex]
  1150.                 end
  1151.  
  1152.                 substringIndex = substringIndex + 1
  1153.             end
  1154.         end
  1155.  
  1156.         rowIndex = rowIndex + 1
  1157.     end
  1158. end
  1159.  
  1160. --[[
  1161.     set the text for a Text element
  1162. ]]
  1163. function Text:setText(text)
  1164.     self.text = text
  1165.  
  1166.     self:updateTextRows()
  1167.     self:horizontalScroll(0)
  1168.     self:verticalScroll(0)
  1169.     self:updateCells()
  1170. end
  1171.  
  1172. --[[
  1173.     set the text color for a Text element
  1174. ]]
  1175. function Text:setTextColor(color)
  1176.     for x = 1, self.width do
  1177.         for y = 1, self.height do
  1178.             self.cells[x][y].textColor = color
  1179.         end
  1180.     end
  1181. end
  1182.  
  1183. --------------------------------
  1184. --Button
  1185. --------------------------------
  1186.  
  1187. Button = Text:new {
  1188.     onClickName = nil,
  1189.     onClick = nil,
  1190. }
  1191.  
  1192. function Button:new(o)
  1193.     o = o or {}
  1194.     o = Text:new(o)
  1195.  
  1196.     setmetatable(o, self)
  1197.     self.__index = self
  1198.  
  1199.     if not o.buffer.display.isMonitor then
  1200.         registerSelectionCallback("mouse_click", o, Button.mouseClick, o.onClickName)
  1201.     else
  1202.         registerSelectionCallback("monitor_touch", o, Button.monitorTouch, o.onClickName)
  1203.     end
  1204.  
  1205.     return o
  1206. end
  1207.  
  1208. function Button.mouseClick(btn, event, button, x, y)
  1209.     if btn.onClick and button == 1 then
  1210.         btn:onClick()
  1211.     end
  1212. end
  1213.  
  1214. function Button.monitorTouch(btn, event, side, x, y)
  1215.     if btn.onClick then
  1216.         btn:onClick()
  1217.     end
  1218. end
  1219.  
  1220. --------------------------------
  1221. --Textbox
  1222. --------------------------------
  1223.  
  1224. TextEdit = {
  1225.     index = 0,
  1226.     string = "",
  1227.     replacement = "",
  1228.     previousCursorPos = 0,
  1229. }
  1230.  
  1231. function TextEdit:new(o)
  1232.     o = o or {}
  1233.  
  1234.     setmetatable(o, self)
  1235.     self.__index = self
  1236.  
  1237.     return o
  1238. end
  1239.  
  1240. Textbox = Text:new{
  1241.     class = "Textbox",
  1242.     cursorPos = 0,
  1243.     cursorBackgroundColor = colors.white,
  1244.     cursorTextColor = colors.black,
  1245.     cursorRowOffset = 0,
  1246.     cursorRowIndex = 1,
  1247.     enterSubmits = false,
  1248.  
  1249.     selecting = false,
  1250.     selectionStartIndex = nil,
  1251.     selectionEndIndex = nil,
  1252.     selectionBackgroundColor = colors.gray,
  1253.     selectionTextColor = colors.lightGray,
  1254.  
  1255.     maxUndo = 500,
  1256.     historyIndex = 0,
  1257.     changeHistory = {},
  1258.    
  1259.     autoCompleteEnable = false,
  1260.     autoCompleting = false,
  1261.     allAutoCompleteChoices = {},
  1262.     currentAutoCompleteChoices = {},
  1263.     currentChoiceIndex = 1,
  1264.     autoCompletePos = 0,
  1265. }
  1266.  
  1267. function Textbox:new(o)
  1268.     o = o or {}
  1269.  
  1270.     o.changeHistory = {}
  1271.  
  1272.     o.allAutoCompleteChoices = o.allAutoCompleteChoices or {}
  1273.     o.currentAutoCompleteChoices = {}
  1274.  
  1275.     o = Text:new(o)
  1276.  
  1277.     setmetatable(o, self)
  1278.     self.__index = self
  1279.  
  1280.     o:addNewHistory()
  1281.  
  1282.     --o:setHorizontalAlignment(o.horizontalAlignment)
  1283.     --o:setVerticalAlignment(o.verticalAlignment)
  1284.  
  1285.     o:setCursorPos(0)
  1286.  
  1287.     if o.name then
  1288.         registerSelectionCallback("char", o, Textbox.characterTyped, "characterTyped")
  1289.         registerSelectionCallback("key", o, Textbox.keyPressed, "keyPressed")
  1290.         registerSelectionCallback("mouse_click", o, Textbox.mouseClicked, "mouseClicked")
  1291.         registerSelectionCallback("mouse_drag", o, Textbox.mouseDragged, "mouseDragged")
  1292.  
  1293.         registerSelectionCallback("monitor_touch", o, Textbox.monitorTouched, "monitorTouched")
  1294.  
  1295.         removeSelectionCallback("mouse_scroll", o, "textScroll")
  1296.         registerSelectionCallback("mouse_scroll", o, Textbox.textScroll, "textScroll")
  1297.     end
  1298.  
  1299.     return o
  1300. end
  1301.  
  1302. --[[
  1303.     scroll texbox element and update cursor position
  1304. ]]
  1305. function Textbox.textScroll(txb, event, scrollDir, x, y)
  1306.     txb:eraseSelection()
  1307.     txb:eraseCursor()
  1308.     if shiftHeld then
  1309.         txb:horizontalScroll(scrollDir)
  1310.     else
  1311.         txb:verticalScroll(scrollDir)
  1312.     end
  1313.     txb:drawSelection()
  1314.     txb:drawCursor()
  1315. end
  1316.  
  1317. --[[
  1318.     convert from cursor position to cursor Y value
  1319. ]]
  1320. function Textbox:getCursorPosY()
  1321.     return 1 - self:getStartRowIndex() + self.cursorRowIndex
  1322. end
  1323.  
  1324. --[[
  1325.     convert from cursor position to cursor X value
  1326. ]]
  1327. function Textbox:getCursorPosX()
  1328.     return 2 - self:getStartSubstringIndex(self.cursorRowIndex) + self.cursorRowOffset
  1329. end
  1330.  
  1331. --[[
  1332.     compute cursorRowIndex and cursorRowOffset based on cursor position
  1333. ]]
  1334. function Textbox:computeRowIndexAndOffset(textPosition)
  1335.     local stringIndex = 0
  1336.     local rowIndex = 1
  1337.     local rowOffset = 0
  1338.  
  1339.     local foundCursorPos = false
  1340.  
  1341.     for index, textData in ipairs(self.textRows) do
  1342.         if stringIndex + #textData.text >= textPosition then
  1343.             rowIndex = index
  1344.             rowOffset = textPosition - stringIndex
  1345.             foundCursorPos = true
  1346.             break
  1347.         else
  1348.             stringIndex = stringIndex + #textData.text + textData.newLineTerm
  1349.         end
  1350.     end
  1351.  
  1352.     if not foundCursorPos then
  1353.         rowIndex = #self.textRows + 1
  1354.         rowOffset = 0
  1355.     end
  1356.  
  1357.     return rowIndex, rowOffset
  1358. end
  1359.  
  1360. --[[
  1361.     compute the cursor position based on the cursorRowIndex and cursorRowOffset
  1362. ]]
  1363. function Textbox:computeCursorPos()
  1364.     self.cursorPos = 0
  1365.  
  1366.     for index = 1, self.cursorRowIndex - 1 do
  1367.         self.cursorPos = self.cursorPos + #self.textRows[index].text + self.textRows[index].newLineTerm
  1368.     end
  1369.  
  1370.     self.cursorPos = self.cursorPos + self.cursorRowOffset
  1371. end
  1372.  
  1373. --[[
  1374.     erase cursor position (overwrite with default background and text color)
  1375. ]]
  1376. function Textbox:eraseCursor()
  1377.     local prevCursorX = self:getCursorPosX()
  1378.     local prevCursorY = self:getCursorPosY()
  1379.  
  1380.     if prevCursorX > 0 and prevCursorX <= self.width and prevCursorY > 0 and prevCursorY <= self.height then
  1381.         self.cells[prevCursorX][prevCursorY].backgroundColor = self.backgroundColor
  1382.         self.cells[prevCursorX][prevCursorY].textColor = self.textColor
  1383.     end
  1384. end
  1385.  
  1386. --[[
  1387.     draw the cursor to the screen
  1388. ]]
  1389. function Textbox:drawCursor()
  1390.     local cursorX = self:getCursorPosX()
  1391.     local cursorY = self:getCursorPosY()
  1392.  
  1393.     if cursorX > 0 and cursorX <= self.width and cursorY > 0 and cursorY <= self.height then
  1394.         self.cells[cursorX][cursorY].backgroundColor = self.cursorBackgroundColor
  1395.         self.cells[cursorX][cursorY].textColor = self.cursorTextColor
  1396.     end
  1397. end
  1398.  
  1399. --[[
  1400.     erase selection highlighting
  1401. ]]
  1402. function Textbox:eraseSelection()
  1403.     if not self.selecting then
  1404.         return
  1405.     end
  1406.  
  1407.     local minSelection = math.min(self.selectionStartIndex, self.selectionEndIndex)
  1408.     local maxSelection = math.max(self.selectionStartIndex, self.selectionEndIndex)
  1409.  
  1410.     local selectionStartRow, selectionStartOffset = self:computeRowIndexAndOffset(minSelection)
  1411.     local selectionEndRow, selectionEndOffset = self:computeRowIndexAndOffset(maxSelection)
  1412.  
  1413.     local rowIndex = self:getStartRowIndex()
  1414.     local substringIndex
  1415.  
  1416.     for y = 1, self.height do
  1417.         substringIndex = self:getStartSubstringIndex(rowIndex)
  1418.  
  1419.         for x = 1, self.width do
  1420.             if selectionStartRow == selectionEndRow and rowIndex == selectionStartRow and substringIndex > selectionStartOffset and substringIndex <= selectionEndOffset
  1421.             or selectionStartRow ~= selectionEndRow
  1422.             and (rowIndex == selectionStartRow and substringIndex > selectionStartOffset and substringIndex <= #self.textRows[rowIndex].text
  1423.             or rowIndex == selectionEndRow and substringIndex > 0 and substringIndex <= selectionEndOffset
  1424.             or rowIndex > selectionStartRow and rowIndex < selectionEndRow and substringIndex > 0 and substringIndex <= #self.textRows[rowIndex].text) then
  1425.                 self.cells[x][y].backgroundColor = self.backgroundColor
  1426.                 self.cells[x][y].textColor = self.textColor
  1427.             end
  1428.  
  1429.             substringIndex = substringIndex + 1
  1430.         end
  1431.  
  1432.         rowIndex = rowIndex + 1
  1433.     end
  1434. end
  1435.  
  1436. --[[
  1437.     draw highlighted selection
  1438. ]]
  1439. function Textbox:drawSelection()
  1440.     if not self.selecting then
  1441.         return
  1442.     end
  1443.  
  1444.     local minSelection = math.min(self.selectionStartIndex, self.selectionEndIndex)
  1445.     local maxSelection = math.max(self.selectionStartIndex, self.selectionEndIndex)
  1446.  
  1447.     local selectionStartRow, selectionStartOffset = self:computeRowIndexAndOffset(minSelection)
  1448.     local selectionEndRow, selectionEndOffset = self:computeRowIndexAndOffset(maxSelection)
  1449.  
  1450.     local rowIndex = self:getStartRowIndex()
  1451.     local substringIndex
  1452.  
  1453.     for y = 1, self.height do
  1454.         substringIndex = self:getStartSubstringIndex(rowIndex)
  1455.  
  1456.         for x = 1, self.width do
  1457.             if selectionStartRow == selectionEndRow and rowIndex == selectionStartRow and substringIndex > selectionStartOffset and substringIndex <= selectionEndOffset
  1458.             or selectionStartRow ~= selectionEndRow
  1459.             and (rowIndex == selectionStartRow and substringIndex > selectionStartOffset and substringIndex <= #self.textRows[rowIndex].text
  1460.             or rowIndex == selectionEndRow and substringIndex > 0 and substringIndex <= selectionEndOffset
  1461.             or rowIndex > selectionStartRow and rowIndex < selectionEndRow and substringIndex > 0 and substringIndex <= #self.textRows[rowIndex].text) then
  1462.                 self.cells[x][y].backgroundColor = self.selectionBackgroundColor
  1463.                 self.cells[x][y].textColor = self.selectionTextColor
  1464.             end
  1465.  
  1466.             substringIndex = substringIndex + 1
  1467.         end
  1468.  
  1469.         rowIndex = rowIndex + 1
  1470.     end
  1471. end
  1472.  
  1473. --[[
  1474.     move the view buffer along with the cursor
  1475. ]]
  1476. function Textbox:scrollCursorIntoBounds()
  1477.     local cursorX = self:getCursorPosX()
  1478.     local cursorY = self:getCursorPosY()
  1479.  
  1480.     local leftScrollOffset = cursorX - self.padding - 1
  1481.     local rightScrollOffset = cursorX - self.width + self.padding  
  1482.     local upScrollOffset = cursorY - self.padding - 1
  1483.     local downScrollOffset = cursorY - self.height + self.padding
  1484.  
  1485.     if leftScrollOffset < 0 then
  1486.         self:horizontalScroll(leftScrollOffset)
  1487.     elseif rightScrollOffset > 0 then
  1488.         self:horizontalScroll(rightScrollOffset)
  1489.     end
  1490.  
  1491.     if upScrollOffset < 0 then
  1492.         self:verticalScroll(upScrollOffset)
  1493.     elseif downScrollOffset > 0 then
  1494.         self:verticalScroll(downScrollOffset)
  1495.     end
  1496. end
  1497.  
  1498. --[[
  1499.     set cursor position to new position
  1500. ]]
  1501. function Textbox:setCursorPos(newCursorPos)
  1502.     if newCursorPos < 0 or newCursorPos > #self.text then
  1503.         self:drawSelection()
  1504.         self:drawCursor()
  1505.         return
  1506.     end
  1507.  
  1508.     self:eraseSelection()
  1509.     self:eraseCursor()    
  1510.  
  1511.     self.cursorPos = newCursorPos
  1512.     self.cursorRowIndex, self.cursorRowOffset = self:computeRowIndexAndOffset(self.cursorPos)
  1513.  
  1514.     self:scrollCursorIntoBounds()
  1515.  
  1516.     self:drawSelection()
  1517.     self:drawCursor()
  1518. end
  1519.  
  1520. --[[
  1521.     set cursor position based on an x and y input
  1522. ]]
  1523. function Textbox:setCursorPosXY(x, y)
  1524.     self:eraseSelection()
  1525.     self:eraseCursor()
  1526.  
  1527.     if #self.textRows ~= 0 then
  1528.         self.cursorRowIndex = math.min(#self.textRows + self.textRows[#self.textRows].newLineTerm, math.max(1, y - 1 + self:getStartRowIndex()))
  1529.     else
  1530.         self.cursorRowIndex = 1
  1531.     end
  1532.  
  1533.     self.cursorRowOffset = math.min(#self.textRows[self.cursorRowIndex].text, math.max(0, x - 2 + self:getStartSubstringIndex(self.cursorRowIndex)))
  1534.  
  1535.     self:computeCursorPos()
  1536.     self:scrollCursorIntoBounds()
  1537.  
  1538.     self:drawSelection()
  1539.     self:drawCursor()
  1540. end
  1541.  
  1542. --[[
  1543.     add new undo history to the history table
  1544. ]]
  1545. function Textbox:addNewHistory()
  1546.     table.insert(self.changeHistory, self.historyIndex, {})
  1547.     if #self.changeHistory > self.maxUndo then
  1548.         table.remove(self.changeHistory, self.maxUndo + 1)
  1549.     end
  1550.  
  1551.     for i = 1, self.historyIndex - 1 do
  1552.         table.remove(self.changeHistory, 1)
  1553.     end
  1554.  
  1555.     self.historyIndex = 1
  1556. end
  1557.  
  1558. --[[
  1559.     undo previous text editing actions
  1560. ]]
  1561. function Textbox:undo()
  1562.     if self.historyIndex >= #self.changeHistory then
  1563.         return
  1564.     end
  1565.  
  1566.     if self.selecting then
  1567.         self:eraseSelection()
  1568.         self:stopSelecting()
  1569.     end
  1570.  
  1571.     local change = self.changeHistory[self.historyIndex]
  1572.     self:setText(self.text:sub(0, change.index - 1)..change.string..self.text:sub(change.index + #change.replacement))
  1573.     self:setCursorPos(change.previousCursorPos)
  1574.  
  1575.     self.historyIndex = self.historyIndex + 1
  1576. end
  1577.  
  1578. --[[
  1579.     redo previous text editing actions
  1580. ]]
  1581. function Textbox:redo()
  1582.     if self.historyIndex <= 1 then
  1583.         return
  1584.     end
  1585.  
  1586.     if self.selecting then
  1587.         self:eraseSelection()
  1588.         self:stopSelecting()
  1589.     end
  1590.  
  1591.     self.historyIndex = self.historyIndex - 1
  1592.  
  1593.     local change = self.changeHistory[self.historyIndex]
  1594.     self:setText(self.text:sub(0, change.index - 1)..change.replacement..self.text:sub(change.index + #change.string))
  1595.     self:setCursorPos(change.nextCursorPos)
  1596. end
  1597.  
  1598. function Textbox:getAutoCompletionChoices()
  1599.     local wordIndex = self:findStartOfCurrentWord()
  1600.     local startOfWord = self.text:sub(wordIndex, self.cursorPos)
  1601.     local indexInWord = 1 + self.cursorPos - wordIndex
  1602.  
  1603.     self.currentAutoCompleteChoices = {}
  1604.     for _, word in ipairs(self.allAutoCompleteChoices) do
  1605.         if word:sub(1, indexInWord) == startOfWord and word:sub(indexInWord + 1) ~= "" then
  1606.             table.insert(self.currentAutoCompleteChoices, word:sub(indexInWord + 1))
  1607.         end
  1608.     end
  1609.  
  1610.     self.currentChoiceIndex = 1
  1611.     self.autoCompleting = (#self.currentAutoCompleteChoices ~= 0)
  1612. end
  1613.  
  1614. function Textbox:showAutoCompletionChoice()
  1615.     if self.autoCompleting then
  1616.         local currentChoice = self.currentAutoCompleteChoices[self.currentChoiceIndex]
  1617.         self:setSelectionStart(self.cursorPos)
  1618.        
  1619.         self:setText(self.text:sub(0, self.cursorPos)..currentChoice..self.text:sub(self.cursorPos + 1))
  1620.         self:setSelectionEnd(self.selectionStartIndex + #currentChoice)
  1621.     end
  1622. end
  1623.  
  1624. function Textbox:eraseAutoCompletionChoice()
  1625.     if self.autoCompleting then
  1626.         self:eraseSelection()
  1627.         self:stopSelecting()
  1628.         self:setText(self.text:sub(0, self.cursorPos)..self.text:sub(1 + self.cursorPos + #self.currentAutoCompleteChoices[self.currentChoiceIndex]))
  1629.     end
  1630. end
  1631.  
  1632. --[[
  1633.     insert string at cursor position and increment cursor position
  1634. ]]
  1635. function Textbox:insert(string)
  1636.     self:addNewHistory()
  1637.     if self.selecting then
  1638.         local minSelection = math.min(self.selectionStartIndex, self.selectionEndIndex)
  1639.         local maxSelection = math.max(self.selectionStartIndex, self.selectionEndIndex)
  1640.        
  1641.         if not self.autoCompleting then
  1642.             self.changeHistory[self.historyIndex] = TextEdit:new {
  1643.                 index = minSelection + 1,
  1644.                 string = self.text:sub(minSelection + 1, maxSelection),
  1645.                 replacement = string,
  1646.                 previousCursorPos = self.cursorPos,
  1647.                 nextCursorPos = minSelection + #string,
  1648.             }
  1649.         else
  1650.             self.changeHistory[self.historyIndex] = TextEdit:new {
  1651.                 index = self.cursorPos + 1,
  1652.                 replacement = string,
  1653.                 previousCursorPos = self.cursorPos,
  1654.                 nextCursorPos = self.cursorPos + #string
  1655.             }
  1656.         end
  1657.  
  1658.         self:setCursorPos(minSelection)
  1659.         self:eraseSelection()
  1660.         self:setText(self.text:sub(0, minSelection)..self.text:sub(maxSelection + 1))
  1661.         self:stopSelecting()
  1662.     else
  1663.         self.changeHistory[self.historyIndex] = TextEdit:new {
  1664.             index = self.cursorPos + 1,
  1665.             replacement = string,
  1666.             previousCursorPos = self.cursorPos,
  1667.             nextCursorPos = self.cursorPos + #string
  1668.         }
  1669.     end
  1670.    
  1671.     self:setText(self.text:sub(0, self.cursorPos)..string..self.text:sub(self.cursorPos + 1))
  1672.     self:setCursorPos(self.cursorPos + #string)
  1673.  
  1674.     self:getAutoCompletionChoices()
  1675.     self:showAutoCompletionChoice()
  1676. end
  1677.  
  1678. function Textbox:findStartOfCurrentWord()
  1679.     local spaceIndex
  1680.     local nextSpaceIndex = self.text:find(" ")
  1681.  
  1682.     while nextSpaceIndex and nextSpaceIndex <= self.cursorPos do
  1683.         spaceIndex = nextSpaceIndex
  1684.         nextSpaceIndex = self.text:find(" ", spaceIndex + 1)
  1685.     end
  1686.  
  1687.     if not spaceIndex then
  1688.         return 1
  1689.     end
  1690.  
  1691.     return spaceIndex + 1
  1692. end
  1693.  
  1694. --[[
  1695.     reset selection variables
  1696. ]]
  1697. function Textbox:stopSelecting()
  1698.     self.selecting = false
  1699.     self.selectionStartIndex = nil
  1700.     self.selectionEndIndex = nil
  1701. end
  1702.  
  1703. --[[
  1704.     sets the beginning of the selection area
  1705. ]]
  1706. function Textbox:setSelectionStart(textIndex)
  1707.     self:eraseSelection()
  1708.     self:stopSelecting()
  1709.     self.selectionStartIndex = textIndex
  1710. end
  1711.  
  1712. function Textbox:setSelectionEnd(textIndex)
  1713.     self:eraseSelection()
  1714.     self.selectionEndIndex = textIndex
  1715.     self.selecting = true
  1716.     self:drawSelection()
  1717. end
  1718.  
  1719. --[[
  1720.     insert characters as they are received at cursor position while incrementing cursor position
  1721. ]]
  1722. function Textbox.characterTyped(txb, event, character)
  1723.     txb:insert(character)
  1724. end
  1725.  
  1726. --[[
  1727.     handle key inputs for textbox element
  1728. ]]
  1729. function Textbox.keyPressed(txb, event, key, isHeld)
  1730.     local keyName = keys.getName(key)
  1731.  
  1732.     local minSelection, maxSelection
  1733.     if txb.selecting then
  1734.         minSelection = math.min(txb.selectionStartIndex, txb.selectionEndIndex)
  1735.         maxSelection = math.max(txb.selectionStartIndex, txb.selectionEndIndex)
  1736.     end
  1737.  
  1738.     if ctrlHeld and keyName == "a" then
  1739.         txb:setSelectionStart(0)
  1740.         txb:setCursorPos(#txb.text)
  1741.         txb:setSelectionEnd(txb.cursorPos)
  1742.         txb:drawCursor()
  1743.     elseif keyName == "left" and txb.cursorPos > 0 then
  1744.         txb:eraseAutoCompletionChoice()
  1745.         txb.autoCompleting = false
  1746.  
  1747.         if shiftHeld then
  1748.             if not txb.selecting then
  1749.                 txb:setSelectionStart(txb.cursorPos)
  1750.             end
  1751.             txb:setCursorPos(txb.cursorPos - 1)
  1752.             txb:setSelectionEnd(txb.cursorPos)
  1753.             txb:drawCursor()
  1754.         elseif txb.selecting then
  1755.             txb:setCursorPos(minSelection)
  1756.             txb:eraseSelection()
  1757.             txb:stopSelecting()
  1758.             txb:drawCursor()
  1759.         else
  1760.             txb:setCursorPos(txb.cursorPos - 1)
  1761.         end
  1762.     elseif keyName == "right" and txb.cursorPos <= #txb.text then
  1763.         txb:eraseAutoCompletionChoice()
  1764.         txb.autoCompleting = false
  1765.  
  1766.         if shiftHeld then
  1767.             if not txb.selecting then
  1768.                 txb:setSelectionStart(txb.cursorPos)
  1769.             end
  1770.             txb:setCursorPos(txb.cursorPos + 1)
  1771.             txb:setSelectionEnd(txb.cursorPos)
  1772.             txb:drawCursor()
  1773.         elseif txb.selecting then
  1774.             txb:setCursorPos(maxSelection)
  1775.             txb:eraseSelection()
  1776.             txb:stopSelecting()
  1777.             txb:drawCursor()
  1778.         else
  1779.             txb:setCursorPos(txb.cursorPos + 1)
  1780.         end
  1781.     elseif keyName == "up" then
  1782.         if txb.autoCompleting then
  1783.             txb:eraseAutoCompletionChoice()
  1784.             txb.currentChoiceIndex = (txb.currentChoiceIndex + #txb.currentAutoCompleteChoices) % #txb.currentAutoCompleteChoices + 1
  1785.             txb:showAutoCompletionChoice()
  1786.         else
  1787.             local cursorX = txb:getCursorPosX()
  1788.             local cursorY = txb:getCursorPosY()
  1789.  
  1790.             if shiftHeld then
  1791.                 if not txb.selecting then
  1792.                     txb:setSelectionStart(txb.cursorPos)
  1793.                 end
  1794.                 txb:setCursorPosXY(cursorX, cursorY - 1)
  1795.                 txb:setSelectionEnd(txb.cursorPos)
  1796.                 txb:drawCursor()
  1797.             elseif txb.selecting then
  1798.                 txb:setCursorPos(minSelection)
  1799.                 cursorX = txb:getCursorPosX()
  1800.                 cursorY = txb:getCursorPosY()
  1801.                 txb:setCursorPosXY(cursorX, cursorY - 1)
  1802.                 txb:eraseSelection()
  1803.                 txb:stopSelecting()
  1804.                 txb:drawCursor()
  1805.             else
  1806.                 txb:setCursorPosXY(cursorX, cursorY - 1)
  1807.             end
  1808.         end
  1809.     elseif keyName == "down" then
  1810.         if txb.autoCompleting then
  1811.             txb:eraseAutoCompletionChoice()
  1812.             txb.currentChoiceIndex = (txb.currentChoiceIndex + 1) % #txb.currentAutoCompleteChoices + 1
  1813.             txb:showAutoCompletionChoice()
  1814.         else
  1815.             local cursorX = txb:getCursorPosX()
  1816.             local cursorY = txb:getCursorPosY()
  1817.  
  1818.             if shiftHeld then
  1819.                 if not txb.selecting then
  1820.                     txb:setSelectionStart(txb.cursorPos)
  1821.                 end
  1822.                 txb:setCursorPosXY(cursorX, cursorY + 1)
  1823.                 txb:setSelectionEnd(txb.cursorPos)
  1824.                 txb:drawCursor()
  1825.             elseif txb.selecting then
  1826.                 txb:setCursorPos(maxSelection)
  1827.                 cursorX = txb:getCursorPosX()
  1828.                 cursorY = txb:getCursorPosY()
  1829.                 txb:setCursorPosXY(cursorX, cursorY + 1)
  1830.                 txb:eraseSelection()
  1831.                 txb:stopSelecting()
  1832.                 txb:drawCursor()
  1833.             else
  1834.                 txb:setCursorPosXY(cursorX, cursorY + 1)
  1835.             end
  1836.         end
  1837.     elseif keyName == "enter" and not txb.enterSubmits then
  1838.         txb:eraseSelection()
  1839.         txb:eraseCursor()
  1840.         if txb.autoCompleting then
  1841.             txb:eraseAutoCompletionChoice()
  1842.             txb.autoCompleting = false
  1843.             txb:insert(txb.currentAutoCompleteChoices[txb.currentChoiceIndex])
  1844.         else
  1845.             txb:insert("\n")
  1846.         end
  1847.     elseif keyName == "backspace" then
  1848.         txb:eraseAutoCompletionChoice()
  1849.         txb.autoCompleting = false
  1850.  
  1851.         if txb.selecting then
  1852.             txb:addNewHistory()
  1853.             txb.changeHistory[txb.historyIndex] = TextEdit:new {
  1854.                 index = minSelection + 1,
  1855.                 string = txb.text:sub(minSelection + 1, maxSelection),
  1856.                 previousCursorPos = txb.cursorPos,
  1857.                 nextCursorPos = minSelection,
  1858.             }
  1859.  
  1860.             txb:setCursorPos(minSelection)
  1861.             txb:eraseSelection()
  1862.             txb:setText(txb.text:sub(0, minSelection)..txb.text:sub(maxSelection + 1))
  1863.             txb:stopSelecting()
  1864.             txb:drawCursor()
  1865.         elseif txb.cursorPos > 0 then
  1866.             txb:addNewHistory()
  1867.             txb.changeHistory[txb.historyIndex] = TextEdit:new {
  1868.                 index = txb.cursorPos,
  1869.                 string = txb.text:sub(txb.cursorPos, txb.cursorPos),
  1870.                 previousCursorPos = txb.cursorPos,
  1871.                 nextCursorPos = txb.cursorPos - 1,
  1872.             }
  1873.  
  1874.             txb:eraseSelection()
  1875.             txb:eraseCursor()
  1876.             txb:setText(txb.text:sub(0, txb.cursorPos - 1)..txb.text:sub(txb.cursorPos + 1))
  1877.             txb:setCursorPos(txb.cursorPos - 1)
  1878.         end
  1879.     elseif keyName == "delete" then
  1880.         txb:eraseAutoCompletionChoice()
  1881.         txb.autoCompleting = false
  1882.  
  1883.         if txb.selecting then
  1884.             txb:addNewHistory()
  1885.             txb.changeHistory[txb.historyIndex] = TextEdit:new {
  1886.                 index = minSelection + 1,
  1887.                 string = txb.text:sub(minSelection + 1, maxSelection),
  1888.                 previousCursorPos = txb.cursorPos,
  1889.                 nextCursorPos = minSelection,
  1890.             }
  1891.  
  1892.             txb:setCursorPos(minSelection)
  1893.             txb:eraseSelection()
  1894.             txb:setText(txb.text:sub(0, minSelection)..txb.text:sub(maxSelection + 1))
  1895.             txb:stopSelecting()
  1896.             txb:drawCursor()
  1897.         elseif txb.cursorPos < #txb.text then
  1898.             txb:addNewHistory()
  1899.             txb.changeHistory[txb.historyIndex] = TextEdit:new {
  1900.                 index = txb.cursorPos + 1,
  1901.                 string = txb.text:sub(txb.cursorPos + 1, txb.cursorPos + 1),
  1902.                 previousCursorPos = txb.cursorPos,
  1903.                 nextCursorPos = txb.cursorPos,
  1904.             }
  1905.  
  1906.             txb:eraseSelection()
  1907.             txb:eraseCursor()
  1908.             txb:setText(txb.text:sub(0, txb.cursorPos)..txb.text:sub(txb.cursorPos + 2))
  1909.             txb:setCursorPos(txb.cursorPos)
  1910.         end
  1911.     elseif keyName == "home" then
  1912.         txb:eraseAutoCompletionChoice()
  1913.         txb.autoCompleting = false
  1914.  
  1915.         if shiftHeld then
  1916.             if not txb.selecting then
  1917.                 txb:setSelectionStart(txb.cursorPos)
  1918.             end
  1919.             txb:setCursorPos(txb.cursorPos - txb.cursorRowOffset)
  1920.             txb:setSelectionEnd(txb.cursorPos)
  1921.             txb:drawCursor()
  1922.         else
  1923.             txb:setCursorPos(txb.cursorPos - txb.cursorRowOffset)
  1924.         end
  1925.     elseif keyName == "end" then
  1926.         txb:eraseAutoCompletionChoice()
  1927.         txb.autoCompleting = false
  1928.  
  1929.         if shiftHeld then
  1930.             if not txb.selecting then
  1931.                 txb:setSelectionStart(txb.cursorPos)
  1932.             end
  1933.             txb:setCursorPos(txb.cursorPos - txb.cursorRowOffset + #txb.textRows[txb.cursorRowIndex].text)
  1934.             txb:setSelectionEnd(txb.cursorPos)
  1935.             txb:drawCursor()
  1936.         else
  1937.             txb:setCursorPos(txb.cursorPos - txb.cursorRowOffset + #txb.textRows[txb.cursorRowIndex].text)
  1938.         end
  1939.     elseif ctrlHeld and keyName == "z" then
  1940.         txb:eraseAutoCompletionChoice()
  1941.         txb.autoCompleting = false
  1942.         txb:undo()
  1943.     elseif ctrlHeld and keyName == "y" then
  1944.         txb:eraseAutoCompletionChoice()
  1945.         txb.autoCompleting = false
  1946.         txb:redo()
  1947.     end
  1948. end
  1949.  
  1950. --[[
  1951.     handle monitor touch events
  1952. ]]
  1953. function Textbox.monitorTouched(txb, event, side, x, y)
  1954.     Textbox.mouseClicked(txb, "mouse_click", 1, x, y)
  1955. end
  1956.  
  1957. --[[
  1958.     handle mouse clicks for textbox element
  1959. ]]
  1960. function Textbox.mouseClicked(txb, event, button, x, y)
  1961.     if button == 1 then
  1962.         local lx, ly = txb:getLocalPos(x, y)
  1963.  
  1964.         txb:setCursorPosXY(lx, ly)
  1965.         txb:setSelectionStart(txb.cursorPos)
  1966.         txb:drawCursor()
  1967.     end
  1968. end
  1969.  
  1970. --[[
  1971.     handle mouse drag events for textbox element
  1972. ]]
  1973. function Textbox.mouseDragged(txb, event, button, x, y)
  1974.     if button == 1 then
  1975.         local lx, ly = txb:getLocalPos(x, y)
  1976.  
  1977.         txb:setCursorPosXY(lx, ly)
  1978.         txb:setSelectionEnd(txb.cursorPos)
  1979.         txb:drawCursor()
  1980.     end
  1981. end
  1982.  
  1983. ContentArea = Element:new {
  1984.     initialDrag = { x = 0, y = 0 },
  1985.     dragDisplacements = {},
  1986. }
  1987.  
  1988. function ContentArea:new(o)
  1989.     o = o or {}
  1990.     o.dragDisplacements = {}
  1991.  
  1992.     o = Element:new(o)
  1993.  
  1994.     setmetatable(o, self)
  1995.     self.__index = self
  1996.  
  1997.     if o.name then
  1998.         registerGlobalCallback("mouse_click", o, ContentArea.mouseClick, "mouseClick")
  1999.         registerGlobalCallback("mouse_drag", o, ContentArea.mouseDrag, "mouseDrag")
  2000.     end
  2001.  
  2002.     return o
  2003. end
  2004.  
  2005. function ContentArea.mouseClick(cont, event, button, x, y)
  2006.     if cont:selected(x, y) and button == 3 then
  2007.         cont.initialDrag.x = x
  2008.         cont.initialDrag.y = y
  2009.  
  2010.         cont.dragDisplacements = {}
  2011.         for index, child in ipairs(cont.children) do
  2012.             table.insert(cont.dragDisplacements, {["x"] = x - child.globalX, ["y"] = y - child.globalY})
  2013.         end
  2014.     end
  2015. end
  2016.  
  2017. function ContentArea.mouseDrag(cont, event, button, x, y)
  2018.     if button == 3 then
  2019.         for index, child in ipairs(cont.children) do
  2020.             child:setGlobalPos(x - cont.dragDisplacements[index].x, y - cont.dragDisplacements[index].y)
  2021.         end
  2022.     end
  2023. end
  2024.  
  2025. Window = Element:new {
  2026.     class = "Window",
  2027.     titleTxt = nil,
  2028.     contentElmnt = nil,
  2029.     exitBtn = nil,
  2030.     maximizeBtn = nil,
  2031.     minimizeBtn = nil,
  2032.     contents = nil,
  2033. }
  2034.  
  2035. function Window:new(o)
  2036.     o = o or {}
  2037.     o = Element:new(o)
  2038.  
  2039.     setmetatable(o, self)
  2040.     self.__index = self
  2041.  
  2042.     o.titleTxt = o.titleTxt or Text:new {
  2043.         name = o.name.."_titleTxt",
  2044.         parent = o,
  2045.         buffer = o.buffer,
  2046.         width = o.width - 3,
  2047.         text = "Window",
  2048.     }
  2049.  
  2050.     o.contentElmnt = o.contentElmnt or ContentArea:new {
  2051.         name = o.name.."_contentElmnt",
  2052.         parent = o,
  2053.         buffer = o.buffer,
  2054.         x = 1,
  2055.         y = 2,
  2056.         width = o.width,
  2057.         height = o.height - 1,
  2058.         backgroundColor = colors.white,
  2059.         isBuffer = true,
  2060.     }
  2061.  
  2062.     o.exitBtn = Button:new {
  2063.         name = o.name.."_exitBtn",
  2064.         parent = o,
  2065.         buffer = o.buffer,
  2066.         x = o.width,
  2067.         y = 1,
  2068.         width = 1,
  2069.         height = 1,
  2070.         backgroundColor = colors.red,
  2071.         text = "x",
  2072.         onClickName = "exit",
  2073.         onClick = function(btn)
  2074.             btn.parent.exit()
  2075.         end
  2076.     }
  2077.  
  2078.     o.maximizeBtn = Button:new {
  2079.         name = o.name.."_maximizeBtn",
  2080.         parent = o,
  2081.         buffer = o.buffer,
  2082.         x = o.width - 1,
  2083.         y = 1,
  2084.         width = 1,
  2085.         height = 1,
  2086.         backgroundColor = colors.lime,
  2087.         text = "+",
  2088.         onClickName = "maximize",
  2089.         onClick = function(btn)
  2090.             btn.parent.maximize()
  2091.         end
  2092.     }
  2093.  
  2094.     o.minimizeBtn = Button:new {
  2095.         name = o.name.."_minimizeBtn",
  2096.         parent = o,
  2097.         buffer = o.buffer,
  2098.         x = o.width - 2,
  2099.         y = 1,
  2100.         width = 1,
  2101.         height = 1,
  2102.         backgroundColor = colors.orange,
  2103.         text = "-",
  2104.         onClickName = "minimize",
  2105.         onClick = function(btn)
  2106.             btn.parent.minimize()
  2107.         end
  2108.     }
  2109.  
  2110.     o.contents = o.contents or {}
  2111.     for _, content in ipairs(o.contents) do
  2112.         content:setPos(content.x + o.contentElmnt.globalX - 1, content.y + o.contentElmnt.globalY - 1)
  2113.         content:setParent(o.contentElmnt)
  2114.         content.buffer = o.contentElmnt
  2115.     end
  2116.  
  2117.     if o.name then
  2118.         registerSelectionCallback("mouse_drag", o.titleTxt, o.mouseDrag, "mouseDrag")
  2119.     end
  2120.  
  2121.     return o
  2122. end
  2123.  
  2124. function Window.mouseDrag(titleTxt, event, button, x, y)
  2125.     if button == 1 then
  2126.         titleTxt.parent:setGlobalPos(x, y)
  2127.     end
  2128. end
  2129.  
  2130. --[[
  2131.     set display and return buffer linked to it
  2132. ]]
  2133. function createBuffer(device, bufferName, backgroundColor, x, y, width, height)
  2134.     local isMonitor
  2135.     local side
  2136.  
  2137.     local deviceMT = getmetatable(device)
  2138.  
  2139.     if deviceMT and deviceMT.type == "monitor" then
  2140.         isMonitor = true
  2141.         side = deviceMT.name
  2142.     else
  2143.         isMonitor = false
  2144.     end
  2145.  
  2146.     if not x and not y then
  2147.         x = 1
  2148.         y = 1
  2149.     end
  2150.  
  2151.     if not width and not height then
  2152.         width, height = device.getSize()
  2153.     end
  2154.  
  2155.     local display = Display:new {
  2156.         device = device,
  2157.         isMonitor = isMonitor,
  2158.         side = side,
  2159.         width = width,
  2160.         height = height,
  2161.     }
  2162.  
  2163.     local buffer = Buffer:new {
  2164.         name = bufferName,
  2165.         display = display,
  2166.         backgroundColor = backgroundColor,
  2167.         globalX = x,
  2168.         globalY = y,
  2169.         width = display.width,
  2170.         height = display.height,
  2171.     }
  2172.  
  2173.     table.insert(buffers, buffer)
  2174.  
  2175.     return buffer
  2176. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement