Advertisement
alexexe82

GUI.lua

Jan 5th, 2018
988
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 128.98 KB | None | 0 0
  1.  
  2. require("advancedLua")
  3. local component = require("component")
  4. local computer = require("computer")
  5. local keyboard = require("keyboard")
  6. local fs = require("filesystem")
  7. local unicode = require("unicode")
  8. local event = require("event")
  9. local color = require("color")
  10. local image = require("image")
  11. local buffer = require("doubleBuffering")
  12.  
  13. -----------------------------------------------------------------------------------------------------
  14.  
  15. local GUI = {}
  16.  
  17. GUI.alignment = {
  18.     horizontal = enum(
  19.         "left",
  20.         "center",
  21.         "right"
  22.     ),
  23.     vertical = enum(
  24.         "top",
  25.         "center",
  26.         "bottom"
  27.     )
  28. }
  29.  
  30. GUI.directions = enum(
  31.     "horizontal",
  32.     "vertical"
  33. )
  34.  
  35. GUI.sizePolicies = enum(
  36.     "percentage",
  37.     "absolute"
  38. )
  39.  
  40. GUI.dropDownMenuElementTypes = enum(
  41.     "default",
  42.     "separator"
  43. )
  44.  
  45. GUI.filesystemModes = enum(
  46.     "file",
  47.     "directory",
  48.     "both",
  49.     "open",
  50.     "save"
  51. )
  52.  
  53. GUI.colors = {
  54.     disabled = {
  55.         background = 0x888888,
  56.         text = 0xAAAAAA
  57.     },
  58.     contextMenu = {
  59.         separator = 0x888888,
  60.         default = {
  61.             background = 0xFFFFFF,
  62.             text = 0x2D2D2D
  63.         },
  64.         disabled = 0x888888,
  65.         pressed = {
  66.             background = 0x3366CC,
  67.             text = 0xFFFFFF
  68.         },
  69.         transparency = {
  70.             background = 0.24,
  71.             shadow = 0.4
  72.         }
  73.     },
  74. }
  75.  
  76. GUI.paletteConfigPath = "/lib/.palette.cfg"
  77.  
  78. ----------------------------------------- Interface objects -----------------------------------------
  79.  
  80. local function callMethod(method, ...)
  81.     if method then method(...) end
  82. end
  83.  
  84. local function objectIsClicked(object, x, y)
  85.     return
  86.         x >= object.x and
  87.         y >= object.y and
  88.         x <= object.x + object.width - 1 and
  89.         y <= object.y + object.height - 1 and
  90.         not object.disabled and
  91.         not object.hidden
  92. end
  93.  
  94. local function objectDraw(object)
  95.     return object
  96. end
  97.  
  98. function GUI.object(x, y, width, height)
  99.     return {
  100.         x = x,
  101.         y = y,
  102.         width = width,
  103.         height = height,
  104.         isClicked = objectIsClicked,
  105.         draw = objectDraw
  106.     }
  107. end
  108.  
  109. ----------------------------------------- Object alignment -----------------------------------------
  110.  
  111. function GUI.setAlignment(object, horizontalAlignment, verticalAlignment)
  112.     object.alignment = {
  113.         horizontal = horizontalAlignment,
  114.         vertical = verticalAlignment
  115.     }
  116.  
  117.     return object
  118. end
  119.  
  120. function GUI.getAlignmentCoordinates(object, subObject)
  121.     local x, y
  122.     if object.alignment.horizontal == GUI.alignment.horizontal.left then
  123.         x = object.x
  124.     elseif object.alignment.horizontal == GUI.alignment.horizontal.center then
  125.         x = math.floor(object.x + object.width / 2 - subObject.width / 2)
  126.     elseif object.alignment.horizontal == GUI.alignment.horizontal.right then
  127.         x = object.x + object.width - subObject.width
  128.     else
  129.         error("Unknown horizontal alignment: " .. tostring(object.alignment.horizontal))
  130.     end
  131.  
  132.     if object.alignment.vertical == GUI.alignment.vertical.top then
  133.         y = object.y
  134.     elseif object.alignment.vertical == GUI.alignment.vertical.center then
  135.         y = math.floor(object.y + object.height / 2 - subObject.height / 2)
  136.     elseif object.alignment.vertical == GUI.alignment.vertical.bottom then
  137.         y = object.y + object.height - subObject.height
  138.     else
  139.         error("Unknown vertical alignment: " .. tostring(object.alignment.vertical))
  140.     end
  141.  
  142.     return x, y
  143. end
  144.  
  145. function GUI.getMarginCoordinates(object)
  146.     local x, y = object.x, object.y
  147.  
  148.     if object.alignment.horizontal == GUI.alignment.horizontal.left then
  149.         x = x + object.margin.horizontal
  150.     elseif object.alignment.horizontal == GUI.alignment.horizontal.right then
  151.         x = x - object.margin.horizontal
  152.     end
  153.  
  154.     if object.alignment.vertical == GUI.alignment.vertical.top then
  155.         y = y + object.margin.vertical
  156.     elseif object.alignment.vertical == GUI.alignment.vertical.bottom then
  157.         y = y - object.margin.vertical
  158.     end
  159.  
  160.     return x, y
  161. end
  162.  
  163. ----------------------------------------- Containers -----------------------------------------
  164.  
  165. local function containerObjectIndexOf(object)
  166.     if not object.parent then error("Object doesn't have a parent container") end
  167.  
  168.     for objectIndex = 1, #object.parent.children do
  169.         if object.parent.children[objectIndex] == object then
  170.             return objectIndex
  171.         end
  172.     end
  173. end
  174.  
  175. local function containerObjectMoveForward(object)
  176.     local objectIndex = containerObjectIndexOf(object)
  177.     if objectIndex < #object.parent.children then
  178.         object.parent.children[index], object.parent.children[index + 1] = object.parent.children[index + 1], object.parent.children[index]
  179.     end
  180.    
  181.     return object
  182. end
  183.  
  184. local function containerObjectMoveBackward(object)
  185.     local objectIndex = containerObjectIndexOf(object)
  186.     if objectIndex > 1 then
  187.         object.parent.children[objectIndex], object.parent.children[objectIndex - 1] = object.parent.children[objectIndex - 1], object.parent.children[objectIndex]
  188.     end
  189.    
  190.     return object
  191. end
  192.  
  193. local function containerObjectMoveToFront(object)
  194.     table.remove(object.parent.children, containerObjectIndexOf(object))
  195.     table.insert(object.parent.children, object)
  196.    
  197.     return object
  198. end
  199.  
  200. local function containerObjectMoveToBack(object)
  201.     table.remove(object.parent.children, containerObjectIndexOf(object))
  202.     table.insert(object.parent.children, 1, object)
  203.    
  204.     return object
  205. end
  206.  
  207. local function containerObjectGetFirstParent(object)
  208.     local currentParent = object.parent
  209.     while currentParent.parent do
  210.         currentParent = currentParent.parent
  211.     end
  212.  
  213.     return currentParent
  214. end
  215.  
  216. local function containerObjectSelfDelete(object)
  217.     table.remove(object.parent.children, containerObjectIndexOf(object))
  218. end
  219.  
  220. -----------------------------------------------------------------------------------------------------
  221.  
  222. local function containerObjectAnimationStart(animation, duration)
  223.     animation.position = 0
  224.     animation.duration = duration
  225.     animation.started = true
  226.     animation.startUptime = computer.uptime()
  227.  
  228.     computer.pushSignal("GUI", "animationStarted")
  229. end
  230.  
  231. local function containerObjectAnimationStop(animation)
  232.     animation.position = 0
  233.     animation.started = false
  234. end
  235.  
  236. local function containerObjectAnimationDelete(animation)
  237.     animation.deleteLater = true
  238. end
  239.  
  240. local function containerObjectAddAnimation(object, frameHandler, onFinish)
  241.     local animation = {
  242.         object = object,
  243.         position = 0,
  244.         start = containerObjectAnimationStart,
  245.         stop = containerObjectAnimationStop,
  246.         delete = containerObjectAnimationDelete,
  247.         frameHandler = frameHandler,
  248.         onFinish = onFinish,
  249.     }
  250.  
  251.     local firstParent = object:getFirstParent()
  252.     firstParent.animations = firstParent.animations or {}
  253.     table.insert(firstParent.animations, animation)
  254.  
  255.     return animation
  256. end
  257.  
  258. function GUI.addChildToContainer(container, object, atIndex)
  259.     object.localX = object.x
  260.     object.localY = object.y
  261.     object.indexOf = containerObjectIndexOf
  262.     object.moveToFront = containerObjectMoveToFront
  263.     object.moveToBack = containerObjectMoveToBack
  264.     object.moveForward = containerObjectMoveForward
  265.     object.moveBackward = containerObjectMoveBackward
  266.     object.getFirstParent = containerObjectGetFirstParent
  267.     object.delete = containerObjectSelfDelete
  268.     object.parent = container
  269.     object.addAnimation = containerObjectAddAnimation
  270.  
  271.     if atIndex then
  272.         table.insert(container.children, atIndex, object)
  273.     else
  274.         table.insert(container.children, object)
  275.     end
  276.    
  277.     return object
  278. end
  279.  
  280. local function deleteContainersContent(container, from, to)
  281.     from = from or 1
  282.     for objectIndex = from, to or #container.children do
  283.         table.remove(container.children, from)
  284.     end
  285. end
  286.  
  287. local function getRectangleIntersection(R1X1, R1Y1, R1X2, R1Y2, R2X1, R2Y1, R2X2, R2Y2)
  288.     if R2X1 <= R1X2 and R2Y1 <= R1Y2 and R2X2 >= R1X1 and R2Y2 >= R1Y1 then
  289.         return
  290.             math.max(R2X1, R1X1),
  291.             math.max(R2Y1, R1Y1),
  292.             math.min(R2X2, R1X2),
  293.             math.min(R2Y2, R1Y2)
  294.     else
  295.         return
  296.     end
  297. end
  298.  
  299. function GUI.drawContainerContent(container)
  300.     local R1X1, R1Y1, R1X2, R1Y2, child = buffer.getDrawLimit()
  301.     local intersectionX1, intersectionY1, intersectionX2, intersectionY2 = getRectangleIntersection(
  302.         R1X1,
  303.         R1Y1,
  304.         R1X2,
  305.         R1Y2,
  306.         container.x,
  307.         container.y,
  308.         container.x + container.width - 1,
  309.         container.y + container.height - 1
  310.     )
  311.  
  312.     if intersectionX1 then
  313.         buffer.setDrawLimit(intersectionX1, intersectionY1, intersectionX2, intersectionY2)
  314.        
  315.         for i = 1, #container.children do
  316.             child = container.children[i]
  317.            
  318.             if not child.hidden then
  319.                 child.x, child.y = container.x + child.localX - 1, container.y + child.localY - 1
  320.                 child:draw()
  321.             end
  322.         end
  323.  
  324.         buffer.setDrawLimit(R1X1, R1Y1, R1X2, R1Y2)
  325.     end
  326.  
  327.     return container
  328. end
  329.  
  330. local function containerHandler(isScreenEvent, mainContainer, currentContainer, eventData, intersectionX1, intersectionY1, intersectionX2, intersectionY2)
  331.     local breakRecursion, child = false
  332.  
  333.     if not isScreenEvent or intersectionX1 and eventData[3] >= intersectionX1 and eventData[4] >= intersectionY1 and eventData[3] <= intersectionX2 and eventData[4] <= intersectionY2 then
  334.         for i = #currentContainer.children, 1, -1 do
  335.             child = currentContainer.children[i]
  336.  
  337.             if not child.hidden then
  338.                 if child.children then
  339.                     local newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2 = getRectangleIntersection(
  340.                         intersectionX1,
  341.                         intersectionY1,
  342.                         intersectionX2,
  343.                         intersectionY2,
  344.                         child.x,
  345.                         child.y,
  346.                         child.x + child.width - 1,
  347.                         child.y + child.height - 1
  348.                     )
  349.  
  350.                     if newIntersectionX1 then
  351.                         if containerHandler(isScreenEvent, mainContainer, child, eventData, newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2) then
  352.                             breakRecursion = true
  353.                             break
  354.                         end
  355.                     end
  356.                 else
  357.                     if isScreenEvent then
  358.                         if child:isClicked(eventData[3], eventData[4]) then
  359.                             callMethod(child.eventHandler, mainContainer, child, eventData)
  360.                             breakRecursion = true
  361.                             break
  362.                         end
  363.                     else
  364.                         callMethod(child.eventHandler, mainContainer, child, eventData)
  365.                     end
  366.                 end
  367.             end
  368.         end
  369.  
  370.         callMethod(currentContainer.eventHandler, mainContainer, currentContainer, eventData)
  371.     end
  372.  
  373.     if breakRecursion then
  374.         return true
  375.     end
  376. end
  377.  
  378. local function containerStartEventHandling(container, eventHandlingDelay)
  379.     container.eventHandlingDelay = eventHandlingDelay
  380.  
  381.     local eventData, animationIndex, animation, animationOnFinishMethods
  382.     repeat
  383.         eventData = {event.pull(container.animations and 0 or container.eventHandlingDelay)}
  384.        
  385.         containerHandler(
  386.             (
  387.                 eventData[1] == "touch" or
  388.                 eventData[1] == "drag" or
  389.                 eventData[1] == "drop" or
  390.                 eventData[1] == "scroll" or
  391.                 eventData[1] == "double_touch"
  392.             ),
  393.             container,
  394.             container,
  395.             eventData,
  396.             container.x,
  397.             container.y,
  398.             container.x + container.width - 1,
  399.             container.y + container.height - 1
  400.         )
  401.  
  402.         if container.animations then
  403.             animationIndex, animationOnFinishMethods = 1, {}
  404.  
  405.             -- Продрачиваем анимации и вызываем обработчики кадров
  406.             while animationIndex <= #container.animations do
  407.                 animation = container.animations[animationIndex]
  408.  
  409.                 if animation.deleteLater then
  410.                     table.remove(container.animations, animationIndex)
  411.                     if #container.animations == 0 then
  412.                         container.animations = nil
  413.                         break
  414.                     end
  415.                 else
  416.                     if animation.started then
  417.                         animationNeedDraw = true
  418.                         animation.position = (computer.uptime() - animation.startUptime) / animation.duration
  419.                        
  420.                         if animation.position < 1 then
  421.                             animation.frameHandler(container, animation)
  422.                         else
  423.                             animation.position = 1
  424.                             animation.started = false
  425.                             animation.frameHandler(container, animation)
  426.                            
  427.                             if animation.onFinish then
  428.                                 table.insert(animationOnFinishMethods, animation)
  429.                             end
  430.                         end
  431.                     end
  432.  
  433.                     animationIndex = animationIndex + 1
  434.                 end
  435.             end
  436.  
  437.             -- По завершению продрочки отрисовываем изменения на экране
  438.             container:draw()
  439.             buffer.draw()
  440.  
  441.             -- Вызываем поочередно все методы .onFinish
  442.             for i = 1, #animationOnFinishMethods do
  443.                 animationOnFinishMethods[i].onFinish(container, animationOnFinishMethods[i])
  444.             end
  445.         end
  446.     until container.dataToReturn
  447.  
  448.     local dataToReturn = container.dataToReturn
  449.     container.dataToReturn = nil
  450.     return table.unpack(dataToReturn)
  451. end
  452.  
  453. local function containerReturnData(container, ...)
  454.     container.dataToReturn = {...}
  455. end
  456.  
  457. local function containerStopEventHandling(container)
  458.     containerReturnData(container, nil)
  459. end
  460.  
  461. function GUI.container(x, y, width, height)
  462.     local container = GUI.object(x, y, width, height)
  463.  
  464.     container.children = {}
  465.     container.draw = GUI.drawContainerContent
  466.     container.deleteChildren = deleteContainersContent
  467.     container.addChild = GUI.addChildToContainer
  468.     container.returnData = containerReturnData
  469.     container.startEventHandling = containerStartEventHandling
  470.     container.stopEventHandling = containerStopEventHandling
  471.  
  472.     return container
  473. end
  474.  
  475. function GUI.fullScreenContainer()
  476.     return GUI.container(1, 1, buffer.getResolution())
  477. end
  478.  
  479. ----------------------------------------- Buttons -----------------------------------------
  480.  
  481. local function buttonPlayAnimation(button, onFinish)
  482.     button.animationStarted = true
  483.     button:addAnimation(
  484.         function(mainContainer, animation)
  485.             if button.pressed then
  486.                 if button.colors.default.background and button.colors.pressed.background then
  487.                     button.animationCurrentBackground = color.transition(button.colors.pressed.background, button.colors.default.background, animation.position)
  488.                 end
  489.                 button.animationCurrentText = color.transition(button.colors.pressed.text, button.colors.default.text, animation.position)
  490.             else
  491.                 if button.colors.default.background and button.colors.pressed.background then
  492.                     button.animationCurrentBackground = color.transition(button.colors.default.background, button.colors.pressed.background, animation.position)
  493.                 end
  494.                 button.animationCurrentText = color.transition(button.colors.default.text, button.colors.pressed.text, animation.position)
  495.             end
  496.         end,
  497.         function(mainContainer, animation)
  498.             button.animationStarted = false
  499.             button.pressed = not button.pressed
  500.             onFinish(mainContainer, animation)
  501.         end
  502.     ):start(button.animationDuration)
  503. end
  504.  
  505. local function buttonPress(button, mainContainer, object, eventData)
  506.     if button.animated then
  507.         buttonPlayAnimation(button, function(mainContainer, animation)
  508.             if button.onTouch then
  509.                 button.onTouch(mainContainer, button, eventData)
  510.             end
  511.  
  512.             animation:delete()
  513.  
  514.             if not button.switchMode then
  515.                 buttonPlayAnimation(button, function(mainContainer, animation)
  516.                     animation:delete()
  517.                 end)
  518.             end
  519.         end)
  520.     else
  521.         button.pressed = not button.pressed
  522.  
  523.         mainContainer:draw()
  524.         buffer.draw()
  525.  
  526.         if not button.switchMode then
  527.             button.pressed = not button.pressed
  528.            
  529.             os.sleep(0.2)
  530.            
  531.             mainContainer:draw()
  532.             buffer.draw()
  533.         end
  534.  
  535.         if button.onTouch then
  536.             button.onTouch(mainContainer, button, eventData)
  537.         end
  538.     end
  539. end
  540.  
  541. local function buttonEventHandler(mainContainer, button, eventData)
  542.     if eventData[1] == "touch" then
  543.         button:press(mainContainer, button, eventData)
  544.     end
  545. end
  546.  
  547. local function buttonGetColors(button)
  548.     if button.disabled then
  549.         return button.colors.disabled.background, button.colors.disabled.text
  550.     else
  551.         if button.animated and button.animationStarted then
  552.             return button.animationCurrentBackground, button.animationCurrentText
  553.         else
  554.             if button.pressed then
  555.                 return button.colors.pressed.background, button.colors.pressed.text
  556.             else
  557.                 return button.colors.default.background, button.colors.default.text
  558.             end
  559.         end
  560.     end
  561. end
  562.  
  563. local function buttonDrawText(button, textColor)
  564.     buffer.text(math.floor(button.x + button.width / 2 - unicode.len(button.text) / 2), math.floor(button.y + button.height / 2), textColor, button.text)
  565. end
  566.  
  567. local function buttonDraw(button)
  568.     local backgroundColor, textColor = buttonGetColors(button)
  569.     if backgroundColor then
  570.         buffer.square(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ", button.colors.transparency)
  571.     end
  572.  
  573.     buttonDrawText(button, textColor)
  574. end
  575.  
  576. local function framedButtonDraw(button)
  577.     local backgroundColor, textColor = buttonGetColors(button)
  578.     if backgroundColor then
  579.         buffer.frame(button.x, button.y, button.width, button.height, backgroundColor)
  580.     end
  581.  
  582.     buttonDrawText(button, textColor)
  583. end
  584.  
  585. local function roundedButtonDraw(button)
  586.     local backgroundColor, textColor = buttonGetColors(button)
  587.  
  588.     if backgroundColor then
  589.         local x2, y2 = button.x + button.width - 1, button.y + button.height - 1
  590.         if button.height > 1 then
  591.             buffer.text(button.x + 1, button.y, backgroundColor, string.rep("▄", button.width - 2))
  592.             buffer.text(button.x, button.y, backgroundColor, "⣠")
  593.             buffer.text(x2, button.y, backgroundColor, "⣄")
  594.            
  595.             buffer.square(button.x, button.y + 1, button.width, button.height - 2, backgroundColor, textColor, " ")
  596.            
  597.             buffer.text(button.x + 1, y2, backgroundColor, string.rep("▀", button.width - 2))
  598.             buffer.text(button.x, y2, backgroundColor, "⠙")
  599.             buffer.text(x2, y2, backgroundColor, "⠋")
  600.         else
  601.             buffer.square(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ")
  602.             GUI.roundedCorners(button.x, button.y, button.width, button.height, backgroundColor)
  603.         end
  604.     end
  605.  
  606.     buttonDrawText(button, textColor)
  607. end
  608.  
  609. local function buttonCreate(x, y, width, height, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text)
  610.     local button = GUI.object(x, y, width, height)
  611.  
  612.     button.colors = {
  613.         default = {
  614.             background = backgroundColor,
  615.             text = textColor
  616.         },
  617.         pressed = {
  618.             background = backgroundPressedColor,
  619.             text = textPressedColor
  620.         },
  621.         disabled = {
  622.             background = GUI.colors.disabled.background,
  623.             text = GUI.colors.disabled.text
  624.         }
  625.     }
  626.     button.animationCurrentBackground = backgroundColor
  627.     button.animationCurrentText = textColor
  628.  
  629.     button.text = text
  630.     button.animationDuration = 0.2
  631.     button.animated = true
  632.     button.pressed = false
  633.    
  634.     button.press = buttonPress
  635.     button.eventHandler = buttonEventHandler
  636.  
  637.     return button
  638. end
  639.  
  640. local function adaptiveButtonCreate(x, y, xOffset, yOffset, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text)
  641.     return buttonCreate(x, y, unicode.len(text) + xOffset * 2, yOffset * 2 + 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text)
  642. end
  643.  
  644. function GUI.button(...)
  645.     local button = buttonCreate(...)
  646.     button.draw = buttonDraw
  647.     return button
  648. end
  649.  
  650. function GUI.adaptiveButton(...)
  651.     local button = adaptiveButtonCreate(...)
  652.     button.draw = buttonDraw
  653.     return button
  654. end
  655.  
  656. function GUI.framedButton(...)
  657.     local button = buttonCreate(...)
  658.     button.draw = framedButtonDraw
  659.     return button
  660. end
  661.  
  662. function GUI.adaptiveFramedButton(...)
  663.     local button = adaptiveButtonCreate(...)
  664.     button.draw = framedButtonDraw
  665.     return button
  666. end
  667.  
  668. function GUI.roundedButton(...)
  669.     local button = buttonCreate(...)
  670.     button.draw = roundedButtonDraw
  671.     return button
  672. end
  673.  
  674. function GUI.adaptiveRoundedButton(...)
  675.     local button = adaptiveButtonCreate(...)
  676.     button.draw = roundedButtonDraw
  677.     return button
  678. end
  679.  
  680. ----------------------------------------- Panel -----------------------------------------
  681.  
  682. local function drawPanel(object)
  683.     buffer.square(object.x, object.y, object.width, object.height, object.colors.background, 0x000000, " ", object.colors.transparency)
  684.     return object
  685. end
  686.  
  687. function GUI.panel(x, y, width, height, color, transparency)
  688.     local object = GUI.object(x, y, width, height)
  689.    
  690.     object.colors = {
  691.         background = color,
  692.         transparency = transparency
  693.     }
  694.     object.draw = drawPanel
  695.    
  696.     return object
  697. end
  698.  
  699. ----------------------------------------- Label -----------------------------------------
  700.  
  701. local function drawLabel(object)
  702.     local xText, yText = GUI.getAlignmentCoordinates(object, {width = unicode.len(object.text), height = 1})
  703.     buffer.text(xText, yText, object.colors.text, object.text)
  704.     return object
  705. end
  706.  
  707. function GUI.label(x, y, width, height, textColor, text)
  708.     local object = GUI.object(x, y, width, height)
  709.     object.setAlignment = GUI.setAlignment
  710.     object:setAlignment(GUI.alignment.horizontal.left, GUI.alignment.vertical.top)
  711.     object.colors = {text = textColor}
  712.     object.text = text
  713.     object.draw = drawLabel
  714.     return object
  715. end
  716.  
  717. ----------------------------------------- Image -----------------------------------------
  718.  
  719. local function drawImage(object)
  720.     buffer.image(object.x, object.y, object.image)
  721.     return object
  722. end
  723.  
  724. function GUI.image(x, y, image)
  725.     local object = GUI.object(x, y, image[1], image[2])
  726.     object.image = image
  727.     object.draw = drawImage
  728.     return object
  729. end
  730.  
  731. ----------------------------------------- Action buttons -----------------------------------------
  732.  
  733. function GUI.actionButtons(x, y, fatSymbol)
  734.     local symbol = fatSymbol and "⬤" or "●"
  735.    
  736.     local container = GUI.container(x, y, 5, 1)
  737.     container.close = container:addChild(GUI.button(1, 1, 1, 1, nil, 0xFF4940, nil, 0x992400, symbol))
  738.     container.minimize = container:addChild(GUI.button(3, 1, 1, 1, nil, 0xFFB640, nil, 0x996D00, symbol))
  739.     container.maximize = container:addChild(GUI.button(5, 1, 1, 1, nil, 0x00B640, nil, 0x006D40, symbol))
  740.  
  741.     return container
  742. end
  743.  
  744. ----------------------------------------- Menu -----------------------------------------
  745.  
  746. local function menuDraw(menu)
  747.     buffer.square(menu.x, menu.y, menu.width, 1, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency)
  748.     menu:reimplementedDraw()
  749. end
  750.  
  751. local function menuItemEventHandler(mainContainer, object, eventData)
  752.     if eventData[1] == "touch" then
  753.         if object.onTouch then
  754.             object.pressed = true
  755.             mainContainer:draw()
  756.             buffer.draw()
  757.  
  758.             object.onTouch(eventData)
  759.            
  760.             object.pressed = false
  761.             mainContainer:draw()
  762.             buffer.draw()
  763.         end
  764.     end
  765. end
  766.  
  767. local function menuAddItem(menu, text, textColor)
  768.     local x = 2; for i = 1, #menu.children do x = x + unicode.len(menu.children[i].text) + 2; end
  769.     local item = menu:addChild(GUI.adaptiveButton(x, 1, 1, 0, nil, textColor or menu.colors.default.text, menu.colors.pressed.background, menu.colors.pressed.text, text))
  770.     item.animated = false
  771.     item.eventHandler = menuItemEventHandler
  772.  
  773.     return item
  774. end
  775.  
  776. function GUI.menu(x, y, width, backgroundColor, textColor, backgroundPressedColor, textPressedColor, backgroundTransparency)
  777.     local menu = GUI.container(x, y, width, 1)
  778.    
  779.     menu.colors = {
  780.         default = {
  781.             background = backgroundColor,
  782.             text = textColor,
  783.         },
  784.         pressed = {
  785.             background = backgroundPressedColor,
  786.             text = textPressedColor,
  787.         },
  788.         transparency = backgroundTransparency
  789.     }
  790.     menu.addItem = menuAddItem
  791.     menu.reimplementedDraw = menu.draw
  792.     menu.draw = menuDraw
  793.  
  794.     return menu
  795. end
  796.  
  797. ----------------------------------------- ProgressBar Object -----------------------------------------
  798.  
  799. local function drawProgressBar(object)
  800.     local activeWidth = math.floor(object.value * object.width / 100)
  801.     if object.thin then
  802.         buffer.text(object.x, object.y, object.colors.passive, string.rep("━", object.width))
  803.         buffer.text(object.x, object.y, object.colors.active, string.rep("━", activeWidth))
  804.     else
  805.         buffer.square(object.x, object.y, object.width, object.height, object.colors.passive, 0x0, " ")
  806.         buffer.square(object.x, object.y, activeWidth, object.height, object.colors.active, 0x0, " ")
  807.     end
  808.  
  809.     if object.showValue then
  810.         local stringValue = tostring((object.valuePrefix or "") .. object.value .. (object.valuePostfix or ""))
  811.         buffer.text(math.floor(object.x + object.width / 2 - unicode.len(stringValue) / 2), object.y + 1, object.colors.value, stringValue)
  812.     end
  813.  
  814.     return object
  815. end
  816.  
  817. function GUI.progressBar(x, y, width, activeColor, passiveColor, valueColor, value, thin, showValue, valuePrefix, valuePostfix)
  818.     local object = GUI.object(x, y, width, 1)
  819.    
  820.     object.value = value
  821.     object.colors = {active = activeColor, passive = passiveColor, value = valueColor}
  822.     object.thin = thin
  823.     object.draw = drawProgressBar
  824.     object.showValue = showValue
  825.     object.valuePrefix = valuePrefix
  826.     object.valuePostfix = valuePostfix
  827.    
  828.     return object
  829. end
  830.  
  831. ----------------------------------------- Other GUI elements -----------------------------------------
  832.  
  833. function GUI.windowShadow(x, y, width, height, transparency, thin)
  834.     transparency = transparency
  835.     if thin then
  836.         buffer.square(x + width, y + 1, 1, height - 1, 0x000000, 0x000000, " ", transparency)
  837.         buffer.text(x + 1, y + height, 0x000000, string.rep("▀", width), transparency)
  838.         buffer.text(x + width, y, 0x000000, "▄", transparency)
  839.     else
  840.         buffer.square(x + width, y + 1, 2, height, 0x000000, 0x000000, " ", transparency)
  841.         buffer.square(x + 2, y + height, width - 2, 1, 0x000000, 0x000000, " ", transparency)
  842.     end
  843. end
  844.  
  845. function GUI.roundedCorners(x, y, width, height, color, transparency)
  846.     buffer.text(x - 1, y, color, "⠰", transparency)
  847.     buffer.text(x + width, y, color, "⠆", transparency)
  848. end
  849.  
  850. ------------------------------------------------- Error window -------------------------------------------------------------------
  851.  
  852. function GUI.error(...)
  853.     local args = {...}
  854.     for i = 1, #args do
  855.         if type(args[i]) == "table" then
  856.             args[i] = table.toString(args[i], true)
  857.         else
  858.             args[i] = tostring(args[i])
  859.         end
  860.     end
  861.     if #args == 0 then args[1] = "nil" end
  862.  
  863.     local sign = image.fromString([[06030000FF 0000FF 00F7FF▟00F7FF▙0000FF 0000FF 0000FF 00F7FF▟F7FF00 F7FF00 00F7FF▙0000FF 00F7FF▟F7FF00CF7FF00yF7FF00kF7FF00a00F7FF▙]])
  864.     local offset = 2
  865.     local lines = #args > 1 and "\"" .. table.concat(args, "\", \"") .. "\"" or args[1]
  866.     local bufferWidth, bufferHeight = buffer.getResolution()
  867.     local width = math.floor(bufferWidth * 0.5)
  868.     local textWidth = width - image.getWidth(sign) - 2
  869.  
  870.     lines = string.wrap(lines, textWidth)
  871.     local height = image.getHeight(sign)
  872.     if #lines + 2 > height then
  873.         height = #lines + 2
  874.     end
  875.  
  876.     local mainContainer = GUI.container(1, math.floor(bufferHeight / 2 - height / 2), bufferWidth, height + offset * 2)
  877.     local oldPixels = buffer.copy(mainContainer.x, mainContainer.y, mainContainer.width, mainContainer.height)
  878.  
  879.     local x, y = math.floor(bufferWidth / 2 - width / 2), offset + 1
  880.     mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x1D1D1D))
  881.     mainContainer:addChild(GUI.image(x, y, sign))
  882.     mainContainer:addChild(GUI.textBox(x + image.getWidth(sign) + 2, y, textWidth, #lines, 0x1D1D1D, 0xE1E1E1, lines, 1, 0, 0)).eventHandler = nil
  883.     local buttonWidth = 10
  884.     local button = mainContainer:addChild(GUI.roundedButton(x + image.getWidth(sign) + textWidth - buttonWidth + 2, mainContainer.height - offset, buttonWidth, 1, 0x3366CC, 0xE1E1E1, 0xE1E1E1, 0x3366CC, "OK"))
  885.    
  886.     button.onTouch = function()
  887.         mainContainer:stopEventHandling()
  888.         buffer.paste(mainContainer.x, mainContainer.y, oldPixels)
  889.         buffer.draw()
  890.     end
  891.  
  892.     mainContainer.eventHandler = function(mainContainer, object, eventData)
  893.         if eventData[1] == "key_down" and eventData[4] == 28 then
  894.             button.animated = false
  895.             button:press(mainContainer, object, eventData)
  896.         end
  897.     end
  898.  
  899.     mainContainer:draw()
  900.     buffer.draw(true)
  901.     mainContainer:startEventHandling()
  902. end
  903.  
  904. ----------------------------------------- CodeView object -----------------------------------------
  905.  
  906. local function codeViewDraw(codeView)
  907.     local syntax = require("syntax")
  908.     local toLine = codeView.fromLine + codeView.height - 1
  909.  
  910.     -- Line numbers bar and code area
  911.     codeView.lineNumbersWidth = unicode.len(tostring(toLine)) + 2
  912.     codeView.codeAreaPosition = codeView.x + codeView.lineNumbersWidth
  913.     codeView.codeAreaWidth = codeView.width - codeView.lineNumbersWidth
  914.     buffer.square(codeView.x, codeView.y, codeView.lineNumbersWidth, codeView.height, syntax.colorScheme.lineNumbersBackground, syntax.colorScheme.lineNumbersText, " ")   
  915.     buffer.square(codeView.codeAreaPosition, codeView.y, codeView.codeAreaWidth, codeView.height, syntax.colorScheme.background, syntax.colorScheme.text, " ")
  916.  
  917.     -- Line numbers texts
  918.     local y = codeView.y
  919.     for line = codeView.fromLine, toLine do
  920.         if codeView.lines[line] then
  921.             local text = tostring(line)
  922.             if codeView.highlights[line] then
  923.                 buffer.square(codeView.x, y, codeView.lineNumbersWidth, 1, codeView.highlights[line], syntax.colorScheme.text, " ", 0.3)
  924.                 buffer.square(codeView.codeAreaPosition, y, codeView.codeAreaWidth, 1, codeView.highlights[line], syntax.colorScheme.text, " ")
  925.             end
  926.             buffer.text(codeView.codeAreaPosition - unicode.len(text) - 1, y, syntax.colorScheme.lineNumbersText, text)
  927.             y = y + 1
  928.         else
  929.             break
  930.         end
  931.     end
  932.  
  933.     local oldDrawLimitX1, oldDrawLimitY1, oldDrawLimitX2, oldDrawLimitY2 = buffer.getDrawLimit()
  934.     buffer.setDrawLimit(codeView.codeAreaPosition, codeView.y, codeView.codeAreaPosition + codeView.codeAreaWidth - 1, codeView.y + codeView.height - 1)
  935.  
  936.     local function drawUpperSelection(y, selectionIndex)
  937.         buffer.square(
  938.             codeView.codeAreaPosition + codeView.selections[selectionIndex].from.symbol - codeView.fromSymbol + 1,
  939.             y + codeView.selections[selectionIndex].from.line - codeView.fromLine,
  940.             codeView.codeAreaWidth - codeView.selections[selectionIndex].from.symbol + codeView.fromSymbol - 1,
  941.             1,
  942.             codeView.selections[selectionIndex].color or syntax.colorScheme.selection, syntax.colorScheme.text, " "
  943.         )
  944.     end
  945.  
  946.     local function drawLowerSelection(y, selectionIndex)
  947.         buffer.square(
  948.             codeView.codeAreaPosition,
  949.             y + codeView.selections[selectionIndex].from.line - codeView.fromLine,
  950.             codeView.selections[selectionIndex].to.symbol - codeView.fromSymbol + 2,
  951.             1,
  952.             codeView.selections[selectionIndex].color or syntax.colorScheme.selection, syntax.colorScheme.text, " "
  953.         )
  954.     end
  955.  
  956.     if #codeView.selections > 0 then
  957.         for selectionIndex = 1, #codeView.selections do
  958.             y = codeView.y
  959.             local dy = codeView.selections[selectionIndex].to.line - codeView.selections[selectionIndex].from.line
  960.             if dy == 0 then
  961.                 buffer.square(
  962.                     codeView.codeAreaPosition + codeView.selections[selectionIndex].from.symbol - codeView.fromSymbol + 1,
  963.                     y + codeView.selections[selectionIndex].from.line - codeView.fromLine,
  964.                     codeView.selections[selectionIndex].to.symbol - codeView.selections[selectionIndex].from.symbol + 1,
  965.                     1,
  966.                     codeView.selections[selectionIndex].color or syntax.colorScheme.selection, syntax.colorScheme.text, " "
  967.                 )
  968.             elseif dy == 1 then
  969.                 drawUpperSelection(y, selectionIndex); y = y + 1
  970.                 drawLowerSelection(y, selectionIndex)
  971.             else
  972.                 drawUpperSelection(y, selectionIndex); y = y + 1
  973.                 for i = 1, dy - 1 do
  974.                     buffer.square(codeView.codeAreaPosition, y + codeView.selections[selectionIndex].from.line - codeView.fromLine, codeView.codeAreaWidth, 1, codeView.selections[selectionIndex].color or syntax.colorScheme.selection, syntax.colorScheme.text, " "); y = y + 1
  975.                 end
  976.  
  977.                 drawLowerSelection(y, selectionIndex)
  978.             end
  979.         end
  980.     end
  981.  
  982.     -- Code strings
  983.     y = codeView.y
  984.     buffer.setDrawLimit(codeView.codeAreaPosition + 1, y, codeView.codeAreaPosition + codeView.codeAreaWidth - 2, y + codeView.height - 1)
  985.     for i = codeView.fromLine, toLine do
  986.         if codeView.lines[i] then
  987.             if codeView.highlightLuaSyntax then
  988.                 syntax.highlightString(codeView.codeAreaPosition - codeView.fromSymbol + 2, y, codeView.lines[i], codeView.indentationWidth)
  989.             else
  990.                 buffer.text(codeView.codeAreaPosition - codeView.fromSymbol + 2, y, syntax.colorScheme.text, codeView.lines[i])
  991.             end
  992.             y = y + 1
  993.         else
  994.             break
  995.         end
  996.     end
  997.     buffer.setDrawLimit(oldDrawLimitX1, oldDrawLimitY1, oldDrawLimitX2, oldDrawLimitY2)
  998.  
  999.     if #codeView.lines > codeView.height then
  1000.         codeView.scrollBars.vertical.hidden = false
  1001.         codeView.scrollBars.vertical.colors.background, codeView.scrollBars.vertical.colors.foreground = syntax.colorScheme.scrollBarBackground, syntax.colorScheme.scrollBarForeground
  1002.         codeView.scrollBars.vertical.minimumValue, codeView.scrollBars.vertical.maximumValue, codeView.scrollBars.vertical.value, codeView.scrollBars.vertical.shownValueCount = 1, #codeView.lines, codeView.fromLine, codeView.height
  1003.         codeView.scrollBars.vertical.localX = codeView.width
  1004.         codeView.scrollBars.vertical.localY = 1
  1005.         codeView.scrollBars.vertical.height = codeView.height - 1
  1006.     else
  1007.         codeView.scrollBars.vertical.hidden = true
  1008.     end
  1009.  
  1010.     if codeView.maximumLineLength > codeView.codeAreaWidth - 2 then
  1011.         codeView.scrollBars.horizontal.hidden = false
  1012.         codeView.scrollBars.horizontal.colors.background, codeView.scrollBars.horizontal.colors.foreground = syntax.colorScheme.scrollBarBackground, syntax.colorScheme.scrollBarForeground
  1013.         codeView.scrollBars.horizontal.minimumValue, codeView.scrollBars.horizontal.maximumValue, codeView.scrollBars.horizontal.value, codeView.scrollBars.horizontal.shownValueCount = 1, codeView.maximumLineLength, codeView.fromSymbol, codeView.codeAreaWidth - 2
  1014.         codeView.scrollBars.horizontal.localX = codeView.lineNumbersWidth + 1
  1015.         codeView.scrollBars.horizontal.localY = codeView.height
  1016.         codeView.scrollBars.horizontal.width = codeView.codeAreaWidth - 1
  1017.     else
  1018.         codeView.scrollBars.horizontal.hidden = true
  1019.     end
  1020.  
  1021.     codeView:reimplementedDraw()
  1022. end
  1023.  
  1024. function GUI.codeView(x, y, width, height, lines, fromSymbol, fromLine, maximumLineLength, selections, highlights, highlightLuaSyntax, indentationWidth)
  1025.     local codeView = GUI.container(x, y, width, height)
  1026.    
  1027.     codeView.lines = lines
  1028.     codeView.fromSymbol = fromSymbol
  1029.     codeView.fromLine = fromLine
  1030.     codeView.maximumLineLength = maximumLineLength
  1031.     codeView.selections = selections or {}
  1032.     codeView.highlights = highlights or {}
  1033.     codeView.highlightLuaSyntax = highlightLuaSyntax
  1034.     codeView.indentationWidth = indentationWidth
  1035.  
  1036.     codeView.scrollBars = {
  1037.         vertical = codeView:addChild(GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1, true)),
  1038.         horizontal = codeView:addChild(GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1, true))
  1039.     }
  1040.  
  1041.     codeView.reimplementedDraw = codeView.draw
  1042.     codeView.draw = codeViewDraw
  1043.  
  1044.     return codeView
  1045. end
  1046.  
  1047. ----------------------------------------- Color Selector object -----------------------------------------
  1048.  
  1049. local function colorSelectorDraw(colorSelector)
  1050.     local overlayColor = colorSelector.color < 0x7FFFFF and 0xFFFFFF or 0x000000
  1051.     buffer.square(colorSelector.x, colorSelector.y, colorSelector.width, colorSelector.height, colorSelector.color, overlayColor, " ")
  1052.     if colorSelector.pressed then
  1053.         buffer.square(colorSelector.x, colorSelector.y, colorSelector.width, colorSelector.height, overlayColor, overlayColor, " ", 0.8)
  1054.     end
  1055.     if colorSelector.height > 1 then
  1056.         buffer.text(colorSelector.x, colorSelector.y + colorSelector.height - 1, overlayColor, string.rep("▄", colorSelector.width), 0.8)
  1057.     end
  1058.     buffer.text(colorSelector.x + 1, colorSelector.y + math.floor(colorSelector.height / 2), overlayColor, string.limit(colorSelector.text, colorSelector.width - 2))
  1059.     return colorSelector
  1060. end
  1061.  
  1062. local function colorSelectorEventHandler(mainContainer, object, eventData)
  1063.     if eventData[1] == "touch" then
  1064.         object.pressed = true
  1065.         mainContainer:draw()
  1066.         buffer.draw()
  1067.        
  1068.         object.color = GUI.palette(math.floor(mainContainer.width / 2 - 35), math.floor(mainContainer.height / 2 - 12), object.color or object.color):show()
  1069.        
  1070.         object.pressed = false
  1071.         mainContainer:draw()
  1072.         buffer.draw()
  1073.         callMethod(object.onTouch, eventData)
  1074.     end
  1075. end
  1076.  
  1077. function GUI.colorSelector(x, y, width, height, color, text)
  1078.     local colorSelector = GUI.object(x, y, width, height)
  1079.    
  1080.     colorSelector.eventHandler = colorSelectorEventHandler
  1081.     colorSelector.color = color
  1082.     colorSelector.text = text
  1083.     colorSelector.draw = colorSelectorDraw
  1084.    
  1085.     return colorSelector
  1086. end
  1087.  
  1088. ----------------------------------------- Chart object -----------------------------------------
  1089.  
  1090. local function getAxisValue(number, postfix, roundValues)
  1091.     if roundValues then
  1092.         return math.floor(number) .. postfix
  1093.     else
  1094.         local integer, fractional = math.modf(number)
  1095.         local firstPart, secondPart = "", ""
  1096.         if math.abs(integer) >= 1000 then
  1097.             return math.shortenNumber(integer, 2) .. postfix
  1098.         else
  1099.             if math.abs(fractional) > 0 then
  1100.                 return string.format("%.2f", number) .. postfix
  1101.             else
  1102.                 return number .. postfix
  1103.             end
  1104.         end
  1105.     end
  1106. end
  1107.  
  1108. local function drawChart(object)
  1109.     -- Sorting by x value
  1110.     local valuesCopy = {}
  1111.     for i = 1, #object.values do valuesCopy[i] = object.values[i] end
  1112.     table.sort(valuesCopy, function(a, b) return a[1] < b[1] end)
  1113.    
  1114.     if #valuesCopy == 0 then valuesCopy = {{0, 0}} end
  1115.  
  1116.     -- Max, min, deltas
  1117.     local xMin, xMax, yMin, yMax = valuesCopy[1][1], valuesCopy[#valuesCopy][1], valuesCopy[1][2], valuesCopy[1][2]
  1118.     for i = 1, #valuesCopy do yMin, yMax = math.min(yMin, valuesCopy[i][2]), math.max(yMax, valuesCopy[i][2]) end
  1119.     local dx, dy = xMax - xMin, yMax - yMin
  1120.  
  1121.     -- y axis values and helpers
  1122.     local value, chartHeight, yAxisValueMaxWidth, yAxisValues = yMin, object.height - 1 - (object.showXAxisValues and 1 or 0), 0, {}
  1123.     for y = object.y + object.height - 3, object.y + 1, -chartHeight * object.yAxisValueInterval do
  1124.         local stringValue = getAxisValue(value, object.yAxisPostfix, object.roundValues)
  1125.         yAxisValueMaxWidth = math.max(yAxisValueMaxWidth, unicode.len(stringValue))
  1126.         table.insert(yAxisValues, {y = math.ceil(y), value = stringValue})
  1127.         value = value + dy * object.yAxisValueInterval
  1128.     end
  1129.     local stringValue = getAxisValue(yMax, object.yAxisPostfix, object.roundValues)
  1130.     table.insert(yAxisValues, {y = object.y, value = stringValue})
  1131.     yAxisValueMaxWidth = math.max(yAxisValueMaxWidth, unicode.len(stringValue))
  1132.  
  1133.     local chartWidth = object.width - (object.showYAxisValues and yAxisValueMaxWidth + 2 or 0)
  1134.     local chartX = object.x + object.width - chartWidth
  1135.     for i = 1, #yAxisValues do
  1136.         if object.showYAxisValues then
  1137.             buffer.text(chartX - unicode.len(yAxisValues[i].value) - 2, yAxisValues[i].y, object.colors.axisValue, yAxisValues[i].value)
  1138.         end
  1139.         buffer.text(chartX, yAxisValues[i].y, object.colors.helpers, string.rep("─", chartWidth))
  1140.     end
  1141.  
  1142.     -- x axis values
  1143.     if object.showXAxisValues then
  1144.         value = xMin
  1145.         for x = chartX, chartX + chartWidth - 2, chartWidth * object.xAxisValueInterval do
  1146.             local stringValue = getAxisValue(value, object.xAxisPostfix, object.roundValues)
  1147.             buffer.text(math.floor(x - unicode.len(stringValue) / 2), object.y + object.height - 1, object.colors.axisValue, stringValue)
  1148.             value = value + dx * object.xAxisValueInterval
  1149.         end
  1150.         local value = getAxisValue(xMax, object.xAxisPostfix, object.roundValues)
  1151.         buffer.text(object.x + object.width - unicode.len(value), object.y + object.height - 1, object.colors.axisValue, value)
  1152.     end
  1153.  
  1154.     -- Axis lines
  1155.     for y = object.y, object.y + chartHeight - 1 do
  1156.         buffer.text(chartX - 1, y, object.colors.axis, "┨")
  1157.     end
  1158.     buffer.text(chartX - 1, object.y + chartHeight, object.colors.axis, "┗" .. string.rep("┯━", math.floor(chartWidth / 2)))
  1159.  
  1160.     local function fillVerticalPart(x1, y1, x2, y2)
  1161.         local dx, dy = x2 - x1, y2 - y1
  1162.         local absdx, absdy = math.abs(dx), math.abs(dy)
  1163.         if absdx >= absdy then
  1164.             local step, y = dy / absdx, y1
  1165.             for x = x1, x2, (x1 < x2 and 1 or -1) do
  1166.                 local yFloor = math.floor(y)
  1167.                 buffer.semiPixelSquare(math.floor(x), yFloor, 1, math.floor(object.y + chartHeight) * 2 - yFloor - 1, object.colors.chart)
  1168.                 y = y + step
  1169.             end
  1170.         else
  1171.             local step, x = dx / absdy, x1
  1172.             for y = y1, y2, (y1 < y2 and 1 or -1) do
  1173.                 local yFloor = math.floor(y)
  1174.                 buffer.semiPixelSquare(math.floor(x), yFloor, 1, math.floor(object.y + chartHeight) * 2 - yFloor - 1, object.colors.chart)
  1175.                 x = x + step
  1176.             end
  1177.         end
  1178.     end
  1179.  
  1180.     -- chart
  1181.     for i = 1, #valuesCopy - 1 do
  1182.         local x = math.floor(chartX + (valuesCopy[i][1] - xMin) / dx * (chartWidth - 1))
  1183.         local y = math.floor(object.y + chartHeight - 1 - (valuesCopy[i][2] - yMin) / dy * (chartHeight - 1)) * 2
  1184.         local xNext = math.floor(chartX + (valuesCopy[i + 1][1] - xMin) / dx * (chartWidth - 1))
  1185.         local yNext = math.floor(object.y + chartHeight - 1 - (valuesCopy[i + 1][2] - yMin) / dy * (chartHeight - 1)) * 2
  1186.         if object.fillChartArea then
  1187.             fillVerticalPart(x, y, xNext, yNext)
  1188.         else
  1189.             buffer.semiPixelLine(x, y, xNext, yNext, object.colors.chart)
  1190.         end
  1191.     end
  1192.  
  1193.     return object
  1194. end
  1195.  
  1196. function GUI.chart(x, y, width, height, axisColor, axisValueColor, axisHelpersColor, chartColor, xAxisValueInterval, yAxisValueInterval, xAxisPostfix, yAxisPostfix, fillChartArea, values)
  1197.     local object = GUI.object(x, y, width, height)
  1198.  
  1199.     object.colors = {axis = axisColor, chart = chartColor, axisValue = axisValueColor, helpers = axisHelpersColor}
  1200.     object.draw = drawChart
  1201.     object.values = values or {}
  1202.     object.xAxisPostfix = xAxisPostfix
  1203.     object.yAxisPostfix = yAxisPostfix
  1204.     object.xAxisValueInterval = xAxisValueInterval
  1205.     object.yAxisValueInterval = yAxisValueInterval
  1206.     object.fillChartArea = fillChartArea
  1207.     object.showYAxisValues = true
  1208.     object.showXAxisValues = true
  1209.  
  1210.     return object
  1211. end
  1212.  
  1213. ----------------------------------------- Dropdown Menu -----------------------------------------
  1214.  
  1215. local function dropDownMenuItemDraw(item)
  1216.     local yText = item.y + math.floor(item.height / 2)
  1217.  
  1218.     if item.type == GUI.dropDownMenuElementTypes.default then
  1219.         local textColor = item.color or item.parent.parent.colors.default.text
  1220.  
  1221.         if item.pressed then
  1222.             textColor = item.parent.parent.colors.pressed.text
  1223.             buffer.square(item.x, item.y, item.width, item.height, item.parent.parent.colors.pressed.background, textColor, " ")
  1224.         elseif item.disabled then
  1225.             textColor = item.parent.parent.colors.disabled.text
  1226.         end
  1227.  
  1228.         buffer.text(item.x + 1, yText, textColor, item.text)
  1229.         if item.shortcut then
  1230.             buffer.text(item.x + item.width - unicode.len(item.shortcut) - 1, yText, textColor, item.shortcut)
  1231.         end
  1232.     else
  1233.         buffer.text(item.x, yText, item.parent.parent.colors.separator, string.rep("─", item.width))
  1234.     end
  1235.  
  1236.     return item
  1237. end
  1238.  
  1239. local function dropDownMenuItemEventHandler(mainContainer, object, eventData)
  1240.     if eventData[1] == "touch" then
  1241.         if object.type == GUI.dropDownMenuElementTypes.default then
  1242.             object.pressed = true
  1243.             mainContainer:draw()
  1244.             buffer.draw()
  1245.  
  1246.             if object.subMenu then
  1247.                 object.subMenu.y = object.parent.y + object.localY - 1
  1248.                 object.subMenu.x = object.parent.x + object.parent.width
  1249.                 if buffer.getWidth() - object.parent.x - object.parent.width + 1 < object.subMenu.width then
  1250.                     object.subMenu.x = object.parent.x - object.subMenu.width
  1251.                 end
  1252.  
  1253.                 object.subMenu:show()
  1254.             else
  1255.                 os.sleep(0.2)
  1256.             end
  1257.  
  1258.             object.pressed = false
  1259.             mainContainer:draw()
  1260.             buffer.draw()
  1261.             mainContainer.selectedItem = object:indexOf()
  1262.  
  1263.             callMethod(object.onTouch)
  1264.         end
  1265.  
  1266.         mainContainer:stopEventHandling()
  1267.     end
  1268. end
  1269.  
  1270. local function dropDownMenuCalculateSizes(menu)
  1271.     local totalHeight = 0
  1272.     for i = 1, #menu.itemsContainer.children do
  1273.         totalHeight = totalHeight + (menu.itemsContainer.children[i].type == GUI.dropDownMenuElementTypes.separator and 1 or menu.itemHeight)
  1274.         menu.itemsContainer.children[i].width = menu.width
  1275.     end
  1276.     menu.height = math.min(totalHeight, menu.maximumHeight, buffer.getHeight() - menu.y)
  1277.     menu.itemsContainer.width, menu.itemsContainer.height = menu.width, menu.height
  1278.  
  1279.     menu.nextButton.localY = menu.height
  1280.     menu.prevButton.width, menu.nextButton.width = menu.width, menu.width
  1281.     menu.prevButton.hidden = menu.itemsContainer.children[1].localY >= 1
  1282.     menu.nextButton.hidden = menu.itemsContainer.children[#menu.itemsContainer.children].localY + menu.itemsContainer.children[#menu.itemsContainer.children].height - 1 <= menu.height
  1283. end
  1284.  
  1285. local function dropDownMenuAddItem(menu, text, disabled, shortcut, color)
  1286.     local item = menu.itemsContainer:addChild(GUI.object(1, #menu.itemsContainer.children == 0 and 1 or menu.itemsContainer.children[#menu.itemsContainer.children].localY + menu.itemsContainer.children[#menu.itemsContainer.children].height, menu.width, menu.itemHeight))
  1287.  
  1288.     item.type = GUI.dropDownMenuElementTypes.default
  1289.     item.text = text
  1290.     item.disabled = disabled
  1291.     item.shortcut = shortcut
  1292.     item.color = color
  1293.     item.draw = dropDownMenuItemDraw
  1294.     item.eventHandler = dropDownMenuItemEventHandler
  1295.  
  1296.     dropDownMenuCalculateSizes(menu)
  1297.  
  1298.     return item
  1299. end
  1300.  
  1301. local function dropDownMenuAddSeparator(menu)
  1302.     local item = dropDownMenuAddItem(menu)
  1303.     item.type = GUI.dropDownMenuElementTypes.separator
  1304.     item.height = 1
  1305.  
  1306.     return item
  1307. end
  1308.  
  1309. local function dropDownMenuScrollDown(menu)
  1310.     if menu.itemsContainer.children[1].localY < 1 then
  1311.         for i = 1, #menu.itemsContainer.children do
  1312.             menu.itemsContainer.children[i].localY = menu.itemsContainer.children[i].localY + 1
  1313.         end
  1314.     end
  1315.     menu:draw()
  1316.     buffer.draw()
  1317. end
  1318.  
  1319. local function dropDownMenuScrollUp(menu)
  1320.     if menu.itemsContainer.children[#menu.itemsContainer.children].localY + menu.itemsContainer.children[#menu.itemsContainer.children].height - 1 > menu.height then
  1321.         for i = 1, #menu.itemsContainer.children do
  1322.             menu.itemsContainer.children[i].localY = menu.itemsContainer.children[i].localY - 1
  1323.         end
  1324.     end
  1325.     menu:draw()
  1326.     buffer.draw()
  1327. end
  1328.  
  1329. local function dropDownMenuEventHandler(mainContainer, object, eventData)
  1330.     if eventData[1] == "scroll" then
  1331.         if eventData[5] == 1 then
  1332.             dropDownMenuScrollDown(object)
  1333.         else
  1334.             dropDownMenuScrollUp(object)
  1335.         end
  1336.     end
  1337. end
  1338.  
  1339. local function dropDownMenuDraw(menu)
  1340.     dropDownMenuCalculateSizes(menu)
  1341.  
  1342.     if menu.oldPixels then
  1343.         buffer.paste(menu.x, menu.y, menu.oldPixels)
  1344.     else
  1345.         menu.oldPixels = buffer.copy(menu.x, menu.y, menu.width + 1, menu.height + 1)
  1346.     end
  1347.  
  1348.     buffer.square(menu.x, menu.y, menu.width, menu.height, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency.background)
  1349.     GUI.drawContainerContent(menu)
  1350.     GUI.windowShadow(menu.x, menu.y, menu.width, menu.height, menu.colors.transparency.shadow, true)
  1351.  
  1352.     return menu
  1353. end
  1354.  
  1355. local function dropDownMenuShow(menu)
  1356.     local mainContainer = GUI.fullScreenContainer()
  1357.     -- Удаляем олдпиксельсы, чтоб старое дерьмое не рисовалось во всяких комбобоксах
  1358.     menu.oldPixels = nil
  1359.     mainContainer:addChild(GUI.object(1, 1, mainContainer.width, mainContainer.height)).eventHandler = function(mainContainer, object, eventData)
  1360.         if eventData[1] == "touch" then
  1361.             buffer.paste(menu.x, menu.y, menu.oldPixels)
  1362.             buffer.draw()
  1363.             mainContainer:stopEventHandling()
  1364.         end
  1365.     end
  1366.     mainContainer:addChild(menu)
  1367.    
  1368.     menu:draw()
  1369.     buffer.draw()
  1370.     mainContainer:startEventHandling()
  1371.     buffer.paste(menu.x, menu.y, menu.oldPixels)
  1372.     buffer.draw()
  1373.     -- А вот тут удаляем чисто шоб память не грузить
  1374.     menu.oldPixels = nil
  1375.    
  1376.     if mainContainer.selectedItem then
  1377.         return menu.itemsContainer.children[mainContainer.selectedItem].text, mainContainer.selectedItem
  1378.     end
  1379. end
  1380.  
  1381. function GUI.dropDownMenu(x, y, width, maximumHeight, itemHeight, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, backgroundTransparency, shadowTransparency)
  1382.     local menu = GUI.container(x, y, width, 1)
  1383.    
  1384.     menu.colors = {
  1385.         default = {
  1386.             background = backgroundColor,
  1387.             text = textColor
  1388.         },
  1389.         pressed = {
  1390.             background = backgroundPressedColor,
  1391.             text = textPressedColor
  1392.         },
  1393.         disabled = {
  1394.             text = disabledColor
  1395.         },
  1396.         separator = separatorColor,
  1397.         transparency = {
  1398.             background = backgroundTransparency,
  1399.             shadow = shadowTransparency
  1400.         }
  1401.     }
  1402.  
  1403.     menu.itemsContainer = menu:addChild(GUI.container(1, 1, menu.width, menu.height))
  1404.     menu.prevButton = menu:addChild(GUI.button(1, 1, menu.width, 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, "▲"))
  1405.     menu.nextButton = menu:addChild(GUI.button(1, 1, menu.width, 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, "▼"))
  1406.     menu.prevButton.colors.transparency, menu.nextButton.colors.transparency = backgroundTransparency, backgroundTransparency
  1407.     menu.prevButton.onTouch = function()
  1408.         dropDownMenuScrollDown(menu)
  1409.     end
  1410.     menu.nextButton.onTouch = function()
  1411.         dropDownMenuScrollUp(menu)
  1412.     end
  1413.  
  1414.     menu.itemHeight = itemHeight
  1415.     menu.addSeparator = dropDownMenuAddSeparator
  1416.     menu.addItem = dropDownMenuAddItem
  1417.     menu.draw = dropDownMenuDraw
  1418.     menu.show = dropDownMenuShow
  1419.     menu.maximumHeight = maximumHeight
  1420.     menu.eventHandler = dropDownMenuEventHandler
  1421.  
  1422.     return menu
  1423. end
  1424.  
  1425. ----------------------------------------- Context Menu -----------------------------------------
  1426.  
  1427. local function contextMenuCalculate(menu)
  1428.     local widestItem, widestShortcut = 0, 0
  1429.     for i = 1, #menu.itemsContainer.children do
  1430.         if menu.itemsContainer.children[i].type == GUI.dropDownMenuElementTypes.default then
  1431.             widestItem = math.max(widestItem, unicode.len(menu.itemsContainer.children[i].text))
  1432.             if menu.itemsContainer.children[i].shortcut then
  1433.                 widestShortcut = math.max(widestShortcut, unicode.len(menu.itemsContainer.children[i].shortcut))
  1434.             end
  1435.         end
  1436.     end
  1437.     menu.width = 2 + widestItem + (widestShortcut > 0 and 3 + widestShortcut or 0)
  1438.     menu.height = #menu.itemsContainer.children
  1439. end
  1440.  
  1441. local function contextMenuShow(menu)
  1442.     contextMenuCalculate(menu)
  1443.  
  1444.     local bufferWidth, bufferHeight = buffer.getResolution()
  1445.     if menu.y + menu.height >= bufferHeight then menu.y = bufferHeight - menu.height end
  1446.     if menu.x + menu.width + 1 >= bufferWidth then menu.x = bufferWidth - menu.width - 1 end
  1447.  
  1448.     return dropDownMenuShow(menu)
  1449. end
  1450.  
  1451. local function contextMenuAddItem(menu, ...)
  1452.     contextMenuCalculate(menu)
  1453.     return dropDownMenuAddItem(menu, ...)
  1454. end
  1455.  
  1456. local function contextMenuAddSeparator(menu, ...)
  1457.     contextMenuCalculate(menu)
  1458.     return dropDownMenuAddSeparator(menu, ...)
  1459. end
  1460.  
  1461. local function contextMenuAddSubMenu(menu, text)
  1462.     local item = menu:addItem(text, false, "►")
  1463.     item.subMenu = GUI.contextMenu(1, 1)
  1464.     item.subMenu.colors = menu.colors
  1465.    
  1466.     return item.subMenu
  1467. end
  1468.  
  1469. function GUI.contextMenu(x, y, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, backgroundTransparency, shadowTransparency)
  1470.     local menu = GUI.dropDownMenu(x, y, 1, math.ceil(buffer.getHeight() * 0.5), 1,
  1471.         backgroundColor or GUI.colors.contextMenu.default.background,
  1472.         textColor or GUI.colors.contextMenu.default.text,
  1473.         backgroundPressedColor or GUI.colors.contextMenu.pressed.background,
  1474.         textPressedColor or GUI.colors.contextMenu.pressed.text,
  1475.         disabledColor or GUI.colors.contextMenu.disabled,
  1476.         separatorColor or GUI.colors.contextMenu.separator,
  1477.         backgroundTransparency or GUI.colors.contextMenu.transparency.background,
  1478.         shadowTransparency or GUI.colors.contextMenu.transparency.shadow
  1479.     )
  1480.    
  1481.     menu.colors.transparency.background = menu.colors.transparency.background or GUI.colors.contextMenu.transparency.background
  1482.     menu.colors.transparency.shadow = menu.colors.transparency.shadow or GUI.colors.contextMenu.transparency.shadow
  1483.    
  1484.     menu.show = contextMenuShow
  1485.     menu.addSubMenu = contextMenuAddSubMenu
  1486.     menu.addItem = contextMenuAddItem
  1487.     menu.addSeparator = contextMenuAddSeparator
  1488.  
  1489.     return menu
  1490. end
  1491.  
  1492. ----------------------------------------- Combo Box Object -----------------------------------------
  1493.  
  1494. local function drawComboBox(object)
  1495.     buffer.square(object.x, object.y, object.width, object.height, object.colors.default.background, object.colors.default.text, " ")
  1496.     if object.dropDownMenu.itemsContainer.children[object.selectedItem] then
  1497.         buffer.text(object.x + 1, math.floor(object.y + object.height / 2), object.colors.default.text, string.limit(object.dropDownMenu.itemsContainer.children[object.selectedItem].text, object.width - object.height - 4, "right"))
  1498.     end
  1499.     GUI.button(object.x + object.width - object.height * 2 + 1, object.y, object.height * 2 - 1, object.height, object.colors.arrow.background, object.colors.arrow.text, 0x0, 0x0, object.pressed and "▲" or "▼"):draw()
  1500.  
  1501.     return object
  1502. end
  1503.  
  1504. local function comboBoxGetItem(object, index)
  1505.     return object.dropDownMenu.itemsContainer.children[index]
  1506. end
  1507.  
  1508. local function comboBoxCount(object)
  1509.     return #object.dropDownMenu.itemsContainer.children
  1510. end
  1511.  
  1512. local function comboBoxClear(object)
  1513.     object.dropDownMenu.itemsContainer:deleteChildren()
  1514.     object.selectedItem = 1
  1515.  
  1516.     return object
  1517. end
  1518.  
  1519. local function comboBoxIndexOfItem(object, text)
  1520.     for i = 1, #object.dropDownMenu.itemsContainer.children do
  1521.         if object.dropDownMenu.itemsContainer.children[i].text == text then
  1522.             return i
  1523.         end
  1524.     end
  1525. end
  1526.  
  1527. local function selectComboBoxItem(object)
  1528.     object.pressed = true
  1529.     object:draw()
  1530.  
  1531.     object.dropDownMenu.x, object.dropDownMenu.y = object.x, object.y + object.height
  1532.     object.dropDownMenu.width = object.width
  1533.     local _, selectedItem = object.dropDownMenu:show()
  1534.  
  1535.     object.selectedItem = selectedItem or object.selectedItem
  1536.     object.pressed = false
  1537.     object:draw()
  1538.     buffer.draw()
  1539.  
  1540.     return object
  1541. end
  1542.  
  1543. local function comboBoxEventHandler(mainContainer, object, eventData)
  1544.     if eventData[1] == "touch" then
  1545.         object:selectItem()
  1546.         callMethod(object.onItemSelected, object.dropDownMenu.itemsContainer.children[object.selectedItem], eventData)
  1547.     end
  1548. end
  1549.  
  1550. local function comboBoxAddItem(object, ...)
  1551.     return object.dropDownMenu:addItem(...)
  1552. end
  1553.  
  1554. local function comboBoxAddSeparator(object)
  1555.     return object.dropDownMenu:addSeparator()
  1556. end
  1557.  
  1558. function GUI.comboBox(x, y, width, elementHeight, backgroundColor, textColor, arrowBackgroundColor, arrowTextColor)
  1559.     local object = GUI.object(x, y, width, elementHeight)
  1560.    
  1561.     object.eventHandler = comboBoxEventHandler
  1562.     object.colors = {
  1563.         default = {
  1564.             background = backgroundColor,
  1565.             text = textColor
  1566.         },
  1567.         pressed = {
  1568.             background = GUI.colors.contextMenu.pressed.background,
  1569.             text = GUI.colors.contextMenu.pressed.text
  1570.         },
  1571.         arrow = {
  1572.             background = arrowBackgroundColor,
  1573.             text = arrowTextColor
  1574.         }
  1575.     }
  1576.  
  1577.     object.dropDownMenu = GUI.dropDownMenu(1, 1, 1, math.ceil(buffer.getHeight() * 0.5), elementHeight,
  1578.         object.colors.default.background,
  1579.         object.colors.default.text,
  1580.         object.colors.pressed.background,
  1581.         object.colors.pressed.text,
  1582.         GUI.colors.contextMenu.disabled,
  1583.         GUI.colors.contextMenu.separator,
  1584.         GUI.colors.contextMenu.transparency.background,
  1585.         GUI.colors.contextMenu.transparency.shadow
  1586.     )
  1587.     object.selectedItem = 1
  1588.     object.addItem = comboBoxAddItem
  1589.     object.addSeparator = comboBoxAddSeparator
  1590.     object.draw = drawComboBox
  1591.     object.selectItem = selectComboBoxItem
  1592.     object.clear = comboBoxClear
  1593.     object.indexOfItem = comboBoxIndexOfItem
  1594.     object.getItem = comboBoxGetItem
  1595.     object.count = comboBoxCount
  1596.  
  1597.     return object
  1598. end
  1599.  
  1600. ----------------------------------------- Switch and label object -----------------------------------------
  1601.  
  1602. local function switchAndLabelDraw(switchAndLabel)
  1603.     switchAndLabel.label.width = switchAndLabel.width
  1604.     switchAndLabel.switch.localX = switchAndLabel.width - switchAndLabel.switch.width
  1605.  
  1606.     switchAndLabel.label.x, switchAndLabel.label.y = switchAndLabel.x + switchAndLabel.label.localX - 1, switchAndLabel.y + switchAndLabel.label.localY - 1
  1607.     switchAndLabel.switch.x, switchAndLabel.switch.y = switchAndLabel.x + switchAndLabel.switch.localX - 1, switchAndLabel.y + switchAndLabel.switch.localY - 1
  1608.    
  1609.     switchAndLabel.label:draw()
  1610.     switchAndLabel.switch:draw()
  1611.  
  1612.     return switchAndLabel
  1613. end
  1614.  
  1615. function GUI.switchAndLabel(x, y, width, switchWidth, activeColor, passiveColor, pipeColor, textColor, text, switchState)
  1616.     local switchAndLabel = GUI.container(x, y, width, 1)
  1617.  
  1618.     switchAndLabel.label = switchAndLabel:addChild(GUI.label(1, 1, width, 1, textColor, text))
  1619.     switchAndLabel.switch = switchAndLabel:addChild(GUI.switch(1, 1, switchWidth, activeColor, passiveColor, pipeColor, switchState))
  1620.     switchAndLabel.draw = switchAndLabelDraw
  1621.  
  1622.     return switchAndLabel
  1623. end
  1624.  
  1625. ----------------------------------------- Horizontal Slider Object -----------------------------------------
  1626.  
  1627. local function sliderDraw(object)
  1628.     -- На всякий случай делаем значение не меньше минимального и не больше максимального
  1629.     object.value = math.min(math.max(object.value, object.minimumValue), object.maximumValue)
  1630.    
  1631.     if object.showMaximumAndMinimumValues then
  1632.         local stringMaximumValue, stringMinimumValue = tostring(object.roundValues and math.floor(object.maximumValue) or math.roundToDecimalPlaces(object.maximumValue, 2)), tostring(object.roundValues and math.floor(object.minimumValue) or math.roundToDecimalPlaces(object.minimumValue, 2))
  1633.         buffer.text(object.x - unicode.len(stringMinimumValue) - 1, object.y, object.colors.value, stringMinimumValue)
  1634.         buffer.text(object.x + object.width + 1, object.y, object.colors.value, stringMaximumValue)
  1635.     end
  1636.  
  1637.     if object.currentValuePrefix or object.currentValuePostfix then
  1638.         local stringCurrentValue = (object.currentValuePrefix or "") .. (object.roundValues and math.floor(object.value) or math.roundToDecimalPlaces(object.value, 2)) .. (object.currentValuePostfix or "")
  1639.         buffer.text(math.floor(object.x + object.width / 2 - unicode.len(stringCurrentValue) / 2), object.y + 1, object.colors.value, stringCurrentValue)
  1640.     end
  1641.  
  1642.     local activeWidth = math.floor(object.width - ((object.maximumValue - object.value) * object.width / (object.maximumValue - object.minimumValue)))
  1643.     buffer.text(object.x, object.y, object.colors.passive, string.rep("━", object.width))
  1644.     buffer.text(object.x, object.y, object.colors.active, string.rep("━", activeWidth))
  1645.     buffer.text(object.x + activeWidth - 1, object.y, object.colors.pipe, "⬤")
  1646.  
  1647.     return object
  1648. end
  1649.  
  1650. local function sliderEventHandler(mainContainer, object, eventData)
  1651.     if eventData[1] == "touch" or eventData[1] == "drag" then
  1652.         local clickPosition = eventData[3] - object.x + 1
  1653.         object.value = object.minimumValue + (clickPosition * (object.maximumValue - object.minimumValue) / object.width)
  1654.         mainContainer:draw()
  1655.         buffer.draw()
  1656.         callMethod(object.onValueChanged, object.value, eventData)
  1657.     end
  1658. end
  1659.  
  1660. function GUI.slider(x, y, width, activeColor, passiveColor, pipeColor, valueColor, minimumValue, maximumValue, value, showMaximumAndMinimumValues, currentValuePrefix, currentValuePostfix)
  1661.     local object = GUI.object(x, y, width, 1)
  1662.    
  1663.     object.eventHandler = sliderEventHandler
  1664.     object.colors = {active = activeColor, passive = passiveColor, pipe = pipeColor, value = valueColor}
  1665.     object.draw = sliderDraw
  1666.     object.minimumValue = minimumValue
  1667.     object.maximumValue = maximumValue
  1668.     object.value = value
  1669.     object.showMaximumAndMinimumValues = showMaximumAndMinimumValues
  1670.     object.currentValuePrefix = currentValuePrefix
  1671.     object.currentValuePostfix = currentValuePostfix
  1672.     object.roundValues = false
  1673.    
  1674.     return object
  1675. end
  1676.  
  1677. ----------------------------------------- Switch object -----------------------------------------
  1678.  
  1679. local function switchDraw(switch)
  1680.     buffer.text(switch.x - 1, switch.y, switch.colors.passive, "⠰")
  1681.     buffer.square(switch.x, switch.y, switch.width, 1, switch.colors.passive, 0x000000, " ")
  1682.     buffer.text(switch.x + switch.width, switch.y, switch.colors.passive, "⠆")
  1683.  
  1684.     buffer.text(switch.x - 1, switch.y, switch.colors.active, "⠰")
  1685.     buffer.square(switch.x, switch.y, switch.pipePosition - 1, 1, switch.colors.active, 0x000000, " ")
  1686.  
  1687.     buffer.text(switch.x + switch.pipePosition - 2, switch.y, switch.colors.pipe, "⠰")
  1688.     buffer.square(switch.x + switch.pipePosition - 1, switch.y, 2, 1, switch.colors.pipe, 0x000000, " ")
  1689.     buffer.text(switch.x + switch.pipePosition + 1, switch.y, switch.colors.pipe, "⠆")
  1690.    
  1691.     return switch
  1692. end
  1693.  
  1694. local function switchSetState(switch, state)
  1695.     switch.state = state
  1696.     switch.pipePosition = switch.state and switch.width - 1 or 1
  1697.  
  1698.     return switch
  1699. end
  1700.  
  1701. local function switchEventHandler(mainContainer, switch, eventData)
  1702.     if eventData[1] == "touch" then
  1703.         switch.state = not switch.state
  1704.         switch:addAnimation(
  1705.             function(mainContainer, animation)
  1706.                 if switch.state then
  1707.                     switch.pipePosition = math.round(1 + animation.position * (switch.width - 2))
  1708.                 else   
  1709.                     switch.pipePosition = math.round(1 + (1 - animation.position) * (switch.width - 2))
  1710.                 end
  1711.             end,
  1712.             function(mainContainer, animation)
  1713.                 animation:delete()
  1714.                 callMethod(switch.onStateChanged, mainContainer, switch, eventData, switch.state)
  1715.             end
  1716.         ):start(switch.animationDuration)
  1717.     end
  1718. end
  1719.  
  1720. function GUI.switch(x, y, width, activeColor, passiveColor, pipeColor, state)
  1721.     local switch = GUI.object(x, y, width, 1)
  1722.  
  1723.     switch.pipePosition = 1
  1724.     switch.eventHandler = switchEventHandler
  1725.     switch.colors = {
  1726.         active = activeColor,
  1727.         passive = passiveColor,
  1728.         pipe = pipeColor,
  1729.     }
  1730.     switch.draw = switchDraw
  1731.     switch.state = state or false
  1732.     switch.update = switchUpdate
  1733.     switch.animated = true
  1734.     switch.animationDuration = 0.3
  1735.     switch.setState = switchSetState
  1736.  
  1737.     switch:setState(state)
  1738.    
  1739.     return switch
  1740. end
  1741.  
  1742. ----------------------------------------- Layout object -----------------------------------------
  1743.  
  1744. local function layoutCheckCell(layout, column, row)
  1745.     if column < 1 or column > #layout.columnSizes or row < 1 or row > #layout.rowSizes then
  1746.         error("Specified grid position (" .. tostring(column) .. "x" .. tostring(row) .. ") is out of layout grid range")
  1747.     end
  1748. end
  1749.  
  1750. local function layoutGetAbsoluteTotalSize(array)
  1751.     local absoluteTotalSize = 0
  1752.     for i = 1, #array do
  1753.         if array[i].sizePolicy == GUI.sizePolicies.absolute then
  1754.             absoluteTotalSize = absoluteTotalSize + array[i].size
  1755.         end
  1756.     end
  1757.     return absoluteTotalSize
  1758. end
  1759.  
  1760. local function layoutGetCalculatedSize(array, index, dependency)
  1761.     if array[index].sizePolicy == GUI.sizePolicies.percentage then
  1762.         array[index].calculatedSize = array[index].size * dependency
  1763.     else
  1764.         array[index].calculatedSize = array[index].size
  1765.     end
  1766. end
  1767.  
  1768. local function layoutUpdate(layout)
  1769.     local columnPercentageTotalSize, rowPercentageTotalSize = layout.width - layoutGetAbsoluteTotalSize(layout.columnSizes), layout.height - layoutGetAbsoluteTotalSize(layout.rowSizes)
  1770.     for row = 1, #layout.rowSizes do
  1771.         layoutGetCalculatedSize(layout.rowSizes, row, rowPercentageTotalSize)
  1772.         for column = 1, #layout.columnSizes do
  1773.             layoutGetCalculatedSize(layout.columnSizes, column, columnPercentageTotalSize)
  1774.             layout.cells[row][column].totalWidth, layout.cells[row][column].totalHeight = 0, 0
  1775.         end
  1776.     end
  1777.  
  1778.     -- Подготавливаем объекты к расположению и подсчитываем тотальные размеры
  1779.     local child, layoutRow, layoutColumn, cell
  1780.     for i = 1, #layout.children do
  1781.         child = layout.children[i]
  1782.        
  1783.         if not child.hidden then
  1784.             layoutRow, layoutColumn = child.layoutRow, child.layoutColumn
  1785.  
  1786.             -- Проверка на позицию в сетке
  1787.             if layoutRow >= 1 and layoutRow <= #layout.rowSizes and layoutColumn >= 1 and layoutColumn <= #layout.columnSizes then
  1788.                 cell = layout.cells[layoutRow][layoutColumn]
  1789.                 -- Авто-фиттинг объектов
  1790.                 if cell.fitting.horizontal then
  1791.                     child.width = math.round(layout.columnSizes[layoutColumn].calculatedSize - cell.fitting.horizontalRemove)
  1792.                 end
  1793.                 if cell.fitting.vertical then
  1794.                     child.height = math.round(layout.rowSizes[layoutRow].calculatedSize - cell.fitting.verticalRemove)
  1795.                 end
  1796.  
  1797.                 -- Направление и расчет размеров
  1798.                 if cell.direction == GUI.directions.horizontal then
  1799.                     cell.totalWidth = cell.totalWidth + child.width + cell.spacing
  1800.                     cell.totalHeight = math.max(cell.totalHeight, child.height)
  1801.                 else
  1802.                     cell.totalWidth = math.max(cell.totalWidth, child.width)
  1803.                     cell.totalHeight = cell.totalHeight + child.height + cell.spacing
  1804.                 end
  1805.             else
  1806.                 error("Layout child with index " .. i .. " has been assigned to cell (" .. layoutColumn .. "x" .. layoutRow .. ") out of layout grid range")
  1807.             end
  1808.         end
  1809.     end
  1810.  
  1811.     -- Высчитываем позицицию объектов
  1812.     local x, y = 1, 1
  1813.     for row = 1, #layout.rowSizes do
  1814.         for column = 1, #layout.columnSizes do
  1815.             cell = layout.cells[row][column]
  1816.             cell.x, cell.y = GUI.getAlignmentCoordinates(
  1817.                 {
  1818.                     x = x,
  1819.                     y = y,
  1820.                     width = layout.columnSizes[column].calculatedSize,
  1821.                     height = layout.rowSizes[row].calculatedSize,
  1822.                     alignment = cell.alignment,
  1823.                 },
  1824.                 {
  1825.                     width = cell.totalWidth - (cell.direction == GUI.directions.horizontal and cell.spacing or 0),
  1826.                     height = cell.totalHeight - (cell.direction == GUI.directions.vertical and cell.spacing or 0),
  1827.                 }
  1828.             )
  1829.             if cell.margin then
  1830.                 cell.x, cell.y = GUI.getMarginCoordinates(cell)
  1831.             end
  1832.  
  1833.             x = x + layout.columnSizes[column].calculatedSize
  1834.         end
  1835.  
  1836.         x, y = 1, y + layout.rowSizes[row].calculatedSize
  1837.     end
  1838.  
  1839.     -- Размещаем все объекты
  1840.     for i = 1, #layout.children do
  1841.         child = layout.children[i]
  1842.        
  1843.         if not child.hidden then
  1844.             cell = layout.cells[child.layoutRow][child.layoutColumn]
  1845.             if cell.direction == GUI.directions.horizontal then
  1846.                 child.localX = math.round(cell.x)
  1847.                 child.localY = math.round(cell.y + cell.totalHeight / 2 - child.height / 2)
  1848.                
  1849.                 cell.x = cell.x + child.width + cell.spacing
  1850.             else
  1851.                 child.localX = math.round(cell.x + cell.totalWidth / 2 - child.width / 2)
  1852.                 child.localY = math.round(cell.y)
  1853.                
  1854.                 cell.y = cell.y + child.height + cell.spacing
  1855.             end
  1856.         end
  1857.     end
  1858. end
  1859.  
  1860. local function layoutSetCellPosition(layout, column, row, object)
  1861.     layoutCheckCell(layout, column, row)
  1862.     object.layoutRow = row
  1863.     object.layoutColumn = column
  1864.  
  1865.     return object
  1866. end
  1867.  
  1868. local function layoutSetCellDirection(layout, column, row, direction)
  1869.     layoutCheckCell(layout, column, row)
  1870.     layout.cells[row][column].direction = direction
  1871.  
  1872.     return layout
  1873. end
  1874.  
  1875. local function layoutSetCellSpacing(layout, column, row, spacing)
  1876.     layoutCheckCell(layout, column, row)
  1877.     layout.cells[row][column].spacing = spacing
  1878.  
  1879.     return layout
  1880. end
  1881.  
  1882. local function layoutSetCellAlignment(layout, column, row, horizontalAlignment, verticalAlignment)
  1883.     layoutCheckCell(layout, column, row)
  1884.     layout.cells[row][column].alignment.horizontal, layout.cells[row][column].alignment.vertical = horizontalAlignment, verticalAlignment
  1885.  
  1886.     return layout
  1887. end
  1888.  
  1889. local function layoutSetCellMargin(layout, column, row, horizontalMargin, verticalMargin)
  1890.     layoutCheckCell(layout, column, row)
  1891.     layout.cells[row][column].margin = {
  1892.         horizontal = horizontalMargin,
  1893.         vertical = verticalMargin
  1894.     }
  1895.  
  1896.     return layout
  1897. end
  1898.  
  1899. local function layoutNewCell()
  1900.     return {
  1901.         alignment = {
  1902.             horizontal = GUI.alignment.horizontal.center,
  1903.             vertical = GUI.alignment.vertical.center
  1904.         },
  1905.         direction = GUI.directions.vertical,
  1906.         fitting = {
  1907.         horizontal = false, vertical = false},
  1908.         spacing = 1,
  1909.     }
  1910. end
  1911.  
  1912. local function layoutCalculatePercentageSize(changingExistent, array, index)
  1913.     if array[index].sizePolicy == GUI.sizePolicies.percentage then
  1914.         local allPercents, beforeFromIndexPercents = 0, 0
  1915.         for i = 1, #array do
  1916.             if array[i].sizePolicy == GUI.sizePolicies.percentage then
  1917.                 allPercents = allPercents + array[i].size
  1918.  
  1919.                 if i <= index then
  1920.                     beforeFromIndexPercents = beforeFromIndexPercents + array[i].size
  1921.                 end
  1922.             end
  1923.         end
  1924.  
  1925.         local modifyer
  1926.         if changingExistent then
  1927.             if beforeFromIndexPercents > 1 then
  1928.                 error("Layout summary percentage > 100% at index " .. index)
  1929.             end
  1930.             modifyer = (1 - beforeFromIndexPercents) / (allPercents - beforeFromIndexPercents)
  1931.         else
  1932.             modifyer = (1 - array[index].size) / (allPercents - array[index].size)
  1933.         end
  1934.  
  1935.         for i = changingExistent and index + 1 or 1, #array do
  1936.             if array[i].sizePolicy == GUI.sizePolicies.percentage and i ~= index then
  1937.                 array[i].size = modifyer * array[i].size
  1938.             end
  1939.         end
  1940.     end
  1941. end
  1942.  
  1943. local function layoutSetColumnWidth(layout, column, sizePolicy, size)
  1944.     layout.columnSizes[column].sizePolicy, layout.columnSizes[column].size = sizePolicy, size
  1945.     layoutCalculatePercentageSize(true, layout.columnSizes, column)
  1946.  
  1947.     return layout
  1948. end
  1949.  
  1950. local function layoutSetRowHeight(layout, row, sizePolicy, size)
  1951.     layout.rowSizes[row].sizePolicy, layout.rowSizes[row].size = sizePolicy, size
  1952.     layoutCalculatePercentageSize(true, layout.rowSizes, row)
  1953.  
  1954.     return layout
  1955. end
  1956.  
  1957. local function layoutAddColumn(layout, sizePolicy, size)
  1958.     for i = 1, #layout.rowSizes do
  1959.         table.insert(layout.cells[i], layoutNewCell())
  1960.     end
  1961.  
  1962.     table.insert(layout.columnSizes, {
  1963.         sizePolicy = sizePolicy,
  1964.         size = size
  1965.     })
  1966.     layoutCalculatePercentageSize(false, layout.columnSizes, #layout.columnSizes)
  1967.     -- GUI.error(layout.columnSizes)
  1968.  
  1969.     return layout
  1970. end
  1971.  
  1972. local function layoutAddRow(layout, sizePolicy, size)
  1973.     local row = {}
  1974.     for i = 1, #layout.columnSizes do
  1975.         table.insert(row, layoutNewCell())
  1976.     end
  1977.  
  1978.     table.insert(layout.cells, row)
  1979.     table.insert(layout.rowSizes, {
  1980.         sizePolicy = sizePolicy,
  1981.         size = size
  1982.     })
  1983.  
  1984.     layoutCalculatePercentageSize(false, layout.rowSizes, #layout.rowSizes)
  1985.     -- GUI.error(layout.rowSizes)
  1986.  
  1987.     return layout
  1988. end
  1989.  
  1990. local function layoutRemoveRow(layout, row)
  1991.     table.remove(layout.cells, row)
  1992.  
  1993.     layout.rowSizes[row].size = 0
  1994.     layoutCalculatePercentageSize(false, layout.rowSizes, row)
  1995.  
  1996.     table.remove(layout.rowSizes, row)
  1997.  
  1998.     return layout
  1999. end
  2000.  
  2001. local function layoutRemoveColumn(layout, column)
  2002.     for i = 1, #layout.rowSizes do
  2003.         table.remove(layout.cells[i], column)
  2004.     end
  2005.  
  2006.     layout.columnSizes[column].size = 0
  2007.     layoutCalculatePercentageSize(false, layout.columnSizes, column)
  2008.  
  2009.     table.remove(layout.columnSizes, column)
  2010.  
  2011.     return layout
  2012. end
  2013.  
  2014. local function layoutSetGridSize(layout, columnCount, rowCount)
  2015.     layout.cells = {}
  2016.     layout.rowSizes = {}
  2017.     layout.columnSizes = {}
  2018.  
  2019.     local rowSize, columnSize = 1 / rowCount, 1 / columnCount
  2020.     for i = 1, rowCount do
  2021.         layoutAddRow(layout, GUI.sizePolicies.percentage, 1 / i)
  2022.     end
  2023.  
  2024.     for i = 1, columnCount do
  2025.         layoutAddColumn(layout, GUI.sizePolicies.percentage, 1 / i)
  2026.     end
  2027.  
  2028.     return layout
  2029. end
  2030.  
  2031. local function layoutDraw(layout)
  2032.     layoutUpdate(layout)
  2033.     GUI.drawContainerContent(layout)
  2034.    
  2035.     if layout.showGrid then
  2036.         local x, y = layout.x, layout.y
  2037.         for j = 1, #layout.columnSizes do
  2038.             for i = 1, #layout.rowSizes do
  2039.                 buffer.frame(
  2040.                     math.round(x),
  2041.                     math.round(y),
  2042.                     math.round(layout.columnSizes[j].calculatedSize),
  2043.                     math.round(layout.rowSizes[i].calculatedSize),
  2044.                     0xFF0000
  2045.                 )
  2046.                 y = y + layout.rowSizes[i].calculatedSize
  2047.             end
  2048.             x, y = x + layout.columnSizes[j].calculatedSize, layout.y
  2049.         end
  2050.     end
  2051. end
  2052.  
  2053. local function layoutFitToChildrenSize(layout, column, row)
  2054.     layout.width, layout.height = 0, 0
  2055.  
  2056.     for i = 1, #layout.children do
  2057.         if not layout.children[i].hidden then
  2058.             if layout.cells[row][column].direction == GUI.directions.horizontal then
  2059.                 layout.width = layout.width + layout.children[i].width + layout.cells[row][column].spacing
  2060.                 layout.height = math.max(layout.height, layout.children[i].height)
  2061.             else
  2062.                 layout.width = math.max(layout.width, layout.children[i].width)
  2063.                 layout.height = layout.height + layout.children[i].height + layout.cells[row][column].spacing
  2064.             end
  2065.         end
  2066.     end
  2067.  
  2068.     if layout.cells[row][column].direction == GUI.directions.horizontal then
  2069.         layout.width = layout.width - layout.cells[row][column].spacing
  2070.     else
  2071.         layout.height = layout.height - layout.cells[row][column].spacing
  2072.     end
  2073.  
  2074.     return layout
  2075. end
  2076.  
  2077. local function layoutSetCellFitting(layout, column, row, horizontal, vertical, horizontalRemove, verticalRemove )
  2078.     layoutCheckCell(layout, column, row)
  2079.     layout.cells[row][column].fitting = {
  2080.         horizontal = horizontal,
  2081.         vertical = vertical,
  2082.         horizontalRemove = horizontalRemove or 0,
  2083.         verticalRemove = verticalRemove or 0,
  2084.     }
  2085.  
  2086.     return layout
  2087. end
  2088.  
  2089. local function layoutAddChild(layout, object, ...)
  2090.     object.layoutRow = layout.defaultRow
  2091.     object.layoutColumn = layout.defaultColumn
  2092.     GUI.addChildToContainer(layout, object, ...)
  2093.  
  2094.     return object
  2095. end
  2096.  
  2097. function GUI.layout(x, y, width, height, columnCount, rowCount)
  2098.     local layout = GUI.container(x, y, width, height)
  2099.  
  2100.     layout.defaultRow = 1
  2101.     layout.defaultColumn = 1
  2102.  
  2103.     layout.addRow = layoutAddRow
  2104.     layout.addColumn = layoutAddColumn
  2105.     layout.removeRow = layoutRemoveRow
  2106.     layout.removeColumn = layoutRemoveColumn
  2107.  
  2108.     layout.setRowHeight = layoutSetRowHeight
  2109.     layout.setColumnWidth = layoutSetColumnWidth
  2110.  
  2111.     layout.setCellPosition = layoutSetCellPosition
  2112.     layout.setCellDirection = layoutSetCellDirection
  2113.     layout.setGridSize = layoutSetGridSize
  2114.     layout.setCellSpacing = layoutSetCellSpacing
  2115.     layout.setCellAlignment = layoutSetCellAlignment
  2116.     layout.setCellMargin = layoutSetCellMargin
  2117.    
  2118.     layout.fitToChildrenSize = layoutFitToChildrenSize
  2119.     layout.setCellFitting = layoutSetCellFitting
  2120.  
  2121.     layout.addChild = layoutAddChild
  2122.     layout.draw = layoutDraw
  2123.  
  2124.     layoutSetGridSize(layout, columnCount, rowCount)
  2125.  
  2126.     return layout
  2127. end
  2128.  
  2129. -----------------------------------------------------------------------------------------------------
  2130.  
  2131. local function filesystemDialogDraw(filesystemDialog)
  2132.     if filesystemDialog.extensionComboBox.hidden then
  2133.         filesystemDialog.input.width = filesystemDialog.cancelButton.localX - 4
  2134.     else
  2135.         filesystemDialog.input.width = filesystemDialog.extensionComboBox.localX - 3
  2136.     end
  2137.  
  2138.     if filesystemDialog.IOMode == GUI.filesystemModes.save then
  2139.         filesystemDialog.submitButton.disabled = not filesystemDialog.input.text
  2140.     else
  2141.         filesystemDialog.input.text = filesystemDialog.filesystemTree.selectedItem or ""
  2142.         filesystemDialog.submitButton.disabled = not filesystemDialog.filesystemTree.selectedItem
  2143.     end
  2144.    
  2145.     GUI.drawContainerContent(filesystemDialog)
  2146.     GUI.windowShadow(filesystemDialog.x, filesystemDialog.y, filesystemDialog.width, filesystemDialog.height, GUI.colors.contextMenu.transparency.shadow, true)
  2147.  
  2148.     return filesystemDialog
  2149. end
  2150.  
  2151. local function filesystemDialogSetMode(filesystemDialog, IOMode, filesystemMode)
  2152.     filesystemDialog.IOMode = IOMode
  2153.     filesystemDialog.filesystemMode = filesystemMode
  2154.  
  2155.     if filesystemDialog.IOMode == GUI.filesystemModes.save then
  2156.         filesystemDialog.filesystemTree.showMode = GUI.filesystemModes.directory
  2157.         filesystemDialog.filesystemTree.selectionMode = GUI.filesystemModes.directory
  2158.         filesystemDialog.input.disabled = false
  2159.         filesystemDialog.extensionComboBox.hidden = filesystemDialog.filesystemMode ~= GUI.filesystemModes.file or not filesystemDialog.filesystemTree.extensionFilters
  2160.     else
  2161.         if filesystemDialog.filesystemMode == GUI.filesystemModes.file then
  2162.             filesystemDialog.filesystemTree.showMode = GUI.filesystemModes.both
  2163.             filesystemDialog.filesystemTree.selectionMode = GUI.filesystemModes.file
  2164.         else
  2165.             filesystemDialog.filesystemTree.showMode = GUI.filesystemModes.directory
  2166.             filesystemDialog.filesystemTree.selectionMode = GUI.filesystemModes.directory
  2167.         end
  2168.  
  2169.         filesystemDialog.input.disabled = true
  2170.         filesystemDialog.extensionComboBox.hidden = true
  2171.     end
  2172.  
  2173.     filesystemDialog.filesystemTree:updateFileList()
  2174. end
  2175.  
  2176. local function filesystemDialogAddExtensionFilter(filesystemDialog, extension)
  2177.     filesystemDialog.extensionComboBox:addItem(extension)
  2178.     filesystemDialog.extensionComboBox.width = math.max(filesystemDialog.extensionComboBox.width, unicode.len(extension) + 3)
  2179.     filesystemDialog.extensionComboBox.localX = filesystemDialog.cancelButton.localX - filesystemDialog.extensionComboBox.width - 2
  2180.     filesystemDialog.filesystemTree:addExtensionFilter(extension)
  2181.  
  2182.     filesystemDialog:setMode(filesystemDialog.IOMode, filesystemDialog.filesystemMode)
  2183. end
  2184.  
  2185. function GUI.filesystemDialog(x, y, width, height, submitButtonText, cancelButtonText, placeholderText, path)
  2186.     local filesystemDialog = GUI.container(x, y, width, height)
  2187.    
  2188.     filesystemDialog:addChild(GUI.panel(1, height - 2, width, 3, 0xD2D2D2))
  2189.    
  2190.     filesystemDialog.cancelButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 2, 0, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xE1E1E1, cancelButtonText))
  2191.     filesystemDialog.submitButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 2, 0, 0x3C3C3C, 0xE1E1E1, 0xE1E1E1, 0x3C3C3C, submitButtonText))
  2192.     filesystemDialog.submitButton.localX = filesystemDialog.width - filesystemDialog.submitButton.width - 1
  2193.     filesystemDialog.cancelButton.localX = filesystemDialog.submitButton.localX - filesystemDialog.cancelButton.width - 2
  2194.  
  2195.     filesystemDialog.extensionComboBox = filesystemDialog:addChild(GUI.comboBox(1, height - 1, 1, 1, 0xE1E1E1, 0x666666, 0xC3C3C3, 0x888888))
  2196.     filesystemDialog.extensionComboBox.hidden = true
  2197.  
  2198.     filesystemDialog.input = filesystemDialog:addChild(GUI.input(2, height - 1, 1, 1, 0xE1E1E1, 0x666666, 0x999999, 0xE1E1E1, 0x3C3C3C, "", placeholderText))
  2199.  
  2200.     filesystemDialog.filesystemTree = filesystemDialog:addChild(GUI.filesystemTree(1, 1, width, height - 3, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xAAAAAA, 0x3C3C3C, 0xE1E1E1, 0xBBBBBB, 0xAAAAAA, 0xC3C3C3, 0x444444))
  2201.     filesystemDialog.filesystemTree.workPath = path
  2202.  
  2203.     filesystemDialog.draw = filesystemDialogDraw
  2204.     filesystemDialog.setMode = filesystemDialogSetMode
  2205.     filesystemDialog.addExtensionFilter = filesystemDialogAddExtensionFilter
  2206.  
  2207.     filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file)
  2208.  
  2209.     return filesystemDialog
  2210. end
  2211.  
  2212. local function filesystemDialogShow(filesystemDialog)
  2213.     filesystemDialog:addAnimation(
  2214.         function(mainContainer, animation)
  2215.             filesystemDialog.localY = math.floor(1 + (1.0 - animation.position) * (-filesystemDialog.height))
  2216.         end,
  2217.         function(mainContainer, animation)
  2218.             animation:delete()
  2219.         end
  2220.     ):start(0.5)
  2221.  
  2222.     return filesystemDialog
  2223. end
  2224.  
  2225. -----------------------------------------------------------------------------------------------------
  2226.  
  2227. function GUI.addFilesystemDialogToContainer(parentContainer, ...)
  2228.     local container = parentContainer:addChild(GUI.container(1, 1, parentContainer.width, parentContainer.height))
  2229.     container:addChild(GUI.object(1, 1, container.width, container.height))
  2230.    
  2231.     local filesystemDialog = container:addChild(GUI.filesystemDialog(1, 1, math.floor(container.width * 0.35), math.floor(container.height * 0.8), ...))
  2232.     filesystemDialog.localX = math.floor(container.width / 2 - filesystemDialog.width / 2)
  2233.     filesystemDialog.localY = -filesystemDialog.height
  2234.  
  2235.     local function onAnyTouch()
  2236.         local firstParent = filesystemDialog:getFirstParent()
  2237.         container:delete()
  2238.         firstParent:draw()
  2239.         buffer.draw()
  2240.     end
  2241.  
  2242.     filesystemDialog.cancelButton.onTouch = function()
  2243.         onAnyTouch()
  2244.         callMethod(filesystemDialog.onCancel)
  2245.     end
  2246.  
  2247.     filesystemDialog.submitButton.onTouch = function()
  2248.         onAnyTouch()
  2249.        
  2250.         local path = filesystemDialog.filesystemTree.selectedItem or filesystemDialog.filesystemTree.workPath or "/"
  2251.         if filesystemDialog.IOMode == GUI.filesystemModes.save then
  2252.             path = path .. filesystemDialog.input.text
  2253.            
  2254.             if filesystemDialog.filesystemMode == GUI.filesystemModes.file then
  2255.                 local selectedItem = filesystemDialog.extensionComboBox:getItem(filesystemDialog.extensionComboBox.selectedItem)
  2256.                 path = path .. (selectedItem and selectedItem.text or "")
  2257.             else
  2258.                 path = path .. "/"
  2259.             end
  2260.         end
  2261.  
  2262.         callMethod(filesystemDialog.onSubmit, path)
  2263.     end
  2264.  
  2265.     filesystemDialog.show = filesystemDialogShow
  2266.  
  2267.     return filesystemDialog
  2268. end
  2269.  
  2270. -----------------------------------------------------------------------------------------------------
  2271.  
  2272. local function filesystemChooserDraw(object)
  2273.     local tipWidth = object.height * 2 - 1
  2274.     local y = math.floor(object.y + object.height / 2)
  2275.    
  2276.     buffer.square(object.x, object.y, object.width - tipWidth, object.height, object.colors.background, object.colors.text, " ")
  2277.     buffer.square(object.x + object.width - tipWidth, object.y, tipWidth, object.height, object.pressed and object.colors.tipText or object.colors.tipBackground, object.pressed and object.colors.tipBackground or object.colors.tipText, " ")
  2278.     buffer.text(object.x + object.width - math.floor(tipWidth / 2) - 1, y, object.pressed and object.colors.tipBackground or object.colors.tipText, "…")
  2279.     buffer.text(object.x + 1, y, object.colors.text, string.limit(object.path or object.placeholderText, object.width - tipWidth - 2, "left"))
  2280.  
  2281.     return filesystemChooser
  2282. end
  2283.  
  2284. local function filesystemChooserAddExtensionFilter(object, extension)
  2285.     object.extensionFilters[unicode.lower(extension)] = true
  2286. end
  2287.  
  2288. local function filesystemChooserSetMode(object, filesystemMode)
  2289.     object.filesystemMode = filesystemMode
  2290. end
  2291.  
  2292. local function filesystemChooserEventHandler(mainContainer, object, eventData)
  2293.     if eventData[1] == "touch" then
  2294.         object.pressed = true
  2295.         mainContainer:draw()
  2296.         buffer.draw()
  2297.  
  2298.         local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, object.submitButtonText, object.cancelButtonText, object.placeholderText, object.filesystemDialogPath)      
  2299.        
  2300.         for key in pairs(object.extensionFilters) do
  2301.             filesystemDialog:addExtensionFilter(key)
  2302.         end
  2303.         filesystemDialog:setMode(GUI.filesystemModes.open, object.filesystemMode)
  2304.        
  2305.         filesystemDialog.onCancel = function()
  2306.             object.pressed = false
  2307.  
  2308.             mainContainer:draw()
  2309.             buffer.draw()
  2310.         end
  2311.  
  2312.         filesystemDialog.onSubmit = function(path)
  2313.             object.path = path
  2314.             object.pressed = false
  2315.  
  2316.             mainContainer:draw()
  2317.             buffer.draw()
  2318.             callMethod(object.onSubmit, object.path)
  2319.         end
  2320.  
  2321.         filesystemDialog:show()
  2322.     end
  2323. end
  2324.  
  2325. function GUI.filesystemChooser(x, y, width, height, backgroundColor, textColor, tipBackgroundColor, tipTextColor, path, submitButtonText, cancelButtonText, placeholderText, filesystemDialogPath)
  2326.     local object = GUI.object(x, y, width, height)
  2327.    
  2328.     object.eventHandler = comboBoxEventHandler
  2329.     object.colors = {
  2330.         tipBackground = tipBackgroundColor,
  2331.         tipText = tipTextColor,
  2332.         text = textColor,
  2333.         background = backgroundColor
  2334.     }
  2335.  
  2336.     object.submitButtonText = submitButtonText
  2337.     object.cancelButtonText = cancelButtonText
  2338.     object.placeholderText = placeholderText
  2339.     object.pressed = false
  2340.     object.path = path
  2341.     object.filesystemDialogPath = filesystemDialogPath
  2342.     object.filesystemMode = GUI.filesystemModes.file
  2343.     object.extensionFilters = {}
  2344.  
  2345.     object.draw = filesystemChooserDraw
  2346.     object.eventHandler = filesystemChooserEventHandler
  2347.     object.addExtensionFilter = filesystemChooserAddExtensionFilter
  2348.     object.setMode = filesystemChooserSetMode
  2349.  
  2350.     return object
  2351. end
  2352.  
  2353. -----------------------------------------------------------------------------------------------------
  2354.  
  2355. local function resizerDraw(object)
  2356.     local horizontalMode = object.width >= object.height
  2357.     local x, y, symbol
  2358.     if horizontalMode then
  2359.         buffer.text(object.x, math.floor(object.y + object.height / 2), object.colors.helper, string.rep("━", object.width))
  2360.     else
  2361.         local x = math.floor(object.x + object.width / 2)
  2362.         for i = object.y, object.y + object.height - 1 do
  2363.             local index = buffer.getIndex(x, i)
  2364.             buffer.rawSet(index, buffer.rawGet(index), object.colors.helper, "┃")
  2365.         end
  2366.     end
  2367.  
  2368.     if object.touchPosition then
  2369.         buffer.text(object.touchPosition.x - 1, object.touchPosition.y, object.colors.arrow, "←→")
  2370.     end
  2371. end
  2372.  
  2373. local function resizerEventHandler(mainContainer, object, eventData)
  2374.     if eventData[1] == "touch" then
  2375.         object.touchPosition = {x = eventData[3], y = eventData[4]}
  2376.        
  2377.         mainContainer:draw()
  2378.         buffer.draw()
  2379.     elseif eventData[1] == "drag" and object.touchPosition then
  2380.         local x, y = object.touchPosition.x, object.touchPosition.y
  2381.         object.touchPosition.x, object.touchPosition.y = eventData[3], eventData[4]
  2382.        
  2383.         if object.onResize then
  2384.             object.onResize(mainContainer, object, eventData, eventData[3] - x, eventData[4] - y)
  2385.         end
  2386.  
  2387.         mainContainer:draw()
  2388.         buffer.draw()
  2389.     elseif eventData[1] == "drop" then
  2390.         object.touchPosition = nil
  2391.  
  2392.         if object.onResizeFinished then
  2393.             object.onResizeFinished(mainContainer, object, eventData)
  2394.         end
  2395.  
  2396.         mainContainer:draw()
  2397.         buffer.draw()
  2398.     end
  2399. end
  2400.  
  2401. function GUI.resizer(x, y, width, height, helperColor, arrowColor)
  2402.     local object = GUI.object(x, y, width, height)
  2403.    
  2404.     object.colors = {
  2405.         helper = helperColor,
  2406.         arrow = arrowColor
  2407.     }
  2408.  
  2409.     object.draw = resizerDraw
  2410.     object.eventHandler = resizerEventHandler
  2411.  
  2412.     return object
  2413. end
  2414.  
  2415. ----------------------------------------- Scrollbar object -----------------------------------------
  2416.  
  2417. local function scrollBarDraw(scrollBar)
  2418.     local isVertical = scrollBar.height > scrollBar.width
  2419.     local valuesDelta = scrollBar.maximumValue - scrollBar.minimumValue + 1
  2420.     local part = scrollBar.value / valuesDelta
  2421.  
  2422.     if isVertical then
  2423.         local barSize = math.ceil(scrollBar.shownValueCount / valuesDelta * scrollBar.height)
  2424.         local halfBarSize = math.floor(barSize / 2)
  2425.        
  2426.         scrollBar.ghostPosition.y = scrollBar.y + halfBarSize
  2427.         scrollBar.ghostPosition.height = scrollBar.height - barSize
  2428.  
  2429.         if scrollBar.thin then
  2430.             local y1 = math.floor(scrollBar.ghostPosition.y + part * scrollBar.ghostPosition.height - halfBarSize)
  2431.             local y2 = y1 + barSize - 1
  2432.             local background
  2433.  
  2434.             for y = scrollBar.y, scrollBar.y + scrollBar.height - 1 do
  2435.                 background = buffer.get(scrollBar.x, y)
  2436.                 buffer.set(scrollBar.x, y, background, y >= y1 and y <= y2 and scrollBar.colors.foreground or scrollBar.colors.background, "┃")
  2437.             end
  2438.         else
  2439.             buffer.square(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ")
  2440.             buffer.square(
  2441.                 scrollBar.x,
  2442.                 math.floor(scrollBar.ghostPosition.y + part * scrollBar.ghostPosition.height - halfBarSize),
  2443.                 scrollBar.width,
  2444.                 barSize,
  2445.                 scrollBar.colors.foreground, 0x0, " "
  2446.             )
  2447.         end
  2448.     else
  2449.         local barSize = math.ceil(scrollBar.shownValueCount / valuesDelta * scrollBar.width)
  2450.         local halfBarSize = math.floor(barSize / 2)
  2451.        
  2452.         scrollBar.ghostPosition.x = scrollBar.x + halfBarSize
  2453.         scrollBar.ghostPosition.width = scrollBar.width - barSize
  2454.  
  2455.         if scrollBar.thin then
  2456.             local x1 = math.floor(scrollBar.ghostPosition.x + part * scrollBar.ghostPosition.width - halfBarSize)
  2457.             local x2 = x1 + barSize - 1
  2458.             local background
  2459.  
  2460.             for x = scrollBar.x, scrollBar.x + scrollBar.width - 1 do
  2461.                 background = buffer.get(x, scrollBar.y)
  2462.                 buffer.set(x, scrollBar.y, background, x >= x1 and x <= x2 and scrollBar.colors.foreground or scrollBar.colors.background, "⠤")
  2463.             end
  2464.         else
  2465.             buffer.square(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ")
  2466.             buffer.square(
  2467.                 math.floor(scrollBar.ghostPosition.x + part * scrollBar.ghostPosition.width - halfBarSize),
  2468.                 scrollBar.y,
  2469.                 barSize,
  2470.                 scrollBar.height,
  2471.                 scrollBar.colors.foreground, 0x0, " "
  2472.             )
  2473.         end
  2474.     end
  2475.  
  2476.     return scrollBar
  2477. end
  2478.  
  2479. local function scrollBarEventHandler(mainContainer, object, eventData)
  2480.     local newValue = object.value
  2481.  
  2482.     if eventData[1] == "touch" or eventData[1] == "drag" then
  2483.         local delta = object.maximumValue - object.minimumValue + 1
  2484.         if object.height > object.width then
  2485.             newValue = math.floor((eventData[4] - object.y + 1) / object.height * delta)
  2486.         else
  2487.             newValue = math.floor((eventData[3] - object.x + 1) / object.width * delta)
  2488.         end
  2489.     elseif eventData[1] == "scroll" then
  2490.         if eventData[5] == 1 then
  2491.             if object.value >= object.minimumValue + object.onScrollValueIncrement then
  2492.                 newValue = object.value - object.onScrollValueIncrement
  2493.             else
  2494.                 newValue = object.minimumValue
  2495.             end
  2496.         else
  2497.             if object.value <= object.maximumValue - object.onScrollValueIncrement then
  2498.                 newValue = object.value + object.onScrollValueIncrement
  2499.             else
  2500.                 newValue = object.maximumValue
  2501.             end
  2502.         end
  2503.     end
  2504.  
  2505.     if eventData[1] == "touch" or eventData[1] == "drag" or eventData[1] == "scroll" then
  2506.         object.value = newValue
  2507.         callMethod(object.onTouch, eventData)
  2508.         mainContainer:draw()
  2509.         buffer.draw()
  2510.     end
  2511. end
  2512.  
  2513. function GUI.scrollBar(x, y, width, height, backgroundColor, foregroundColor, minimumValue, maximumValue, value, shownValueCount, onScrollValueIncrement, thin)
  2514.     local scrollBar = GUI.object(x, y, width, height)
  2515.  
  2516.     scrollBar.eventHandler = scrollBarEventHandler
  2517.     scrollBar.maximumValue = maximumValue
  2518.     scrollBar.minimumValue = minimumValue
  2519.     scrollBar.value = value
  2520.     scrollBar.onScrollValueIncrement = onScrollValueIncrement
  2521.     scrollBar.shownValueCount = shownValueCount
  2522.     scrollBar.thin = thin
  2523.     scrollBar.colors = {
  2524.         background = backgroundColor,
  2525.         foreground = foregroundColor,
  2526.     }
  2527.     scrollBar.ghostPosition = {}
  2528.     scrollBar.draw = scrollBarDraw
  2529.  
  2530.     return scrollBar
  2531. end
  2532.  
  2533. ----------------------------------------- Tree object -----------------------------------------
  2534.  
  2535. local function treeDraw(tree)  
  2536.     local y, yEnd, showScrollBar = tree.y, tree.y + tree.height - 1, #tree.items > tree.height
  2537.     local textLimit = tree.width - (showScrollBar and 1 or 0)
  2538.  
  2539.     if tree.colors.default.background then
  2540.         buffer.square(tree.x, tree.y, tree.width, tree.height, tree.colors.default.background, tree.colors.default.expandable, " ")
  2541.     end
  2542.  
  2543.     for i = tree.fromItem, #tree.items do
  2544.         local textColor, arrowColor, text = tree.colors.default.notExpandable, tree.colors.default.arrow, tree.items[i].expandable and "■ " or "□ "
  2545.  
  2546.         if tree.selectedItem == tree.items[i].definition then
  2547.             textColor, arrowColor = tree.colors.selected.any, tree.colors.selected.arrow
  2548.             buffer.square(tree.x, y, tree.width, 1, tree.colors.selected.background, textColor, " ")
  2549.         else
  2550.             if tree.items[i].expandable then
  2551.                 textColor = tree.colors.default.expandable
  2552.             elseif tree.items[i].disabled then
  2553.                 textColor = tree.colors.disabled
  2554.             end
  2555.         end
  2556.  
  2557.         if tree.items[i].expandable then
  2558.             buffer.text(tree.x + tree.items[i].offset, y, arrowColor, tree.expandedItems[tree.items[i].definition] and "▽" or "▷")
  2559.         end
  2560.  
  2561.         buffer.text(tree.x + tree.items[i].offset + 2, y, textColor, unicode.sub(text .. tree.items[i].name, 1, textLimit - tree.items[i].offset - 2))
  2562.  
  2563.         y = y + 1
  2564.         if y > yEnd then break end
  2565.     end
  2566.  
  2567.     if showScrollBar then
  2568.         local scrollBar = tree.scrollBar
  2569.         scrollBar.x = tree.x + tree.width - 1
  2570.         scrollBar.y = tree.y
  2571.         scrollBar.width = 1
  2572.         scrollBar.height = tree.height
  2573.         scrollBar.colors.background = tree.colors.scrollBar.background
  2574.         scrollBar.colors.foreground = tree.colors.scrollBar.foreground
  2575.         scrollBar.minimumValue = 1
  2576.         scrollBar.maximumValue = #tree.items
  2577.         scrollBar.value = tree.fromItem
  2578.         scrollBar.shownValueCount = tree.height
  2579.         scrollBar.onScrollValueIncrement = 1
  2580.         scrollBar.thin = true
  2581.  
  2582.         scrollBar:draw()
  2583.     end
  2584.  
  2585.     return tree
  2586. end
  2587.  
  2588. local function treeEventHandler(mainContainer, tree, eventData)
  2589.     if eventData[1] == "touch" then
  2590.         local i = eventData[4] - tree.y + tree.fromItem
  2591.         if tree.items[i] then
  2592.             if
  2593.                 tree.items[i].expandable and
  2594.                 (
  2595.                     tree.selectionMode == GUI.filesystemModes.file or
  2596.                     eventData[3] >= tree.x + tree.items[i].offset - 1 and eventData[3] <= tree.x + tree.items[i].offset + 1
  2597.                 )
  2598.             then
  2599.                 if tree.expandedItems[tree.items[i].definition] then
  2600.                     tree.expandedItems[tree.items[i].definition] = nil
  2601.                 else
  2602.                     tree.expandedItems[tree.items[i].definition] = true
  2603.                 end
  2604.  
  2605.                 callMethod(tree.onItemExpanded, tree.selectedItem, eventData)
  2606.             else
  2607.                 if
  2608.                     (
  2609.                         (
  2610.                             tree.selectionMode == GUI.filesystemModes.both or
  2611.                             tree.selectionMode == GUI.filesystemModes.directory and tree.items[i].expandable or
  2612.                             tree.selectionMode == GUI.filesystemModes.file
  2613.                         ) and not tree.items[i].disabled
  2614.                     )
  2615.                 then
  2616.                     tree.selectedItem = tree.items[i].definition
  2617.                     callMethod(tree.onItemSelected, tree.selectedItem, eventData)
  2618.                 end
  2619.             end
  2620.  
  2621.             mainContainer:draw()
  2622.             buffer.draw()
  2623.         end
  2624.     elseif eventData[1] == "scroll" then
  2625.         if eventData[5] == 1 then
  2626.             if tree.fromItem > 1 then
  2627.                 tree.fromItem = tree.fromItem - 1
  2628.                 mainContainer:draw()
  2629.                 buffer.draw()
  2630.             end
  2631.         else
  2632.             if tree.fromItem < #tree.items then
  2633.                 tree.fromItem = tree.fromItem + 1
  2634.                 mainContainer:draw()
  2635.                 buffer.draw()
  2636.             end
  2637.         end
  2638.     end
  2639. end
  2640.  
  2641. local function treeAddItem(tree, name, definition, offset, expandable, disabled)
  2642.     table.insert(tree.items, {name = name, expandable = expandable, offset = offset or 0, definition = definition, disabled = disabled})
  2643.     return tree
  2644. end
  2645.  
  2646. function GUI.tree(x, y, width, height, backgroundColor, expandableColor, notExpandableColor, arrowColor, backgroundSelectedColor, anySelectionColor, arrowSelectionColor, disabledColor, scrollBarBackground, scrollBarForeground, showMode, selectionMode)
  2647.     local tree = GUI.container(x, y, width, height)
  2648.    
  2649.     tree.eventHandler = treeEventHandler
  2650.     tree.colors = {
  2651.         default = {
  2652.             background = backgroundColor,
  2653.             expandable = expandableColor,
  2654.             notExpandable = notExpandableColor,
  2655.             arrow = arrowColor,
  2656.         },
  2657.         selected = {
  2658.             background = backgroundSelectedColor,
  2659.             any = anySelectionColor,
  2660.             arrow = arrowSelectionColor,
  2661.         },
  2662.         scrollBar = {
  2663.             background = scrollBarBackground,
  2664.             foreground = scrollBarForeground
  2665.         },
  2666.         disabled = disabledColor
  2667.     }
  2668.     tree.items = {}
  2669.     tree.fromItem = 1
  2670.     tree.selectedItem = nil
  2671.     tree.expandedItems = {}
  2672.  
  2673.     tree.scrollBar = GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1)
  2674.  
  2675.     tree.showMode = showMode
  2676.     tree.selectionMode = selectionMode
  2677.     tree.eventHandler = treeEventHandler
  2678.     tree.addItem = treeAddItem
  2679.     tree.draw = treeDraw
  2680.  
  2681.     return tree
  2682. end
  2683.  
  2684. ----------------------------------------- FilesystemTree object -----------------------------------------
  2685.  
  2686. local function filesystemTreeUpdateFileListRecursively(tree, path, offset)
  2687.     local list = {}
  2688.     for file in fs.list(path) do
  2689.         table.insert(list, file)
  2690.     end
  2691.  
  2692.     local i, expandables = 1, {}
  2693.     while i <= #list do
  2694.         if fs.isDirectory(path .. list[i]) then
  2695.             table.insert(expandables, list[i])
  2696.             table.remove(list, i)
  2697.         else
  2698.             i = i + 1
  2699.         end
  2700.     end
  2701.  
  2702.     table.sort(expandables, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
  2703.     table.sort(list, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
  2704.  
  2705.     if tree.showMode == GUI.filesystemModes.both or tree.showMode == GUI.filesystemModes.directory then
  2706.         for i = 1, #expandables do
  2707.             tree:addItem(fs.name(expandables[i]), path .. expandables[i], offset, true)
  2708.  
  2709.             if tree.expandedItems[path .. expandables[i]] then
  2710.                 filesystemTreeUpdateFileListRecursively(tree, path .. expandables[i], offset + 2)
  2711.             end
  2712.         end
  2713.     end
  2714.  
  2715.     if tree.showMode == GUI.filesystemModes.both or tree.showMode == GUI.filesystemModes.file then
  2716.         for i = 1, #list do
  2717.             tree:addItem(list[i], path .. list[i], offset, false,tree.extensionFilters and not tree.extensionFilters[fs.extension(path .. list[i], true)] or false)
  2718.         end
  2719.     end
  2720. end
  2721.  
  2722. local function filesystemTreeUpdateFileList(tree)
  2723.     tree.items = {}
  2724.     filesystemTreeUpdateFileListRecursively(tree, tree.workPath, 1)
  2725. end
  2726.  
  2727. local function filesystemTreeAddExtensionFilter(tree, extensionFilter)
  2728.     tree.extensionFilters = tree.extensionFilters or {}
  2729.     tree.extensionFilters[unicode.lower(extensionFilter)] = true
  2730. end
  2731.  
  2732. function GUI.filesystemTree(...)
  2733.     local tree = GUI.tree(...)
  2734.  
  2735.     tree.workPath = "/"
  2736.     tree.updateFileList = filesystemTreeUpdateFileList
  2737.     tree.addExtensionFilter = filesystemTreeAddExtensionFilter
  2738.     tree.onItemExpanded = function()
  2739.         tree:updateFileList()
  2740.     end
  2741.  
  2742.     return tree
  2743. end
  2744.  
  2745. ----------------------------------------- Text Box object -----------------------------------------
  2746.  
  2747. local function textBoxCalculate(object)
  2748.     local doubleVerticalOffset = object.offset.vertical * 2
  2749.     object.textWidth = object.width - object.offset.horizontal * 2 - (object.scrollBarEnabled and 1 or 0)
  2750.  
  2751.     object.linesCopy = {}
  2752.  
  2753.     if object.autoWrap then
  2754.         for i = 1, #object.lines do
  2755.             local isTable = type(object.lines[i]) == "table"
  2756.             for subLine in (isTable and object.lines[i].text or object.lines[i]):gmatch("[^\n]+") do
  2757.                 local wrappedLine = string.wrap(subLine, object.textWidth)
  2758.                 for j = 1, #wrappedLine do
  2759.                     table.insert(object.linesCopy, isTable and {text = wrappedLine[j], color = object.lines[i].color} or wrappedLine[j])
  2760.                 end
  2761.             end
  2762.         end
  2763.     else
  2764.         for i = 1, #object.lines do
  2765.             table.insert(object.linesCopy, object.lines[i])
  2766.         end
  2767.     end
  2768.  
  2769.     if object.autoHeight then
  2770.         object.height = #object.linesCopy + doubleVerticalOffset
  2771.     end
  2772.  
  2773.     object.textHeight = object.height - doubleVerticalOffset
  2774. end
  2775.  
  2776. local function textBoxDraw(object)
  2777.     textBoxCalculate(object)
  2778.  
  2779.     if object.colors.background then
  2780.         buffer.square(object.x, object.y, object.width, object.height, object.colors.background, object.colors.text, " ", object.colors.transparency)
  2781.     end
  2782.  
  2783.     local x, y = nil, object.y + object.offset.vertical
  2784.     local lineType, text, textColor
  2785.     for i = object.currentLine, object.currentLine + object.textHeight - 1 do
  2786.         if object.linesCopy[i] then
  2787.             lineType = type(object.linesCopy[i])
  2788.             if lineType == "string" then
  2789.                 text, textColor = string.limit(object.linesCopy[i], object.textWidth), object.colors.text
  2790.             elseif lineType == "table" then
  2791.                 text, textColor = string.limit(object.linesCopy[i].text, object.textWidth), object.linesCopy[i].color
  2792.             else
  2793.                 error("Unknown TextBox line type: " .. tostring(lineType))
  2794.             end
  2795.  
  2796.             x = GUI.getAlignmentCoordinates(
  2797.                 {
  2798.                     x = object.x + object.offset.horizontal,
  2799.                     y = 1,
  2800.                     width = object.textWidth,
  2801.                     height = 1,
  2802.                     alignment = object.alignment
  2803.                 },
  2804.                 {
  2805.                     width = unicode.len(text),
  2806.                     height = 1
  2807.                 }
  2808.             )
  2809.             buffer.text(x, y, textColor, text)
  2810.             y = y + 1
  2811.         else
  2812.             break
  2813.         end
  2814.     end
  2815.  
  2816.     if object.scrollBarEnabled and object.textHeight < #object.lines then
  2817.         object.scrollBar.x = object.x + object.width - 1
  2818.         object.scrollBar.y = object.y
  2819.         object.scrollBar.height = object.height
  2820.         object.scrollBar.maximumValue = #object.lines - object.textHeight + 1
  2821.         object.scrollBar.value = object.currentLine
  2822.         object.scrollBar.shownValueCount = object.textHeight
  2823.  
  2824.         object.scrollBar:draw()
  2825.     end
  2826.  
  2827.     return object
  2828. end
  2829.  
  2830. local function scrollDownTextBox(object, count)
  2831.     count = math.min(count or 1, #object.lines - object.height - object.currentLine + object.offset.vertical * 2 + 1)
  2832.     if #object.lines >= object.height and object.currentLine < #object.lines - count then
  2833.         object.currentLine = object.currentLine + count
  2834.     end
  2835.  
  2836.     return object
  2837. end
  2838.  
  2839. local function scrollUpTextBox(object, count)
  2840.     count = count or 1
  2841.     if object.currentLine > count and object.currentLine >= 1 then object.currentLine = object.currentLine - count end
  2842.     return object
  2843. end
  2844.  
  2845. local function scrollToStartTextBox(object)
  2846.     object.currentLine = 1
  2847.     return object
  2848. end
  2849.  
  2850. local function scrollToEndTextBox(object)
  2851.     if #object.lines > object.textHeight then
  2852.         object.currentLine = #object.lines - object.textHeight + 1
  2853.     end
  2854.  
  2855.     return object
  2856. end
  2857.  
  2858. local function textBoxScrollEventHandler(mainContainer, object, eventData)
  2859.     if eventData[1] == "scroll" then
  2860.         if eventData[5] == 1 then
  2861.             object:scrollUp()
  2862.             mainContainer:draw()
  2863.             buffer.draw()
  2864.         else
  2865.             object:scrollDown()
  2866.             mainContainer:draw()
  2867.             buffer.draw()
  2868.         end
  2869.     end
  2870. end
  2871.  
  2872. function GUI.textBox(x, y, width, height, backgroundColor, textColor, lines, currentLine, horizontalOffset, verticalOffset, autoWrap, autoHeight)
  2873.     local object = GUI.object(x, y, width, height)
  2874.    
  2875.     object.eventHandler = textBoxScrollEventHandler
  2876.     object.colors = {
  2877.         text = textColor,
  2878.         background = backgroundColor
  2879.     }
  2880.     object.setAlignment = GUI.setAlignment
  2881.     object:setAlignment(GUI.alignment.horizontal.left, GUI.alignment.vertical.top)
  2882.     object.lines = lines
  2883.     object.currentLine = currentLine or 1
  2884.     object.draw = textBoxDraw
  2885.     object.scrollUp = scrollUpTextBox
  2886.     object.scrollDown = scrollDownTextBox
  2887.     object.scrollToStart = scrollToStartTextBox
  2888.     object.scrollToEnd = scrollToEndTextBox
  2889.     object.offset = {horizontal = horizontalOffset or 0, vertical = verticalOffset or 0}
  2890.     object.autoWrap = autoWrap
  2891.     object.autoHeight = autoHeight
  2892.     object.scrollBar = GUI.scrollBar(1, 1, 1, 1, 0xC3C3C3, 0x444444, 1, 1, 1, 1, 1, true)
  2893.     object.scrollBarEnabled = false
  2894.  
  2895.     textBoxCalculate(object)
  2896.  
  2897.     return object
  2898. end
  2899.  
  2900. ----------------------------------------- Input object -----------------------------------------
  2901.  
  2902. local function inputSetCursorPosition(input, newPosition)
  2903.     if newPosition < 1 then
  2904.         newPosition = 1
  2905.     elseif newPosition > unicode.len(input.text) + 1 then
  2906.         newPosition = unicode.len(input.text) + 1
  2907.     end
  2908.  
  2909.     if newPosition > input.textCutFrom + input.width - 1 - input.textOffset * 2 then
  2910.         input.textCutFrom = input.textCutFrom + newPosition - (input.textCutFrom + input.width - 1 - input.textOffset * 2)
  2911.     elseif newPosition < input.textCutFrom then
  2912.         input.textCutFrom = newPosition
  2913.     end
  2914.  
  2915.     input.cursorPosition = newPosition
  2916.  
  2917.     return input
  2918. end
  2919.  
  2920. local function inputTextDrawMethod(x, y, color, text)
  2921.     buffer.text(x, y, color, text)
  2922. end
  2923.  
  2924. local function inputDraw(input)
  2925.     local background, foreground, transparency, text
  2926.     if input.focused then
  2927.         background, transparency = input.colors.focused.background, input.colors.focused.transparency
  2928.         if input.text == "" then
  2929.             input.textCutFrom = 1
  2930.             foreground, text = input.colors.placeholderText, input.text
  2931.         else
  2932.             foreground = input.colors.focused.text
  2933.             if input.textMask then
  2934.                 text = string.rep(input.textMask, unicode.len(input.text))
  2935.             else
  2936.                 text = input.text
  2937.             end
  2938.         end
  2939.     else
  2940.         background, transparency = input.colors.default.background, input.colors.default.transparency
  2941.         if input.text == "" then
  2942.             input.textCutFrom = 1
  2943.             foreground, text = input.colors.placeholderText, input.placeholderText
  2944.         else
  2945.             foreground = input.colors.default.text
  2946.             if input.textMask then
  2947.                 text = string.rep(input.textMask, unicode.len(input.text))
  2948.             else
  2949.                 text = input.text
  2950.             end
  2951.         end
  2952.     end
  2953.  
  2954.     if background then
  2955.         buffer.square(input.x, input.y, input.width, input.height, background, foreground, " ", transparency)
  2956.     end
  2957.  
  2958.     local y = input.y + math.floor(input.height / 2)
  2959.  
  2960.     input.textDrawMethod(
  2961.         input.x + input.textOffset,
  2962.         y,
  2963.         foreground,
  2964.         unicode.sub(
  2965.             text or "",
  2966.             input.textCutFrom,
  2967.             input.textCutFrom + input.width - 1 - input.textOffset * 2
  2968.         )
  2969.     )
  2970.  
  2971.     if input.cursorBlinkState then
  2972.         local index = buffer.getIndex(input.x + input.cursorPosition - input.textCutFrom + input.textOffset, y)
  2973.         local background = buffer.rawGet(index)
  2974.         buffer.rawSet(index, background, input.colors.cursor, input.cursorSymbol)
  2975.     end
  2976.  
  2977.     if input.autoCompleteEnabled then
  2978.         input.autoComplete.x = input.x
  2979.         if input.autoCompleteVerticalAlignment == GUI.alignment.vertical.top then
  2980.             input.autoComplete.y = input.y - input.autoComplete.height
  2981.         else
  2982.             input.autoComplete.y = input.y + input.height
  2983.         end
  2984.         input.autoComplete.width = input.width
  2985.         input.autoComplete:draw()
  2986.     end
  2987. end
  2988.  
  2989. local function inputStartInput(input)
  2990.     local mainContainer = input:getFirstParent()
  2991.  
  2992.     local textOnStart = input.text
  2993.     input.focused = true
  2994.    
  2995.     if input.historyEnabled then
  2996.         input.historyIndex = input.historyIndex + 1
  2997.     end
  2998.  
  2999.     if input.eraseTextOnFocus then
  3000.         input.text = ""
  3001.     end
  3002.  
  3003.     input.cursorBlinkState = true
  3004.     input:setCursorPosition(unicode.len(input.text) + 1)
  3005.  
  3006.     if input.autoCompleteEnabled then
  3007.         input.autoCompleteMatchMethod()
  3008.     end
  3009.  
  3010.     mainContainer:draw()
  3011.     buffer.draw()
  3012.  
  3013.     while true do
  3014.         local eventData = { event.pull(input.cursorBlinkDelay) }
  3015.        
  3016.         if eventData[1] == "touch" or eventData[1] == "drag" then
  3017.             if input:isClicked(eventData[3], eventData[4]) then
  3018.                 input:setCursorPosition(input.textCutFrom + eventData[3] - input.x - input.textOffset)
  3019.                
  3020.                 input.cursorBlinkState = true
  3021.                 mainContainer:draw()
  3022.                 buffer.draw()
  3023.             elseif input.autoComplete:isClicked(eventData[3], eventData[4]) then
  3024.                 input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
  3025.             else
  3026.                 input.cursorBlinkState = false
  3027.                 break
  3028.             end
  3029.         elseif eventData[1] == "scroll" then
  3030.             input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
  3031.         elseif eventData[1] == "key_down" then
  3032.             -- Return
  3033.             if eventData[4] == 28 then
  3034.                 if input.autoCompleteEnabled and input.autoComplete.itemCount > 0 then
  3035.                     input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
  3036.                 else
  3037.                     if input.historyEnabled then
  3038.                         -- Очистка истории
  3039.                         for i = 1, (#input.history - input.historyLimit) do
  3040.                             table.remove(input.history, 1)
  3041.                         end
  3042.  
  3043.                         -- Добавление введенных данных в историю
  3044.                         if input.history[#input.history] ~= input.text and unicode.len(input.text) > 0 then
  3045.                             table.insert(input.history, input.text)
  3046.                         end
  3047.                         input.historyIndex = #input.history
  3048.                     end
  3049.  
  3050.                     input.cursorBlinkState = false
  3051.                     break
  3052.                 end
  3053.             -- Arrows up/down/left/right
  3054.             elseif eventData[4] == 200 then
  3055.                 if input.autoCompleteEnabled and input.autoComplete.selectedItem > 1 then
  3056.                     input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
  3057.                 else
  3058.                     if input.historyEnabled and #input.history > 0 then
  3059.                         -- Добавление уже введенного текста в историю при стрелке вверх
  3060.                         if input.historyIndex == #input.history + 1 and unicode.len(input.text) > 0 then
  3061.                             input.history[input.historyIndex] = input.text
  3062.                         end
  3063.  
  3064.                         input.historyIndex = input.historyIndex - 1
  3065.                         if input.historyIndex > #input.history then
  3066.                             input.historyIndex = #input.history
  3067.                         elseif input.historyIndex < 1 then
  3068.                             input.historyIndex = 1
  3069.                         end
  3070.  
  3071.                         input.text = input.history[input.historyIndex]
  3072.                         input:setCursorPosition(unicode.len(input.text) + 1)
  3073.  
  3074.                         if input.autoCompleteEnabled then
  3075.                             input.autoCompleteMatchMethod()
  3076.                         end
  3077.                     end
  3078.                 end
  3079.             elseif eventData[4] == 208 then
  3080.                 if input.autoCompleteEnabled and input.historyIndex == #input.history + 1 then
  3081.                     input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
  3082.                 else
  3083.                     if input.historyEnabled and #input.history > 0 then
  3084.                         input.historyIndex = input.historyIndex + 1
  3085.                         if input.historyIndex > #input.history then
  3086.                             input.historyIndex = #input.history
  3087.                         elseif input.historyIndex < 1 then
  3088.                             input.historyIndex = 1
  3089.                         end
  3090.                        
  3091.                         input.text = input.history[input.historyIndex]
  3092.                         input:setCursorPosition(unicode.len(input.text) + 1)
  3093.  
  3094.                         if input.autoCompleteEnabled then
  3095.                             input.autoCompleteMatchMethod()
  3096.                         end
  3097.                     end
  3098.                 end
  3099.             elseif eventData[4] == 203 then
  3100.                 input:setCursorPosition(input.cursorPosition - 1)
  3101.             elseif eventData[4] == 205 then
  3102.                 input:setCursorPosition(input.cursorPosition + 1)
  3103.             -- Backspace
  3104.             elseif eventData[4] == 14 then
  3105.                 input.text = unicode.sub(unicode.sub(input.text, 1, input.cursorPosition - 1), 1, -2) .. unicode.sub(input.text, input.cursorPosition, -1)
  3106.                 input:setCursorPosition(input.cursorPosition - 1)
  3107.                
  3108.                 if input.autoCompleteEnabled then
  3109.                     input.autoCompleteMatchMethod()
  3110.                 end
  3111.             -- Delete
  3112.             elseif eventData[4] == 211 then
  3113.                 input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. unicode.sub(input.text, input.cursorPosition + 1, -1)
  3114.                
  3115.                 if input.autoCompleteEnabled then
  3116.                     input.autoCompleteMatchMethod()
  3117.                 end
  3118.             else
  3119.                 local char = unicode.char(eventData[3])
  3120.                 if not keyboard.isControl(eventData[3]) then
  3121.                     input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. char .. unicode.sub(input.text, input.cursorPosition, -1)
  3122.                     input:setCursorPosition(input.cursorPosition + 1)
  3123.  
  3124.                     if input.autoCompleteEnabled then
  3125.                         input.autoCompleteMatchMethod()
  3126.                     end
  3127.                 end
  3128.             end
  3129.  
  3130.             input.cursorBlinkState = true
  3131.             mainContainer:draw()
  3132.             buffer.draw()
  3133.         elseif eventData[1] == "clipboard" then
  3134.             input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. eventData[3] .. unicode.sub(input.text, input.cursorPosition, -1)
  3135.             input:setCursorPosition(input.cursorPosition + unicode.len(eventData[3]))
  3136.            
  3137.             input.cursorBlinkState = true
  3138.             mainContainer:draw()
  3139.             buffer.draw()
  3140.         elseif not eventData[1] then
  3141.             input.cursorBlinkState = not input.cursorBlinkState
  3142.             mainContainer:draw()
  3143.             buffer.draw()
  3144.         end
  3145.     end
  3146.  
  3147.     input.focused = false
  3148.     if input.autoCompleteEnabled then
  3149.         input.autoComplete:clear()
  3150.     end
  3151.  
  3152.     if input.validator then
  3153.         if not input.validator(input.text) then
  3154.             input.text = textOnStart
  3155.             input:setCursorPosition(unicode.len(input.text) + 1)
  3156.         end
  3157.     end
  3158.    
  3159.     callMethod(input.onInputFinished, mainContainer, input, mainEventData, input.text)
  3160.    
  3161.     mainContainer:draw()
  3162.     buffer.draw()
  3163. end
  3164.  
  3165. local function inputEventHandler(mainContainer, input, mainEventData)
  3166.     if mainEventData[1] == "touch" then
  3167.         input:startInput()
  3168.     end
  3169. end
  3170.  
  3171. function GUI.input(x, y, width, height, backgroundColor, textColor, placeholderTextColor, backgroundFocusedColor, textFocusedColor, text, placeholderText, eraseTextOnFocus, textMask)
  3172.     local input = GUI.object(x, y, width, height)
  3173.    
  3174.     input.colors = {
  3175.         default = {
  3176.             background = backgroundColor,
  3177.             text = textColor
  3178.         },
  3179.         focused = {
  3180.             background = backgroundFocusedColor,
  3181.             text = textFocusedColor
  3182.         },
  3183.         placeholderText = placeholderTextColor,
  3184.         cursor = 0x00A8FF
  3185.     }
  3186.  
  3187.     input.text = text or ""
  3188.     input.placeholderText = placeholderText
  3189.     input.eraseTextOnFocus = eraseTextOnFocus
  3190.     input.textMask = textMask
  3191.  
  3192.     input.textOffset = 1
  3193.     input.textCutFrom = 1
  3194.     input.cursorPosition = 1
  3195.     input.cursorSymbol = "┃"
  3196.     input.cursorBlinkDelay = 0.4
  3197.     input.cursorBlinkState = false
  3198.     input.textMask = textMask
  3199.     input.setCursorPosition = inputSetCursorPosition
  3200.  
  3201.     input.history = {}
  3202.     input.historyLimit = 20
  3203.     input.historyIndex = 0
  3204.     input.historyEnabled = false
  3205.  
  3206.     input.textDrawMethod = inputTextDrawMethod
  3207.     input.draw = inputDraw
  3208.     input.eventHandler = inputEventHandler
  3209.     input.startInput = inputStartInput
  3210.  
  3211.     input.autoComplete = GUI.autoComplete(1, 1, 30, 7, 0xE1E1E1, 0x999999, 0x3C3C3C, 0x3C3C3C, 0x999999, 0xE1E1E1, 0xC3C3C3, 0x444444)
  3212.     input.autoCompleteEnabled = false
  3213.     input.autoCompleteVerticalAlignment = GUI.alignment.vertical.bottom
  3214.  
  3215.     return input
  3216. end
  3217.  
  3218. -----------------------------------------------------------------------------------------------------
  3219.  
  3220. local function autoCompleteDraw(object)
  3221.     local y, yEnd = object.y, object.y + object.height - 1
  3222.  
  3223.     buffer.square(object.x, object.y, object.width, object.height, object.colors.default.background, object.colors.default.text, " ")
  3224.  
  3225.     for i = object.fromItem, object.itemCount do
  3226.         local textColor, textMatchColor = object.colors.default.text, object.colors.default.textMatch
  3227.         if i == object.selectedItem then
  3228.             buffer.square(object.x, y, object.width, 1, object.colors.selected.background, object.colors.selected.text, " ")
  3229.             textColor, textMatchColor = object.colors.selected.text, object.colors.selected.textMatch
  3230.         end
  3231.  
  3232.         buffer.text(object.x + 1, y, textMatchColor, object.matchText)
  3233.         buffer.text(object.x + 1 + object.matchTextLength, y, textColor, unicode.sub(object.items[i], object.matchTextLength + 1, object.width - 2 - object.matchTextLength))
  3234.  
  3235.         y = y + 1
  3236.         if y > yEnd then
  3237.             break
  3238.         end
  3239.     end
  3240.  
  3241.     if object.itemCount > object.height then
  3242.         object.scrollBar.x = object.x + object.width - 1
  3243.         object.scrollBar.y = object.y
  3244.         object.scrollBar.height = object.height
  3245.         object.scrollBar.maximumValue = object.itemCount - object.height + 1
  3246.         object.scrollBar.value = object.fromItem
  3247.         object.scrollBar.shownValueCount = object.height
  3248.  
  3249.         object.scrollBar:draw()
  3250.     end
  3251. end
  3252.  
  3253. local function autoCompleteScroll(mainContainer, object, direction)
  3254.     if object.itemCount >= object.height then
  3255.         object.fromItem = object.fromItem + direction
  3256.         if object.fromItem < 1 then
  3257.             object.fromItem = 1
  3258.         elseif object.fromItem > object.itemCount - object.height + 1 then
  3259.             object.fromItem = object.itemCount - object.height + 1
  3260.         end
  3261.     end
  3262. end
  3263.  
  3264. local function autoCompleteEventHandler(mainContainer, object, eventData)
  3265.     if eventData[1] == "touch" then
  3266.         object.selectedItem = eventData[4] - object.y + object.fromItem
  3267.         mainContainer:draw()
  3268.         buffer.draw()
  3269.  
  3270.         callMethod(object.onItemSelected, mainContainer, object, eventData, object.selectedItem)
  3271.     elseif eventData[1] == "scroll" then
  3272.         autoCompleteScroll(mainContainer, object, -eventData[5])
  3273.         mainContainer:draw()
  3274.         buffer.draw()
  3275.     elseif eventData[1] == "key_down" then
  3276.         if eventData[4] == 28 then
  3277.             callMethod(object.onItemSelected, mainContainer, object, eventData, object.selectedItem)
  3278.         elseif eventData[4] == 200 then
  3279.             object.selectedItem = object.selectedItem - 1
  3280.             if object.selectedItem < 1 then
  3281.                 object.selectedItem = 1
  3282.             end
  3283.  
  3284.             if object.selectedItem == object.fromItem - 1 then
  3285.                 autoCompleteScroll(mainContainer, object, -1)
  3286.             end
  3287.  
  3288.             mainContainer:draw()
  3289.             buffer.draw()
  3290.         elseif eventData[4] == 208 then
  3291.             object.selectedItem = object.selectedItem + 1
  3292.             if object.selectedItem > object.itemCount then
  3293.                 object.selectedItem = object.itemCount
  3294.             end
  3295.  
  3296.             if object.selectedItem == object.fromItem + object.height then
  3297.                 autoCompleteScroll(mainContainer, object, 1)
  3298.             end
  3299.            
  3300.             mainContainer:draw()
  3301.             buffer.draw()
  3302.         end
  3303.     end
  3304. end
  3305.  
  3306. local function autoCompleteClear(object)
  3307.     object.items = {}
  3308.     object.itemCount = 0
  3309.     object.fromItem = 1
  3310.     object.selectedItem = 1
  3311.     object.height = 0
  3312. end
  3313.  
  3314. local function autoCompleteMatch(object, variants, text)
  3315.     object:clear()
  3316.    
  3317.     if text then
  3318.         for i = 1, #variants do
  3319.             if variants[i] ~= text and variants[i]:match("^" .. text) then
  3320.                 table.insert(object.items, variants[i])
  3321.             end
  3322.         end
  3323.     else
  3324.         for i = 1, #variants do
  3325.             table.insert(object.items, variants[i])
  3326.         end
  3327.     end
  3328.  
  3329.     object.matchText = text or ""
  3330.     object.matchTextLength = unicode.len(object.matchText)
  3331.  
  3332.     table.sort(object.items, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
  3333.  
  3334.     object.itemCount = #object.items
  3335.     object.height = math.min(object.itemCount, object.maximumHeight)
  3336.  
  3337.     return object
  3338. end
  3339.  
  3340. function GUI.autoComplete(x, y, width, maximumHeight, backgroundColor, textColor, textMatchColor, backgroundSelectedColor, textSelectedColor, textMatchSelectedColor, scrollBarBackground, scrollBarForeground)
  3341.     local object = GUI.object(x, y, width, maximumHeight)
  3342.  
  3343.     object.colors = {
  3344.         default = {
  3345.             background = backgroundColor,
  3346.             text = textColor,
  3347.             textMatch = textMatchColor 
  3348.         },
  3349.         selected = {
  3350.             background = backgroundSelectedColor,
  3351.             text = textSelectedColor,
  3352.             textMatch = textMatchSelectedColor
  3353.         }
  3354.     }
  3355.  
  3356.     object.maximumHeight = maximumHeight
  3357.     object.fromItem = 1
  3358.     object.selectedItem = 1
  3359.     object.items = {}
  3360.     object.matchText = " "
  3361.     object.matchTextLength = 1
  3362.     object.itemCount = 0
  3363.  
  3364.     object.scrollBar = GUI.scrollBar(1, 1, 1, 1, scrollBarBackground, scrollBarForeground, 1, 1, 1, 1, 1, true)
  3365.  
  3366.     object.match = autoCompleteMatch
  3367.     object.draw = autoCompleteDraw
  3368.     object.eventHandler = autoCompleteEventHandler
  3369.     object.clear = autoCompleteClear
  3370.  
  3371.     object:clear()
  3372.  
  3373.     return object
  3374. end
  3375.  
  3376. -----------------------------------------------------------------------------------------------------
  3377.  
  3378. local function brailleCanvasDraw(brailleCanvas)
  3379.     local index, background, foreground, symbol
  3380.     for y = 1, brailleCanvas.height do
  3381.         for x = 1, brailleCanvas.width do
  3382.             index = buffer.getIndex(brailleCanvas.x + x - 1, brailleCanvas.y + y - 1)
  3383.             background, foreground, symbol = buffer.rawGet(index)
  3384.             buffer.rawSet(index, background, brailleCanvas.pixels[y][x][9], brailleCanvas.pixels[y][x][10])
  3385.         end
  3386.     end
  3387.  
  3388.     return brailleCanvas
  3389. end
  3390.  
  3391. local function brailleCanvasSet(brailleCanvas, x, y, state, color)
  3392.     local xReal, yReal = math.ceil(x / 2), math.ceil(y / 4)
  3393.    
  3394.     brailleCanvas.pixels[yReal][xReal][(y - (yReal - 1) * 4 - 1) * 2 + x - (xReal - 1) * 2] = state and 1 or 0
  3395.     brailleCanvas.pixels[yReal][xReal][9] = color or brailleCanvas.pixels[yReal][xReal][9]
  3396.     brailleCanvas.pixels[yReal][xReal][10] = unicode.char(
  3397.         10240 +
  3398.         128 * brailleCanvas.pixels[yReal][xReal][8] +
  3399.         64 * brailleCanvas.pixels[yReal][xReal][7] +
  3400.         32 * brailleCanvas.pixels[yReal][xReal][6] +
  3401.         16 * brailleCanvas.pixels[yReal][xReal][4] +
  3402.         8 * brailleCanvas.pixels[yReal][xReal][2] +
  3403.         4 * brailleCanvas.pixels[yReal][xReal][5] +
  3404.         2 * brailleCanvas.pixels[yReal][xReal][3] +
  3405.         brailleCanvas.pixels[yReal][xReal][1]
  3406.     )
  3407.  
  3408.     return brailleCanvas
  3409. end
  3410.  
  3411. local function brailleCanvasGet(brailleCanvas, x, y)
  3412.     local xReal, yReal = math.ceil(x / 2), math.ceil(y / 4)
  3413.     return brailleCanvas.pixels[yReal][xReal][(y - (yReal - 1) * 4 - 1) * 2 + x - (xReal - 1) * 2], brailleCanvas.pixels[yReal][xReal][9], brailleCanvas.pixels[yReal][xReal][10]
  3414. end
  3415.  
  3416. local function brailleCanvasFill(brailleCanvas, x, y, width, height, state, color)
  3417.     for j = y, y + height - 1 do
  3418.         for i = x, x + width - 1 do
  3419.             brailleCanvas:set(i, j, state, color)
  3420.         end
  3421.     end
  3422. end
  3423.  
  3424. local function brailleCanvasClear(brailleCanvas)
  3425.     for j = 1, brailleCanvas.height * 4 do
  3426.         brailleCanvas.pixels[j] = {}
  3427.         for i = 1, brailleCanvas.width * 2 do
  3428.             brailleCanvas.pixels[j][i] = { 0, 0, 0, 0, 0, 0, 0, 0, 0x0, " " }
  3429.         end
  3430.     end
  3431. end
  3432.  
  3433. function GUI.brailleCanvas(x, y, width, height)
  3434.     local brailleCanvas = GUI.object(x, y, width, height)
  3435.    
  3436.     brailleCanvas.pixels = {}
  3437.  
  3438.     brailleCanvas.get = brailleCanvasGet
  3439.     brailleCanvas.set = brailleCanvasSet
  3440.     brailleCanvas.fill = brailleCanvasFill
  3441.     brailleCanvas.clear = brailleCanvasClear
  3442.  
  3443.     brailleCanvas.draw = brailleCanvasDraw
  3444.  
  3445.     brailleCanvas:clear()
  3446.  
  3447.     return brailleCanvas
  3448. end
  3449.  
  3450. ----------------------------------------- TabBar -----------------------------------------
  3451.  
  3452. local function tabBarDraw(tabBar)
  3453.     local totalWidth = 0
  3454.     for i = 2, #tabBar.children do
  3455.         totalWidth = totalWidth + tabBar.children[i].width + tabBar.spaceBetweenTabs
  3456.     end
  3457.  
  3458.     local x = math.floor(tabBar.width / 2 - (totalWidth - tabBar.spaceBetweenTabs) / 2)
  3459.     for i = 2, #tabBar.children do
  3460.         tabBar.children[i].colors.default.background = tabBar.colors.default.background
  3461.         tabBar.children[i].colors.default.text = tabBar.colors.default.text
  3462.         tabBar.children[i].colors.pressed.background = tabBar.colors.selected.background
  3463.         tabBar.children[i].colors.pressed.text = tabBar.colors.selected.text
  3464.         tabBar.children[i].localX = x
  3465.  
  3466.         x = x + tabBar.children[i].width + tabBar.spaceBetweenTabs
  3467.     end
  3468.  
  3469.     tabBar.backgroundPanel.colors.background = tabBar.colors.default.background
  3470.     tabBar.backgroundPanel.width, tabBar.backgroundPanel.height = tabBar.width, tabBar.height
  3471.  
  3472.     GUI.drawContainerContent(tabBar)
  3473.  
  3474.     return tabBar
  3475. end
  3476.  
  3477. local function tabBarTabEventHandler(mainContainer, tabBarTab, eventData)
  3478.     if eventData[1] == "touch" then
  3479.         tabBarTab.parent.selectedItem = tabBarTab:indexOf() - 1
  3480.         for i = 2, #tabBarTab.parent.children do
  3481.             tabBarTab.parent.children[i].pressed = tabBarTab.parent.selectedItem == i - 1
  3482.         end
  3483.  
  3484.         callMethod(tabBarTab.onTouch, mainContainer, tabBarTab, eventData)
  3485.        
  3486.         mainContainer:draw()
  3487.         buffer.draw()
  3488.     end
  3489. end
  3490.  
  3491. local function tabBarAddItem(tabBar, text)
  3492.     local item = tabBar:addChild(GUI.button(1, 1, unicode.len(text) + tabBar.horizontalTabOffset * 2, tabBar.height, tabBar.colors.default.background, tabBar.colors.default.text, tabBar.colors.selected.background, tabBar.colors.selected.text, text))
  3493.     item.animated = false
  3494.     item.switchMode = true
  3495.     item.eventHandler = tabBarTabEventHandler
  3496.  
  3497.     if #tabBar.children - 1 == tabBar.selectedItem then
  3498.         item.pressed = true
  3499.     end
  3500.  
  3501.     return item
  3502. end
  3503.  
  3504. local function tabBarGetItem(tabBar, index)
  3505.     return tabBar.children[index + 1]
  3506. end
  3507.  
  3508. function GUI.tabBar(x, y, width, height, horizontalTabOffset, spaceBetweenTabs, backgroundColor, textColor, backgroundSelectedColor, textSelectedColor, ...)
  3509.     local tabBar = GUI.container(x, y, width, height)
  3510.  
  3511.     tabBar.colors = {
  3512.         default = {
  3513.             background = backgroundColor,
  3514.             text = textColor
  3515.         },
  3516.         selected = {
  3517.             background = backgroundSelectedColor,
  3518.             text = textSelectedColor
  3519.         }
  3520.     }
  3521.  
  3522.     tabBar.horizontalTabOffset = horizontalTabOffset
  3523.     tabBar.spaceBetweenTabs = spaceBetweenTabs
  3524.     tabBar.selectedItem = 1
  3525.     tabBar.backgroundPanel = tabBar:addChild(GUI.panel(1, 1, 1, 1, backgroundColor))
  3526.  
  3527.     tabBar.addItem = tabBarAddItem
  3528.     tabBar.getItem = tabBarGetItem
  3529.     tabBar.draw = tabBarDraw
  3530.  
  3531.     return tabBar
  3532. end
  3533.  
  3534. --------------------------------------------------------------------------------------------------------------
  3535.  
  3536. local function paletteShow(palette)
  3537.     local mainContainer = GUI.fullScreenContainer()
  3538.     mainContainer:addChild(palette)
  3539.  
  3540.     palette.OKButton.onTouch = function(mainContainer, object, eventData)
  3541.         mainContainer:stopEventHandling()
  3542.     end
  3543.    
  3544.     palette.cancelButton.onTouch = function(mainContainer, object, eventData)
  3545.         mainContainer:stopEventHandling()
  3546.     end
  3547.  
  3548.     mainContainer:draw()
  3549.     buffer.draw()
  3550.     mainContainer:startEventHandling() 
  3551.  
  3552.     return palette.color.hex
  3553. end
  3554.  
  3555. function GUI.palette(x, y, startColor)
  3556.     local palette = GUI.container(x, y, 71, 25)
  3557.    
  3558.     palette.color = {hsb = {}, rgb = {}}
  3559.     palette:addChild(GUI.panel(1, 1, palette.width, palette.height, 0xEEEEEE))
  3560.    
  3561.     local bigImage = palette:addChild(GUI.image(1, 1, image.create(50, 25)))
  3562.     local bigCrest = palette:addChild(GUI.object(1, 1, 5, 3))
  3563.  
  3564.     local function paletteDrawBigCrestPixel(x, y, symbol)
  3565.         local background, foreground = buffer.get(x, y)
  3566.         local r, g, b = color.IntegerToRGB(background)
  3567.         buffer.set(x, y, background, (r + g + b) / 3 >= 127 and 0x0 or 0xFFFFFF, symbol)
  3568.     end
  3569.  
  3570.     bigCrest.draw = function(object)
  3571.         paletteDrawBigCrestPixel(object.x, object.y + 1, "─")
  3572.         paletteDrawBigCrestPixel(object.x + 1, object.y + 1, "─")
  3573.         paletteDrawBigCrestPixel(object.x + 3, object.y + 1, "─")
  3574.         paletteDrawBigCrestPixel(object.x + 4, object.y + 1, "─")
  3575.         paletteDrawBigCrestPixel(object.x + 2, object.y, "│")
  3576.         paletteDrawBigCrestPixel(object.x + 2, object.y + 2, "│")
  3577.     end
  3578.    
  3579.     local miniImage = palette:addChild(GUI.image(53, 1, image.create(3, 25)))
  3580.    
  3581.     local miniCrest = palette:addChild(GUI.object(52, 1, 5, 1))
  3582.     miniCrest.draw = function(object)
  3583.         buffer.text(object.x, object.y, 0x0, ">")
  3584.         buffer.text(object.x + 4, object.y, 0x0, "<")
  3585.     end
  3586.  
  3587.     local colorPanel = palette:addChild(GUI.panel(58, 2, 12, 3, 0x0))
  3588.     palette.OKButton = palette:addChild(GUI.roundedButton(58, 6, 12, 1, 0x444444, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "OK"))
  3589.     palette.cancelButton = palette:addChild(GUI.roundedButton(58, 8, 12, 1, 0xFFFFFF, 0x666666, 0x2D2D2D, 0xFFFFFF, "Cancel"))
  3590.  
  3591.     local function paletteRefreshBigImage()
  3592.         local saturationStep, brightnessStep, saturation, brightness = 1 / bigImage.width, 1 / bigImage.height, 0, 1
  3593.         for j = 1, bigImage.height do
  3594.             for i = 1, bigImage.width do
  3595.                 image.set(bigImage.image, i, j, color.optimize(color.HSBToInteger(palette.color.hsb.hue, saturation, brightness)), 0x0, 0x0, " ")
  3596.                 saturation = saturation + saturationStep
  3597.             end
  3598.             saturation, brightness = 0, brightness - brightnessStep
  3599.         end
  3600.     end
  3601.  
  3602.     local function paletteRefreshMiniImage()
  3603.         local hueStep, hue = 360 / miniImage.height, 0
  3604.         for j = 1, miniImage.height do
  3605.             for i = 1, miniImage.width do
  3606.                 image.set(miniImage.image, i, j, color.optimize(color.HSBToInteger(hue, 1, 1)), 0x0, 0, " ")
  3607.             end
  3608.             hue = hue + hueStep
  3609.         end
  3610.     end
  3611.  
  3612.     local function paletteUpdateCrestsCoordinates()
  3613.         bigCrest.localX = math.floor((bigImage.width - 1) * palette.color.hsb.saturation) - 1
  3614.         bigCrest.localY = math.floor((bigImage.height - 1) - (bigImage.height - 1) * palette.color.hsb.brightness)
  3615.         miniCrest.localY = math.floor(palette.color.hsb.hue / 360 * miniImage.height)
  3616.     end
  3617.  
  3618.     local inputs
  3619.  
  3620.     local function paletteUpdateInputs()
  3621.         inputs[1].text = tostring(palette.color.rgb.red)
  3622.         inputs[2].text = tostring(palette.color.rgb.green)
  3623.         inputs[3].text = tostring(palette.color.rgb.blue)
  3624.         inputs[4].text = tostring(math.floor(palette.color.hsb.hue))
  3625.         inputs[5].text = tostring(math.floor(palette.color.hsb.saturation * 100))
  3626.         inputs[6].text = tostring(math.floor(palette.color.hsb.brightness * 100))
  3627.         inputs[7].text = string.format("%06X", palette.color.hex)
  3628.         colorPanel.colors.background = palette.color.hex
  3629.     end
  3630.  
  3631.     local function paletteSwitchColorFromHex(hex)
  3632.         palette.color.hex = hex
  3633.         palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = color.IntegerToRGB(hex)
  3634.         palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = color.RGBToHSB(palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue)
  3635.         paletteUpdateInputs()
  3636.     end
  3637.  
  3638.     local function paletteSwitchColorFromHsb(hue, saturation, brightness)
  3639.         palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = hue, saturation, brightness
  3640.         palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = color.HSBToRGB(hue, saturation, brightness)
  3641.         palette.color.hex = color.RGBToInteger(palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue)
  3642.         paletteUpdateInputs()
  3643.     end
  3644.  
  3645.     local function paletteSwitchColorFromRgb(red, green, blue)
  3646.         palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = red, green, blue
  3647.         palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = color.RGBToHSB(red, green, blue)
  3648.         palette.color.hex = color.RGBToInteger(red, green, blue)
  3649.         paletteUpdateInputs()
  3650.     end
  3651.  
  3652.     local function onAnyInputFinished()
  3653.         paletteRefreshBigImage()
  3654.         paletteUpdateCrestsCoordinates()
  3655.         palette:getFirstParent():draw()
  3656.         buffer.draw()
  3657.     end
  3658.  
  3659.     local function onHexInputFinished()
  3660.         paletteSwitchColorFromHex(tonumber("0x" .. inputs[7].text))
  3661.         onAnyInputFinished()
  3662.     end
  3663.  
  3664.     local function onRgbInputFinished()
  3665.         paletteSwitchColorFromRgb(tonumber(inputs[1].text), tonumber(inputs[2].text), tonumber(inputs[3].text))
  3666.         onAnyInputFinished()
  3667.     end
  3668.  
  3669.     local function onHsbInputFinished()
  3670.         paletteSwitchColorFromHsb(tonumber(inputs[4].text), tonumber(inputs[5].text) / 100, tonumber(inputs[6].text) / 100)
  3671.         onAnyInputFinished()
  3672.     end
  3673.  
  3674.     local function rgbValidaror(text)
  3675.         local number = tonumber(text) if number and number >= 0 and number <= 255 then return true end
  3676.     end
  3677.  
  3678.     local function hValidator(text)
  3679.         local number = tonumber(text) if number and number >= 0 and number <= 359 then return true end
  3680.     end
  3681.  
  3682.     local function sbValidator(text)
  3683.         local number = tonumber(text) if number and number >= 0 and number <= 100 then return true end
  3684.     end
  3685.  
  3686.     local function hexValidator(text)
  3687.         if string.match(text, "^[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]$") then
  3688.             return true
  3689.         end
  3690.     end
  3691.  
  3692.     inputs = {
  3693.         { shortcut = "R:", validator = rgbValidaror, onInputFinished = onRgbInputFinished },
  3694.         { shortcut = "G:", validator = rgbValidaror, onInputFinished = onRgbInputFinished },
  3695.         { shortcut = "B:", validator = rgbValidaror, onInputFinished = onRgbInputFinished },
  3696.         { shortcut = "H:", validator = hValidator,   onInputFinished = onHsbInputFinished },
  3697.         { shortcut = "S:", validator = sbValidator,  onInputFinished = onHsbInputFinished },
  3698.         { shortcut = "L:", validator = sbValidator,  onInputFinished = onHsbInputFinished },
  3699.         { shortcut = "0x", validator = hexValidator, onInputFinished = onHexInputFinished }
  3700.     }
  3701.  
  3702.     local y = 10
  3703.     for i = 1, #inputs do
  3704.         palette:addChild(GUI.label(58, y, 2, 1, 0x000000, inputs[i].shortcut))
  3705.        
  3706.         local validator, onInputFinished = inputs[i].validator, inputs[i].onInputFinished
  3707.         inputs[i] = palette:addChild(GUI.input(61, y, 9, 1, 0xFFFFFF, 0x666666, 0x666666, 0xFFFFFF, 0x000000, "", "", true))
  3708.         inputs[i].validator = validator
  3709.         inputs[i].onInputFinished = onInputFinished
  3710.        
  3711.         y = y + 2
  3712.     end
  3713.    
  3714.     local favourites
  3715.     if fs.exists(GUI.paletteConfigPath) then
  3716.         favourites = table.fromFile(GUI.paletteConfigPath)
  3717.     else
  3718.         favourites = {}
  3719.         for i = 1, 6 do favourites[i] = color.HSBToInteger(math.random(0, 360), 1, 1) end
  3720.         table.toFile(GUI.paletteConfigPath, favourites)
  3721.     end
  3722.  
  3723.     local favouritesContainer = palette:addChild(GUI.container(58, 24, 12, 1))
  3724.     for i = 1, #favourites do
  3725.         favouritesContainer:addChild(GUI.button(i * 2 - 1, 1, 2, 1, favourites[i], 0x0, 0x0, 0x0, " ")).onTouch = function(mainContainer, object, eventData)
  3726.             paletteSwitchColorFromHex(button.colors.default.background)
  3727.             paletteRefreshBigImage()
  3728.             paletteUpdateCrestsCoordinates()
  3729.             mainContainer:draw()
  3730.             buffer.draw()
  3731.         end
  3732.     end
  3733.    
  3734.     palette:addChild(GUI.button(58, 25, 12, 1, 0xFFFFFF, 0x444444, 0x2D2D2D, 0xFFFFFF, "+")).onTouch = function(mainContainer, object, eventData)
  3735.         local favouriteExists = false
  3736.         for i = 1, #favourites do
  3737.             if favourites[i] == palette.color.hex then
  3738.                 favouriteExists = true
  3739.                 break
  3740.             end
  3741.         end
  3742.        
  3743.         if not favouriteExists then
  3744.             table.insert(favourites, 1, palette.color.hex)
  3745.             table.remove(favourites, #favourites)
  3746.             for i = 1, #favourites do
  3747.                 favouritesContainer.children[i].colors.default.background = favourites[i]
  3748.                 favouritesContainer.children[i].colors.pressed.background = 0x0
  3749.             end
  3750.            
  3751.             table.toFile(GUI.paletteConfigPath, favourites)
  3752.  
  3753.             mainContainer:draw()
  3754.             buffer.draw()
  3755.         end
  3756.     end
  3757.  
  3758.     bigImage.eventHandler = function(mainContainer, object, eventData)
  3759.         if eventData[1] == "touch" or eventData[1] == "drag" and bigImage:isClicked(eventData[3], eventData[4]) then
  3760.             bigCrest.localX, bigCrest.localY = eventData[3] - palette.x - 1, eventData[4] - palette.y
  3761.             paletteSwitchColorFromHex(select(3, component.gpu.get(eventData[3], eventData[4])))
  3762.             mainContainer:draw()
  3763.             buffer.draw()
  3764.         end
  3765.     end
  3766.     bigCrest.eventHandler = bigImage.eventHandler
  3767.    
  3768.     miniImage.eventHandler = function(mainContainer, object, eventData)
  3769.         if eventData[1] == "touch" or eventData[1] == "drag" then
  3770.             miniCrest.localY = eventData[4] - palette.y + 1
  3771.             paletteSwitchColorFromHsb((eventData[4] - miniImage.y) * 360 / miniImage.height, palette.color.hsb.saturation, palette.color.hsb.brightness)
  3772.             paletteRefreshBigImage()
  3773.             mainContainer:draw()
  3774.             buffer.draw()
  3775.         end
  3776.     end
  3777.  
  3778.     palette.show = paletteShow
  3779.  
  3780.     paletteSwitchColorFromHex(startColor)
  3781.     paletteUpdateCrestsCoordinates()
  3782.     paletteRefreshBigImage()
  3783.     paletteRefreshMiniImage()
  3784.  
  3785.     return palette
  3786. end
  3787.  
  3788. -----------------------------------------------------------------------------------------------------
  3789.  
  3790. -- buffer.clear()
  3791. -- buffer.draw(true)
  3792.  
  3793. -- local mainContainer = GUI.fullScreenContainer()
  3794. -- mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x262626))
  3795.  
  3796. -- local tabBar = mainContainer:addChild(GUI.tabBar(3, 2, 80, 3, 3, 0, 0xE1E1E1, 0x2D2D2D, 0xC3C3C3, 0x2D2D2D))
  3797. -- tabBar:addItem("Вкладка 1")
  3798. -- tabBar:addItem("Вкладка 2")
  3799. -- tabBar:addItem("Вкладка 3").onTouch = function()
  3800. --  GUI.error("aefae")
  3801. -- end
  3802.  
  3803. -- mainContainer:draw()
  3804. -- buffer.draw(true)
  3805. -- mainContainer:startEventHandling()
  3806.  
  3807.  
  3808. -----------------------------------------------------------------------------------------------------
  3809.  
  3810. return GUI
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement