Omsigames

grapes/GUI.lua

Dec 25th, 2024 (edited)
12
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 156.97 KB | None | 0 0
  1. local keyboard = require("grapes.Keyboard")
  2. local filesystem = require("grapes.Filesystem")
  3. local event = require("event")
  4. local color = require("grapes.Color")
  5. local image = require("grapes.Image")
  6. local screen = require("grapes.Screen")
  7. local paths = require("grapes.Paths")
  8. local text = require("grapes.Text")
  9. local number = require("grapes.Number")
  10. local unicode = require("unicode")
  11. local computer = require("computer")
  12.  
  13. -- Sleeps "time" of seconds via "busy-wait" concept
  14. function event.sleep(time)
  15.     checkArg(1, time, "number", "nil")
  16.  
  17.     local deadline = computer.uptime() + (time or 0)
  18.     repeat
  19.         event.pull(deadline - computer.uptime())
  20.     until computer.uptime() >= deadline
  21. end
  22.  
  23. -----------------------------------------------------------------------------------------
  24.  
  25. local GUI = {
  26.     ALIGNMENT_HORIZONTAL_LEFT = 1,
  27.     ALIGNMENT_HORIZONTAL_CENTER = 2,
  28.     ALIGNMENT_HORIZONTAL_RIGHT = 3,
  29.     ALIGNMENT_VERTICAL_TOP = 4,
  30.     ALIGNMENT_VERTICAL_CENTER = 5,
  31.     ALIGNMENT_VERTICAL_BOTTOM = 6,
  32.  
  33.     DIRECTION_HORIZONTAL = 7,
  34.     DIRECTION_VERTICAL = 8,
  35.  
  36.     SIZE_POLICY_ABSOLUTE = 9,
  37.     SIZE_POLICY_RELATIVE = 10,
  38.  
  39.     IO_MODE_FILE = 11,
  40.     IO_MODE_DIRECTORY = 12,
  41.     IO_MODE_BOTH = 13,
  42.     IO_MODE_OPEN = 14,
  43.     IO_MODE_SAVE = 15,
  44.  
  45.     BUTTON_PRESS_DURATION = 0.2,
  46.     BUTTON_ANIMATION_DURATION = 0.2,
  47.     SWITCH_ANIMATION_DURATION = 0.3,
  48.     FILESYSTEM_DIALOG_ANIMATION_DURATION = 0.5,
  49.    
  50.     CONTEXT_MENU_SEPARATOR_COLOR = 0xA5A5A5,
  51.     CONTEXT_MENU_DEFAULT_TEXT_COLOR = 0x2D2D2D,
  52.     CONTEXT_MENU_DEFAULT_BACKGROUND_COLOR = 0xFFFFFF,
  53.     CONTEXT_MENU_PRESSED_BACKGROUND_COLOR = 0x3366CC,
  54.     CONTEXT_MENU_PRESSED_TEXT_COLOR = 0xFFFFFF,
  55.     CONTEXT_MENU_DISABLED_COLOR = 0x878787,
  56.     CONTEXT_MENU_BACKGROUND_TRANSPARENCY = 0.18,
  57.     CONTEXT_MENU_SHADOW_TRANSPARENCY = 0.4,
  58.  
  59.     BACKGROUND_CONTAINER_PANEL_COLOR = 0x0,
  60.     BACKGROUND_CONTAINER_TITLE_COLOR = 0xE1E1E1,
  61.     BACKGROUND_CONTAINER_PANEL_TRANSPARENCY = 0.3,
  62.  
  63.     WINDOW_BACKGROUND_PANEL_COLOR = 0xF0F0F0,
  64.     WINDOW_SHADOW_TRANSPARENCY = 0.6,
  65.     WINDOW_TITLE_BACKGROUND_COLOR = 0xE1E1E1,
  66.     WINDOW_TITLE_TEXT_COLOR = 0x2D2D2D,
  67.     WINDOW_TAB_BAR_DEFAULT_BACKGROUND_COLOR = 0x2D2D2D,
  68.     WINDOW_TAB_BAR_DEFAULT_TEXT_COLOR = 0xF0F0F0,
  69.     WINDOW_TAB_BAR_SELECTED_BACKGROUND_COLOR = 0xF0F0F0,
  70.     WINDOW_TAB_BAR_SELECTED_TEXT_COLOR = 0x2D2D2D,
  71.  
  72.     LUA_SYNTAX_COLOR_SCHEME = {
  73.         background = 0x1E1E1E,
  74.         text = 0xE1E1E1,
  75.         strings = 0x99FF80,
  76.         loops = 0xFFFF98,
  77.         comments = 0x898989,
  78.         boolean = 0xFFDB40,
  79.         logic = 0xFFCC66,
  80.         numbers = 0x66DBFF,
  81.         functions = 0xFFCC66,
  82.         compares = 0xFFCC66,
  83.         lineNumbersBackground = 0x2D2D2D,
  84.         lineNumbersText = 0xC3C3C3,
  85.         scrollBarBackground = 0x2D2D2D,
  86.         scrollBarForeground = 0x5A5A5A,
  87.         selection = 0x4B4B4B,
  88.         indentation = 0x2D2D2D
  89.     },
  90.  
  91.     LUA_SYNTAX_PATTERNS = {
  92.         "[%.%,%>%<%=%~%+%-%*%/%^%#%%%&]", "compares", 0, 0,
  93.         "[^%a%d][%.%d]+[^%a%d]", "numbers", 1, 1,
  94.         "[^%a%d][%.%d]+$", "numbers", 1, 0,
  95.         "0x%w+", "numbers", 0, 0,
  96.         " not ", "logic", 0, 1,
  97.         " or ", "logic", 0, 1,
  98.         " and ", "logic", 0, 1,
  99.         "function%(", "functions", 0, 1,
  100.         "function%s[^%s%(%)%{%}%[%]]+%(", "functions", 9, 1,
  101.         "nil", "boolean", 0, 0,
  102.         "false", "boolean", 0, 0,
  103.         "true", "boolean", 0, 0,
  104.         " break$", "loops", 0, 0,
  105.         "elseif ", "loops", 0, 1,
  106.         "else[%s%;]", "loops", 0, 1,
  107.         "else$", "loops", 0, 0,
  108.         "function ", "loops", 0, 1,
  109.         "local ", "loops", 0, 1,
  110.         "return", "loops", 0, 0,
  111.         "until ", "loops", 0, 1,
  112.         "then", "loops", 0, 0,
  113.         "if ", "loops", 0, 1,
  114.         "repeat$", "loops", 0, 0,
  115.         " in ", "loops", 0, 1,
  116.         "for ", "loops", 0, 1,
  117.         "end[%s%;]", "loops", 0, 1,
  118.         "end$", "loops", 0, 0,
  119.         "do ", "loops", 0, 1,
  120.         "do$", "loops", 0, 0,
  121.         "while ", "loops", 0, 1,
  122.         "\'[^\']+\'", "strings", 0, 0,
  123.         "\"[^\"]+\"", "strings", 0, 0,
  124.         "%-%-.+", "comments", 0, 0,
  125.     },
  126. }
  127.  
  128. --------------------------------------------------------------------------------
  129.  
  130. function GUI.setAlignment(object, horizontalAlignment, verticalAlignment)
  131.     object.horizontalAlignment, object.verticalAlignment = horizontalAlignment, verticalAlignment
  132.    
  133.     return object
  134. end
  135.  
  136. function GUI.getAlignmentCoordinates(x, y, width1, height1, horizontalAlignment, verticalAlignment, width2, height2)
  137.     if horizontalAlignment == GUI.ALIGNMENT_HORIZONTAL_CENTER then
  138.         x = x + width1 / 2 - width2 / 2
  139.     elseif horizontalAlignment == GUI.ALIGNMENT_HORIZONTAL_RIGHT then
  140.         x = x + width1 - width2
  141.     elseif horizontalAlignment ~= GUI.ALIGNMENT_HORIZONTAL_LEFT then
  142.         error("Unknown horizontal alignment: " .. tostring(horizontalAlignment))
  143.     end
  144.  
  145.     if verticalAlignment == GUI.ALIGNMENT_VERTICAL_CENTER then
  146.         y = y + height1 / 2 - height2 / 2
  147.     elseif verticalAlignment == GUI.ALIGNMENT_VERTICAL_BOTTOM then
  148.         y = y + height1 - height2
  149.     elseif verticalAlignment ~= GUI.ALIGNMENT_VERTICAL_TOP then
  150.         error("Unknown vertical alignment: " .. tostring(verticalAlignment))
  151.     end
  152.  
  153.     return x, y
  154. end
  155.  
  156. function GUI.getMarginCoordinates(x, y, horizontalAlignment, verticalAlignment, horizontalMargin, verticalMargin)
  157.     if horizontalAlignment == GUI.ALIGNMENT_HORIZONTAL_RIGHT then
  158.         x = x - horizontalMargin
  159.     else
  160.         x = x + horizontalMargin
  161.     end
  162.    
  163.     if verticalAlignment == GUI.ALIGNMENT_VERTICAL_BOTTOM then
  164.         y = y - verticalMargin
  165.     else
  166.         y = y + verticalMargin
  167.     end
  168.  
  169.     return x, y
  170. end
  171.  
  172. --------------------------------------------------------------------------------
  173.  
  174. local function objectIsPointInside(object, x, y)
  175.     return
  176.         x >= object.x and
  177.         x < object.x + object.width and
  178.         y >= object.y and
  179.         y < object.y + object.height
  180. end
  181.  
  182. local function objectDraw(object)
  183.     return object
  184. end
  185.  
  186. function GUI.object(x, y, width, height)
  187.     return {
  188.         x = x,
  189.         y = y,
  190.         width = width,
  191.         height = height,
  192.         isPointInside = objectIsPointInside,
  193.         draw = objectDraw
  194.     }
  195. end
  196.  
  197. --------------------------------------------------------------------------------
  198.  
  199. local function containerObjectIndexOf(object)
  200.     if not object.parent then error("Object doesn't have a parent container") end
  201.  
  202.     for objectIndex = 1, #object.parent.children do
  203.         if object.parent.children[objectIndex] == object then
  204.             return objectIndex
  205.         end
  206.     end
  207. end
  208.  
  209. local function containerObjectMoveForward(object)
  210.     local objectIndex = containerObjectIndexOf(object)
  211.     if objectIndex < #object.parent.children then
  212.         object.parent.children[objectIndex], object.parent.children[objectIndex + 1] = object.parent.children[objectIndex + 1], object.parent.children[objectIndex]
  213.     end
  214.    
  215.     return object
  216. end
  217.  
  218. local function containerObjectMoveBackward(object)
  219.     local objectIndex = containerObjectIndexOf(object)
  220.     if objectIndex > 1 then
  221.         object.parent.children[objectIndex], object.parent.children[objectIndex - 1] = object.parent.children[objectIndex - 1], object.parent.children[objectIndex]
  222.     end
  223.    
  224.     return object
  225. end
  226.  
  227. local function containerObjectMoveToFront(object)
  228.     table.remove(object.parent.children, containerObjectIndexOf(object))
  229.     table.insert(object.parent.children, object)
  230.    
  231.     return object
  232. end
  233.  
  234. local function containerObjectMoveToBack(object)
  235.     table.remove(object.parent.children, containerObjectIndexOf(object))
  236.     table.insert(object.parent.children, 1, object)
  237.    
  238.     return object
  239. end
  240.  
  241. local function containerObjectRemove(object)
  242.     table.remove(object.parent.children, containerObjectIndexOf(object))
  243. end
  244.  
  245. local function containerObjectAnimationStart(animation, duration)
  246.     animation.position = 0
  247.     animation.duration = duration
  248.     animation.started = true
  249.     animation.startUptime = computer.uptime()
  250.  
  251.     computer.pushSignal("GUI", "animationStarted")
  252. end
  253.  
  254. local function containerObjectAnimationStop(animation)
  255.     animation.position = 0
  256.     animation.started = false
  257. end
  258.  
  259. local function containerObjectAnimationRemove(animation)
  260.     animation.removeLater = true
  261. end
  262.  
  263. local function containerObjectAddAnimation(object, frameHandler, onFinish)
  264.     local animation = {
  265.         object = object,
  266.         position = 0,
  267.         start = containerObjectAnimationStart,
  268.         stop = containerObjectAnimationStop,
  269.         remove = containerObjectAnimationRemove,
  270.         frameHandler = frameHandler,
  271.         onFinish = onFinish,
  272.     }
  273.  
  274.     object.firstParent.animations = object.firstParent.animations or {}
  275.     table.insert(object.firstParent.animations, animation)
  276.  
  277.     return animation
  278. end
  279.  
  280. local function containerAddChild(container, object, atIndex)
  281.     object.localX = object.x
  282.     object.localY = object.y
  283.     object.indexOf = containerObjectIndexOf
  284.     object.moveToFront = containerObjectMoveToFront
  285.     object.moveToBack = containerObjectMoveToBack
  286.     object.moveForward = containerObjectMoveForward
  287.     object.moveBackward = containerObjectMoveBackward
  288.     object.remove = containerObjectRemove
  289.     object.addAnimation = containerObjectAddAnimation
  290.  
  291.     local function updateFirstParent(object, firstParent)
  292.         object.firstParent = firstParent
  293.         if object.children then
  294.             for i = 1, #object.children do
  295.                 updateFirstParent(object.children[i], firstParent)
  296.             end
  297.         end
  298.     end
  299.  
  300.     object.parent = container
  301.     updateFirstParent(object, container.firstParent or container)
  302.  
  303.     if atIndex then
  304.         table.insert(container.children, atIndex, object)
  305.     else
  306.         table.insert(container.children, object)
  307.     end
  308.    
  309.     return object
  310. end
  311.  
  312. local function containerRemoveChildren(container, from, to)
  313.     from = from or 1
  314.     for objectIndex = from, to or #container.children do
  315.         table.remove(container.children, from)
  316.     end
  317. end
  318.  
  319. local function getRectangleIntersection(R1X1, R1Y1, R1X2, R1Y2, R2X1, R2Y1, R2X2, R2Y2)
  320.     if R2X1 <= R1X2 and R2Y1 <= R1Y2 and R2X2 >= R1X1 and R2Y2 >= R1Y1 then
  321.         return
  322.             math.max(R2X1, R1X1),
  323.             math.max(R2Y1, R1Y1),
  324.             math.min(R2X2, R1X2),
  325.             math.min(R2Y2, R1Y2)
  326.     else
  327.         return
  328.     end
  329. end
  330.  
  331. local function containerDraw(container)
  332.     local R1X1, R1Y1, R1X2, R1Y2, child = screen.getDrawLimit()
  333.     local intersectionX1, intersectionY1, intersectionX2, intersectionY2 = getRectangleIntersection(
  334.         R1X1,
  335.         R1Y1,
  336.         R1X2,
  337.         R1Y2,
  338.         container.x,
  339.         container.y,
  340.         container.x + container.width - 1,
  341.         container.y + container.height - 1
  342.     )
  343.  
  344.     if intersectionX1 then
  345.         screen.setDrawLimit(intersectionX1, intersectionY1, intersectionX2, intersectionY2)
  346.        
  347.         for i = 1, #container.children do
  348.             child = container.children[i]
  349.            
  350.             if not child.hidden then
  351.                 child.x, child.y = container.x + child.localX - 1, container.y + child.localY - 1
  352.                 child:draw()
  353.             end
  354.         end
  355.  
  356.         screen.setDrawLimit(R1X1, R1Y1, R1X2, R1Y2)
  357.     end
  358.  
  359.     return container
  360. end
  361.  
  362. function GUI.container(x, y, width, height)
  363.     local container = GUI.object(x, y, width, height)
  364.  
  365.     container.children = {}
  366.     container.passScreenEvents = true
  367.    
  368.     container.draw = containerDraw
  369.     container.removeChildren = containerRemoveChildren
  370.     container.addChild = containerAddChild
  371.    
  372.     return container
  373. end
  374.  
  375. --------------------------------------------------------------------------------
  376.  
  377. local function workspaceStart(workspace, eventPullTimeout)
  378.     local animation, animationIndex, animationOnFinishMethodsIndex, animationOnFinishMethods, roundedX, roundedY, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32
  379.    
  380.     local function handleContainer(isScreenEvent, currentContainer, intersectionX1, intersectionY1, intersectionX2, intersectionY2)
  381.         local currentContainerPassed, child, newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2
  382.        
  383.         if isScreenEvent then
  384.             roundedX, roundedY = math.ceil(e3), math.ceil(e4)
  385.  
  386.             if
  387.                 roundedX < intersectionX1
  388.                 or roundedX > intersectionX2
  389.                 or roundedY < intersectionY1
  390.                 or roundedY > intersectionY2
  391.             then
  392.                 return
  393.             end
  394.  
  395.             if currentContainer.eventHandler and not currentContainer.disabled then
  396.                 currentContainer.eventHandler(workspace, currentContainer, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32)
  397.             end
  398.  
  399.             currentContainerPassed = not currentContainer.passScreenEvents
  400.        
  401.         elseif currentContainer.eventHandler then
  402.             currentContainer.eventHandler(workspace, currentContainer, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32)
  403.         end
  404.  
  405.         for i = #currentContainer.children, 1, -1 do
  406.             child = currentContainer.children[i]
  407.  
  408.             if not child.hidden then
  409.                 if child.children then
  410.                     newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2 = getRectangleIntersection(
  411.                         intersectionX1,
  412.                         intersectionY1,
  413.                         intersectionX2,
  414.                         intersectionY2,
  415.                         child.x,
  416.                         child.y,
  417.                         child.x + child.width - 1,
  418.                         child.y + child.height - 1
  419.                     )
  420.  
  421.                     if
  422.                         newIntersectionX1
  423.                         and handleContainer(
  424.                             isScreenEvent,
  425.                             child,
  426.                             newIntersectionX1,
  427.                             newIntersectionY1,
  428.                             newIntersectionX2,
  429.                             newIntersectionY2,
  430.                             e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32
  431.                         )
  432.                     then
  433.                         return true
  434.                     end
  435.                 else
  436.                     if workspace.needConsume then
  437.                         workspace.needConsume = nil
  438.  
  439.                         return true
  440.                     end
  441.  
  442.                     if isScreenEvent then
  443.                         if child:isPointInside(roundedX, roundedY) then
  444.                             if child.eventHandler and not child.disabled then
  445.                                 child.eventHandler(workspace, child, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32)
  446.                             end
  447.  
  448.                             if not child.passScreenEvents then
  449.                                 return true
  450.                             end
  451.                         end
  452.  
  453.                     elseif child.eventHandler then
  454.                         child.eventHandler(workspace, child, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32)
  455.                     end
  456.                 end
  457.             end
  458.         end
  459.  
  460.         if currentContainerPassed then
  461.             return true
  462.         end
  463.     end
  464.  
  465.     workspace.eventPullTimeout = eventPullTimeout
  466.  
  467.     repeat
  468.         e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32 = event.pull(workspace.animations and 0 or workspace.eventPullTimeout)
  469.        
  470.         handleContainer(
  471.             e1 == "touch" or
  472.             e1 == "drag" or
  473.             e1 == "drop" or
  474.             e1 == "scroll" or
  475.             e1 == "double_touch",
  476.             workspace,
  477.             workspace.x,
  478.             workspace.y,
  479.             workspace.x + workspace.width - 1,
  480.             workspace.y + workspace.height - 1
  481.         )
  482.  
  483.         if workspace.animations then
  484.             animationIndex, animationOnFinishMethodsIndex, animationOnFinishMethods = 1, 1, {}
  485.             -- Продрачиваем анимации и вызываем обработчики кадров
  486.             while animationIndex <= #workspace.animations do
  487.                 animation = workspace.animations[animationIndex]
  488.  
  489.                 if animation.removeLater then
  490.                     table.remove(workspace.animations, animationIndex)
  491.  
  492.                     if #workspace.animations == 0 then
  493.                         workspace.animations = nil
  494.                        
  495.                         break
  496.                     end
  497.                 else
  498.                     if animation.started then
  499.                         animation.position = (computer.uptime() - animation.startUptime) / animation.duration
  500.                        
  501.                         if animation.position < 1 then
  502.                             animation.frameHandler(animation)
  503.                         else
  504.                             animation.position, animation.started = 1, false
  505.                             animation.frameHandler(animation)
  506.                            
  507.                             if animation.onFinish then
  508.                                 animationOnFinishMethods[animationOnFinishMethodsIndex] = animation
  509.                                 animationOnFinishMethodsIndex = animationOnFinishMethodsIndex + 1
  510.                             end
  511.                         end
  512.                     end
  513.  
  514.                     animationIndex = animationIndex + 1
  515.                 end
  516.             end
  517.  
  518.             -- По завершению продрочки отрисовываем изменения на экране
  519.             workspace:draw()
  520.  
  521.             -- Вызываем поочередно все методы .onFinish
  522.             for i = 1, #animationOnFinishMethods do
  523.                 animationOnFinishMethods[i].onFinish(animationOnFinishMethods[i])
  524.             end
  525.         end
  526.     until workspace.needClose
  527.  
  528.     workspace.needClose = nil
  529. end
  530.  
  531. local function workspaceStop(workspace)
  532.     --screen.clear(0x0)
  533.     workspace.needClose = true
  534. end
  535.  
  536. local function workspaceConsumeEvent(workspace)
  537.     workspace.needConsume = true
  538. end
  539.  
  540. local function workspaceDraw(object, ...)
  541.     containerDraw(object)
  542.     screen.update(...)
  543. end
  544.  
  545. function GUI.workspace(x, y, width, height)
  546.     local workspace = GUI.container(x or 1, y or 1, width or screen.getWidth(), height or screen.getHeight())
  547.    
  548.     workspace.draw = workspaceDraw
  549.     workspace.start = workspaceStart
  550.     workspace.stop = workspaceStop
  551.     workspace.consumeEvent = workspaceConsumeEvent
  552.  
  553.     return workspace
  554. end
  555.  
  556. --------------------------------------------------------------------------------
  557.  
  558. local function pressableDraw(pressable)
  559.     local background = pressable.pressed and pressable.colors.pressed.background or pressable.disabled and pressable.colors.disabled.background or pressable.colors.default.background
  560.     local text = pressable.pressed and pressable.colors.pressed.text or pressable.disabled and pressable.colors.disabled.text or pressable.colors.default.text
  561.  
  562.     if background then
  563.         screen.drawRectangle(pressable.x, pressable.y, pressable.width, pressable.height, background, text, " ")
  564.     end
  565.    
  566.     screen.drawText(math.floor(pressable.x + pressable.width / 2 - unicode.len(pressable.text) / 2), math.floor(pressable.y + pressable.height / 2), text, pressable.text)
  567. end
  568.  
  569. local function pressableHandlePress(workspace, pressable, ...)
  570.     pressable.pressed = not pressable.pressed
  571.     workspace:draw()
  572.  
  573.     if not pressable.switchMode then
  574.         pressable.pressed = not pressable.pressed
  575.         event.sleep(GUI.BUTTON_PRESS_DURATION)
  576.        
  577.         workspace:draw()
  578.     end
  579.  
  580.     if pressable.onTouch then
  581.         pressable.onTouch(workspace, pressable, ...)
  582.     end
  583. end
  584.  
  585. local function pressableEventHandler(workspace, pressable, e1, ...)
  586.     if e1 == "touch" then
  587.         pressableHandlePress(workspace, pressable, e1, ...)
  588.     end
  589. end
  590.  
  591. local function pressable(x, y, width, height, backgroundColor, textColor, backgroundPressedColor, textPressedColor, backgroundDisabledColor, textDisabledColor, text)
  592.     local pressable = GUI.object(x, y, width, height)
  593.  
  594.     pressable.colors = {
  595.         default = {
  596.             background = backgroundColor,
  597.             text = textColor
  598.         },
  599.         pressed = {
  600.             background = backgroundPressedColor,
  601.             text = textPressedColor
  602.         },
  603.         disabled = {
  604.             background = backgroundDisabledColor,
  605.             text = textDisabledColor
  606.         }
  607.     }
  608.  
  609.     pressable.pressed = false
  610.     pressable.text = text
  611.     pressable.draw = pressableDraw
  612.     pressable.eventHandler = pressableEventHandler
  613.  
  614.     return pressable
  615. end
  616.  
  617. --------------------------------------------------------------------------------
  618.  
  619. local function buttonPlayAnimation(button, onFinish)
  620.     button.animationStarted = true
  621.     button:addAnimation(
  622.         function(animation)
  623.             if button.pressed then
  624.                 if button.colors.default.background and button.colors.pressed.background then
  625.                     button.animationCurrentBackground = color.transition(button.colors.pressed.background, button.colors.default.background, animation.position)
  626.                 end
  627.                 button.animationCurrentText = color.transition(button.colors.pressed.text, button.colors.default.text, animation.position)
  628.             else
  629.                 if button.colors.default.background and button.colors.pressed.background then
  630.                     button.animationCurrentBackground = color.transition(button.colors.default.background, button.colors.pressed.background, animation.position)
  631.                 end
  632.                 button.animationCurrentText = color.transition(button.colors.default.text, button.colors.pressed.text, animation.position)
  633.             end
  634.         end,
  635.         function(animation)
  636.             button.animationStarted = false
  637.             button.pressed = not button.pressed
  638.             onFinish(animation)
  639.         end
  640.     ):start(button.animationDuration)
  641. end
  642.  
  643. local function buttonPress(button, workspace, object, ...)
  644.     if button.animated then
  645.         local eventData = {...}
  646.        
  647.         buttonPlayAnimation(button, function(animation)
  648.             if button.onTouch then
  649.                 button.onTouch(workspace, button, table.unpack(eventData))
  650.             end
  651.  
  652.             animation:remove()
  653.  
  654.             if not button.switchMode then
  655.                 buttonPlayAnimation(button, function(animation)
  656.                     animation:remove()
  657.                 end)
  658.             end
  659.         end)
  660.     else
  661.         pressableHandlePress(workspace, button, ...)
  662.     end
  663. end
  664.  
  665. local function buttonEventHandler(workspace, button, e1, ...)
  666.     if e1 == "touch" and (not button.animated or not button.animationStarted) then
  667.         button:press(workspace, button, e1, ...)
  668.     end
  669. end
  670.  
  671. local function buttonGetColors(button)
  672.     if button.disabled then
  673.         return button.colors.disabled.background, button.colors.disabled.text
  674.     else
  675.         if button.animated and button.animationStarted then
  676.             return button.animationCurrentBackground, button.animationCurrentText
  677.         else
  678.             if button.pressed then
  679.                 return button.colors.pressed.background, button.colors.pressed.text
  680.             else
  681.                 return button.colors.default.background, button.colors.default.text
  682.             end
  683.         end
  684.     end
  685. end
  686.  
  687. local function buttonDrawText(button, textColor)
  688.     screen.drawText(math.floor(button.x + button.width / 2 - unicode.len(button.text) / 2), math.floor(button.y + button.height / 2), textColor, button.text)
  689. end
  690.  
  691. local function buttonDraw(button)
  692.     local backgroundColor, textColor = buttonGetColors(button)
  693.    
  694.     if backgroundColor then
  695.         screen.drawRectangle(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ", button.colors.transparency)
  696.     end
  697.     buttonDrawText(button, textColor)
  698. end
  699.  
  700. local function framedButtonDraw(button)
  701.     local backgroundColor, textColor = buttonGetColors(button)
  702.    
  703.     if backgroundColor then
  704.         screen.drawFrame(button.x, button.y, button.width, button.height, backgroundColor)
  705.     end
  706.     buttonDrawText(button, textColor)
  707. end
  708.  
  709. local function roundedButtonDraw(button)
  710.     local backgroundColor, textColor = buttonGetColors(button)
  711.  
  712.     if backgroundColor then
  713.         local x2, y2 = button.x + button.width - 1, button.y + button.height - 1
  714.         if button.height > 1 then
  715.             screen.drawText(button.x + 1, button.y, backgroundColor, string.rep("▄", button.width - 2))
  716.             screen.drawText(button.x, button.y, backgroundColor, "⣠")
  717.             screen.drawText(x2, button.y, backgroundColor, "⣄")
  718.            
  719.             screen.drawRectangle(button.x, button.y + 1, button.width, button.height - 2, backgroundColor, textColor, " ")
  720.            
  721.             screen.drawText(button.x + 1, y2, backgroundColor, string.rep("▀", button.width - 2))
  722.             screen.drawText(button.x, y2, backgroundColor, "⠙")
  723.             screen.drawText(x2, y2, backgroundColor, "⠋")
  724.         else
  725.             screen.drawRectangle(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ")
  726.             GUI.roundedCorners(button.x, button.y, button.width, button.height, backgroundColor)
  727.         end
  728.     end
  729.  
  730.     buttonDrawText(button, textColor)
  731. end
  732.  
  733. local function tagButtonDraw(button)
  734.     local backgroundColor, textColor = buttonGetColors(button)
  735.    
  736.     screen.drawRectangle(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ")
  737.     screen.drawText(button.x - 1, button.y, backgroundColor, "◀")
  738.     buttonDrawText(button, textColor)
  739. end
  740.  
  741. local function buttonCreate(x, y, width, height, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text)
  742.     local button = pressable(x, y, width, height, backgroundColor, textColor, backgroundPressedColor, textPressedColor, 0x878787, 0xA5A5A5, text)
  743.  
  744.     button.animationDuration = GUI.BUTTON_ANIMATION_DURATION
  745.     button.animated = true
  746.  
  747.     button.animationCurrentBackground = backgroundColor
  748.     button.animationCurrentText = textColor
  749.    
  750.     button.press = buttonPress
  751.     button.eventHandler = buttonEventHandler
  752.  
  753.     return button
  754. end
  755.  
  756. local function adaptiveButtonCreate(x, y, xOffset, yOffset, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text)
  757.     return buttonCreate(x, y, unicode.len(text) + xOffset * 2, yOffset * 2 + 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text)
  758. end
  759.  
  760. function GUI.button(...)
  761.     local button = buttonCreate(...)
  762.     button.draw = buttonDraw
  763.  
  764.     return button
  765. end
  766.  
  767. function GUI.adaptiveButton(...)
  768.     local button = adaptiveButtonCreate(...)
  769.     button.draw = buttonDraw
  770.  
  771.     return button
  772. end
  773.  
  774. function GUI.framedButton(...)
  775.     local button = buttonCreate(...)
  776.     button.draw = framedButtonDraw
  777.  
  778.     return button
  779. end
  780.  
  781. function GUI.adaptiveFramedButton(...)
  782.     local button = adaptiveButtonCreate(...)
  783.     button.draw = framedButtonDraw
  784.  
  785.     return button
  786. end
  787.  
  788. function GUI.roundedButton(...)
  789.     local button = buttonCreate(...)
  790.     button.draw = roundedButtonDraw
  791.  
  792.     return button
  793. end
  794.  
  795. function GUI.adaptiveRoundedButton(...)
  796.     local button = adaptiveButtonCreate(...)
  797.     button.draw = roundedButtonDraw
  798.  
  799.     return button
  800. end
  801.  
  802. function GUI.tagButton(...)
  803.     local button = buttonCreate(...)
  804.     button.draw = tagButtonDraw
  805.  
  806.     return button
  807. end
  808.  
  809. function GUI.adaptiveTagButton(...)
  810.     local button = adaptiveButtonCreate(...)
  811.     button.draw = tagButtonDraw
  812.  
  813.     return button
  814. end
  815.  
  816. --------------------------------------------------------------------------------
  817.  
  818. local function drawPanel(object)
  819.     screen.drawRectangle(object.x, object.y, object.width, object.height, object.colors.background, 0x0, " ", object.colors.transparency)
  820.     return object
  821. end
  822.  
  823. function GUI.panel(x, y, width, height, color, transparency)
  824.     local object = GUI.object(x, y, width, height)
  825.    
  826.     object.colors = {
  827.         background = color,
  828.         transparency = transparency
  829.     }
  830.  
  831.     object.draw = drawPanel
  832.    
  833.     return object
  834. end
  835.  
  836. --------------------------------------------------------------------------------
  837.  
  838. local function blurredPanelDraw(object)
  839.     screen.blur(object.x, object.y, object.width, object.height, object.radius, object.color, object.transparency)
  840. end
  841.  
  842. function GUI.blurredPanel(x, y, width, height, radius, color, transparency)
  843.     local object = GUI.object(x, y, width, height)
  844.  
  845.     object.radius = radius or 3
  846.     object.color = color
  847.     object.transparency = transparency
  848.  
  849.     object.draw = blurredPanelDraw
  850.  
  851.     return object
  852. end
  853.  
  854. --------------------------------------------------------------------------------
  855.  
  856. local function drawLabel(object)
  857.     local xText, yText = GUI.getAlignmentCoordinates(
  858.         object.x,
  859.         object.y,
  860.         object.width,
  861.         object.height,
  862.         object.horizontalAlignment,
  863.         object.verticalAlignment,
  864.         unicode.len(object.text),
  865.         1
  866.     )
  867.     screen.drawText(math.floor(xText), math.floor(yText), object.colors.text, object.text)
  868.     return object
  869. end
  870.  
  871. function GUI.label(x, y, width, height, textColor, text)
  872.     local object = GUI.object(x, y, width, height)
  873.    
  874.     object.setAlignment = GUI.setAlignment
  875.     object:setAlignment(GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP)
  876.     object.colors = {text = textColor}
  877.     object.text = text
  878.     object.draw = drawLabel
  879.  
  880.     return object
  881. end
  882.  
  883. --------------------------------------------------------------------------------
  884.  
  885. local function drawImage(object)
  886.     screen.drawImage(object.x, object.y, object.image)
  887.     return object
  888. end
  889.  
  890. function GUI.image(x, y, image)
  891.     local object = GUI.object(x, y, image[1], image[2])
  892.  
  893.     object.image = image
  894.     object.draw = drawImage
  895.  
  896.     return object
  897. end
  898.  
  899. --------------------------------------------------------------------------------
  900.  
  901. function GUI.actionButtons(x, y, fatSymbol)
  902.     local symbol = "●"
  903.    
  904.     local container = GUI.container(x, y, 5, 1)
  905.     container.close = container:addChild(GUI.button(1, 1, 1, 1, nil, 0xFF4940, nil, 0x992400, symbol))
  906.     container.minimize = container:addChild(GUI.button(3, 1, 1, 1, nil, 0xFFB640, nil, 0x996D00, symbol))
  907.     container.maximize = container:addChild(GUI.button(5, 1, 1, 1, nil, 0x00B640, nil, 0x006D40, symbol))
  908.  
  909.     return container
  910. end
  911.  
  912. --------------------------------------------------------------------------------
  913.  
  914. local function drawProgressBar(object)
  915.     local activeWidth = math.floor(math.min(object.value, 100) / 100 * object.width)
  916.     if object.thin then
  917.         screen.drawText(object.x, object.y, object.colors.passive, string.rep("━", object.width))
  918.         screen.drawText(object.x, object.y, object.colors.active, string.rep("━", activeWidth))
  919.     else
  920.         screen.drawRectangle(object.x, object.y, object.width, object.height, object.colors.passive, 0x0, " ")
  921.         screen.drawRectangle(object.x, object.y, activeWidth, object.height, object.colors.active, 0x0, " ")
  922.     end
  923.  
  924.     if object.showValue then
  925.         local stringValue = (object.valuePrefix or "") .. object.value .. (object.valuePostfix or "")
  926.         screen.drawText(math.floor(object.x + object.width / 2 - unicode.len(stringValue) / 2), object.y + 1, object.colors.value, stringValue)
  927.     end
  928.  
  929.     return object
  930. end
  931.  
  932. function GUI.progressBar(x, y, width, activeColor, passiveColor, valueColor, value, thin, showValue, valuePrefix, valuePostfix)
  933.     local object = GUI.object(x, y, width, 1)
  934.    
  935.     object.value = value
  936.     object.colors = {active = activeColor, passive = passiveColor, value = valueColor}
  937.     object.thin = thin
  938.     object.draw = drawProgressBar
  939.     object.showValue = showValue
  940.     object.valuePrefix = valuePrefix
  941.     object.valuePostfix = valuePostfix
  942.    
  943.     return object
  944. end
  945.  
  946. --------------------------------------------------------------------------------
  947.  
  948. function GUI.drawShadow(x, y, width, height, transparency, thin)
  949.     if thin then
  950.         screen.drawRectangle(x + width, y + 1, 1, height - 1, 0x0, 0x0, " ", transparency)
  951.         screen.drawText(x + 1, y + height, 0x0, string.rep("▀", width), transparency)
  952.         screen.drawText(x + width, y, 0x0, "▄", transparency)
  953.     else
  954.         screen.drawRectangle(x + width, y + 1, 2, height, 0x0, 0x0, " ", transparency)
  955.         screen.drawRectangle(x + 2, y + height, width - 2, 1, 0x0, 0x0, " ", transparency)
  956.     end
  957. end
  958.  
  959. function GUI.roundedCorners(x, y, width, height, color, transparency)
  960.     screen.drawText(x - 1, y, color, "⠰", transparency)
  961.     screen.drawText(x + width, y, color, "⠆", transparency)
  962. end
  963.  
  964. --------------------------------------------------------------------------------
  965.  
  966. function GUI.alert(...)
  967.     local args = {...}
  968.    
  969.     for i = 1, #args do
  970.         if type(args[i]) == "table" then
  971.             args[i] = text.serialize(args[i], true)
  972.         else
  973.             args[i] = tostring(args[i])
  974.         end
  975.     end
  976.  
  977.     if #args == 0 then
  978.         args[1] = "nil"
  979.     end
  980.  
  981.     local sign = image.fromString([[06030000FF 0000FF 00F7FF▟00F7FF▙0000FF 0000FF 0000FF 00F7FF▟F7FF00 F7FF00 00F7FF▙0000FF 00F7FF▟F7FF00CF7FF00yF7FF00kF7FF00a00F7FF▙]])
  982.     local offset = 2
  983.     local lines = #args > 1 and "\"" .. table.concat(args, "\", \"") .. "\"" or args[1]
  984.     local bufferWidth, bufferHeight = screen.getResolution()
  985.     local width = math.floor(bufferWidth * 0.5)
  986.     local textWidth = width - image.getWidth(sign) - 2
  987.  
  988.     lines = text.wrap(lines, textWidth)
  989.     local height = image.getHeight(sign)
  990.    
  991.     if #lines + 2 > height then
  992.         height = #lines + 2
  993.     end
  994.  
  995.     local workspace = GUI.workspace(1, math.floor(bufferHeight / 2 - height / 2), bufferWidth, height + offset * 2)
  996.     local oldPixels = screen.copy(workspace.x, workspace.y, workspace.width, workspace.height)
  997.  
  998.     local x, y = math.floor(bufferWidth / 2 - width / 2), offset + 1
  999.     workspace:addChild(GUI.panel(1, 1, workspace.width, workspace.height, 0x1D1D1D))
  1000.     workspace:addChild(GUI.image(x, y, sign))
  1001.     workspace:addChild(GUI.textBox(x + image.getWidth(sign) + 2, y, textWidth, #lines, 0x1D1D1D, 0xE1E1E1, lines, 1, 0, 0)).eventHandler = nil
  1002.    
  1003.     local buttonWidth = 10
  1004.     local button = workspace:addChild(GUI.roundedButton(x + image.getWidth(sign) + textWidth - buttonWidth + 2, workspace.height - offset, buttonWidth, 1, 0x3366CC, 0xE1E1E1, 0xE1E1E1, 0x3366CC, "OK"))
  1005.    
  1006.     button.onTouch = function()
  1007.         workspace:stop()
  1008.         screen.paste(workspace.x, workspace.y, oldPixels)
  1009.         screen.update()
  1010.     end
  1011.  
  1012.     workspace.eventHandler = function(workspace, object, e1, e2, e3, e4, ...)
  1013.         if e1 == "key_down" and e4 == 28 then
  1014.             button.animated = false
  1015.             button:press(workspace, object, e1, e2, e3, e4, ...)
  1016.         end
  1017.     end
  1018.  
  1019.     workspace:draw(true)
  1020.     workspace:start()
  1021. end
  1022.  
  1023. --------------------------------------------------------------------------------
  1024.  
  1025. local function codeViewDraw(codeView)
  1026.     local y, toLine, colorScheme, patterns = codeView.y, codeView.fromLine + codeView.height - 1, codeView.syntaxColorScheme, codeView.syntaxPatterns
  1027.    
  1028.     -- Line numbers bar and code area
  1029.     codeView.lineNumbersWidth = unicode.len(tostring(toLine)) + 2
  1030.     codeView.codeAreaPosition = codeView.x + codeView.lineNumbersWidth
  1031.     codeView.codeAreaWidth = codeView.width - codeView.lineNumbersWidth
  1032.    
  1033.     -- Line numbers
  1034.     screen.drawRectangle(codeView.x, y, codeView.lineNumbersWidth, codeView.height, colorScheme.lineNumbersBackground, colorScheme.lineNumbersText, " ")   
  1035.    
  1036.     -- Background
  1037.     screen.drawRectangle(codeView.codeAreaPosition, y, codeView.codeAreaWidth, codeView.height, colorScheme.background, colorScheme.text, " ")
  1038.    
  1039.     -- Line numbers texts
  1040.     local text
  1041.     for line = codeView.fromLine, toLine do
  1042.         if codeView.lines[line] then
  1043.             text = line .. ""
  1044.             if codeView.highlights[line] then
  1045.                 screen.drawRectangle(codeView.x, y, codeView.lineNumbersWidth, 1, codeView.highlights[line], colorScheme.text, " ", 0.3)
  1046.                 screen.drawRectangle(codeView.codeAreaPosition, y, codeView.codeAreaWidth, 1, codeView.highlights[line], colorScheme.text, " ")
  1047.             end
  1048.  
  1049.             screen.drawText(codeView.codeAreaPosition - unicode.len(text) - 1, y, colorScheme.lineNumbersText, text)
  1050.            
  1051.             y = y + 1
  1052.         else
  1053.             break
  1054.         end
  1055.     end
  1056.    
  1057.     if #codeView.selections > 0 then
  1058.         local function drawUpperSelection(y, selectionIndex)
  1059.             screen.drawRectangle(
  1060.                 math.max(codeView.codeAreaPosition, codeView.codeAreaPosition + codeView.selections[selectionIndex].from.symbol - codeView.fromSymbol + 1),
  1061.                 y + codeView.selections[selectionIndex].from.line - codeView.fromLine,
  1062.                 codeView.codeAreaWidth - codeView.selections[selectionIndex].from.symbol + codeView.fromSymbol - 1,
  1063.                 1,
  1064.                 codeView.selections[selectionIndex].color or colorScheme.selection,
  1065.                 colorScheme.text,
  1066.                 " "
  1067.             )
  1068.         end
  1069.  
  1070.         local function drawLowerSelection(y, selectionIndex)
  1071.             screen.drawRectangle(
  1072.                 codeView.codeAreaPosition,
  1073.                 y + codeView.selections[selectionIndex].from.line - codeView.fromLine,
  1074.                 codeView.selections[selectionIndex].to.symbol - codeView.fromSymbol + 2,
  1075.                 1,
  1076.                 codeView.selections[selectionIndex].color or colorScheme.selection,
  1077.                 colorScheme.text,
  1078.                 " "
  1079.             )
  1080.         end
  1081.  
  1082.         for selectionIndex = 1, #codeView.selections do
  1083.             y = codeView.y
  1084.             local dy = codeView.selections[selectionIndex].to.line - codeView.selections[selectionIndex].from.line
  1085.            
  1086.             if dy == 0 then
  1087.                 screen.drawRectangle(
  1088.                     codeView.codeAreaPosition + codeView.selections[selectionIndex].from.symbol - codeView.fromSymbol + 1,
  1089.                     y + codeView.selections[selectionIndex].from.line - codeView.fromLine,
  1090.                     codeView.selections[selectionIndex].to.symbol - codeView.selections[selectionIndex].from.symbol + 1,
  1091.                     1,
  1092.                     codeView.selections[selectionIndex].color or colorScheme.selection, colorScheme.text, " "
  1093.                 )
  1094.             elseif dy == 1 then
  1095.                 drawUpperSelection(y, selectionIndex)
  1096.                 y = y + 1
  1097.  
  1098.                 drawLowerSelection(y, selectionIndex)
  1099.             else
  1100.                 drawUpperSelection(y, selectionIndex)
  1101.                 y = y + 1
  1102.                
  1103.                 for i = 1, dy - 1 do
  1104.                     screen.drawRectangle(
  1105.                         codeView.codeAreaPosition,
  1106.                         y + codeView.selections[selectionIndex].from.line - codeView.fromLine,
  1107.                         codeView.codeAreaWidth,
  1108.                         1,
  1109.                         codeView.selections[selectionIndex].color or colorScheme.selection,
  1110.                         colorScheme.text,
  1111.                         " "
  1112.                     )
  1113.  
  1114.                     y = y + 1
  1115.                 end
  1116.  
  1117.                 drawLowerSelection(y, selectionIndex)
  1118.             end
  1119.         end
  1120.     end
  1121.  
  1122.     -- Code strings
  1123.     y = codeView.y
  1124.     for i = codeView.fromLine, toLine do
  1125.         if codeView.lines[i] then
  1126.             if codeView.syntaxHighlight then
  1127.                 GUI.highlightString(
  1128.                     codeView.codeAreaPosition + 1,
  1129.                     y,
  1130.                     codeView.fromSymbol,
  1131.                     codeView.indentationWidth,
  1132.                     patterns,
  1133.                     colorScheme,
  1134.                     codeView.lines[i]
  1135.                 )
  1136.             else
  1137.                 screen.drawText(
  1138.                     codeView.codeAreaPosition + 1,
  1139.                     y,
  1140.                     colorScheme.text,
  1141.                     unicode.sub(
  1142.                         codeView.lines[i],
  1143.                         codeView.fromSymbol,
  1144.                         codeView.fromSymbol + codeView.codeAreaWidth - 3
  1145.                     )
  1146.                 )
  1147.             end
  1148.  
  1149.             y = y + 1
  1150.         else
  1151.             break
  1152.         end
  1153.     end
  1154.  
  1155.     -- Scrollbars
  1156.     if #codeView.lines > codeView.height then
  1157.         codeView.verticalScrollBar.colors.background, codeView.verticalScrollBar.colors.foreground = colorScheme.scrollBarBackground, colorScheme.scrollBarForeground
  1158.         codeView.verticalScrollBar.minimumValue, codeView.verticalScrollBar.maximumValue, codeView.verticalScrollBar.value, codeView.verticalScrollBar.shownValueCount = 1, #codeView.lines, codeView.fromLine, codeView.height
  1159.         codeView.verticalScrollBar.localX = codeView.width
  1160.         codeView.verticalScrollBar.localY = 1
  1161.         codeView.verticalScrollBar.height = codeView.height - 1
  1162.         codeView.verticalScrollBar.hidden = false
  1163.     else
  1164.         codeView.verticalScrollBar.hidden = true
  1165.     end
  1166.  
  1167.     if codeView.maximumLineLength > codeView.codeAreaWidth - 2 then
  1168.         codeView.horizontalScrollBar.colors.background, codeView.horizontalScrollBar.colors.foreground = colorScheme.scrollBarBackground, colorScheme.scrollBarForeground
  1169.         codeView.horizontalScrollBar.minimumValue, codeView.horizontalScrollBar.maximumValue, codeView.horizontalScrollBar.value, codeView.horizontalScrollBar.shownValueCount = 1, codeView.maximumLineLength, codeView.fromSymbol, codeView.codeAreaWidth - 2
  1170.         codeView.horizontalScrollBar.localX = codeView.lineNumbersWidth + 1
  1171.         codeView.horizontalScrollBar.localY = codeView.height
  1172.         codeView.horizontalScrollBar.width = codeView.codeAreaWidth - 1
  1173.         codeView.horizontalScrollBar.hidden = false
  1174.     else
  1175.         codeView.horizontalScrollBar.hidden = true
  1176.     end
  1177.  
  1178.     codeView:overrideDraw()
  1179. end
  1180.  
  1181. function GUI.codeView(x, y, width, height, fromSymbol, fromLine, maximumLineLength, selections, highlights, syntaxPatterns, syntaxColorScheme, syntaxHighlight, lines) 
  1182.     local codeView = GUI.container(x, y, width, height)
  1183.    
  1184.     codeView.passScreenEvents = false
  1185.     codeView.lines = lines
  1186.     codeView.fromSymbol = fromSymbol
  1187.     codeView.fromLine = fromLine
  1188.     codeView.maximumLineLength = maximumLineLength
  1189.     codeView.selections = selections or {}
  1190.     codeView.highlights = highlights or {}
  1191.     codeView.syntaxHighlight = syntaxHighlight
  1192.     codeView.syntaxPatterns = syntaxPatterns
  1193.     codeView.syntaxColorScheme = syntaxColorScheme
  1194.     codeView.indentationWidth = 2
  1195.  
  1196.     codeView.verticalScrollBar = codeView:addChild(GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1, true))
  1197.     codeView.horizontalScrollBar = codeView:addChild(GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1, true))
  1198.  
  1199.     codeView.overrideDraw = codeView.draw
  1200.     codeView.draw = codeViewDraw
  1201.  
  1202.     return codeView
  1203. end
  1204.  
  1205. --------------------------------------------------------------------------------
  1206.  
  1207. local function colorSelectorDraw(colorSelector)
  1208.     local overlayColor = colorSelector.color < 0x7FFFFF and 0xFFFFFF or 0x0
  1209.        
  1210.     screen.drawRectangle(
  1211.         colorSelector.x,
  1212.         colorSelector.y,
  1213.         colorSelector.width,
  1214.         colorSelector.height,
  1215.         colorSelector.pressed and color.blend(colorSelector.color, overlayColor, 0.8) or colorSelector.color,
  1216.         overlayColor,
  1217.         " "
  1218.     )
  1219.    
  1220.     if colorSelector.height > 1 and colorSelector.drawLine then
  1221.         screen.drawText(colorSelector.x, colorSelector.y + colorSelector.height - 1, overlayColor, string.rep("▄", colorSelector.width), 0.8)
  1222.     end
  1223.    
  1224.     screen.drawText(colorSelector.x + 1, colorSelector.y + math.floor(colorSelector.height / 2), overlayColor, text.limit(colorSelector.text, colorSelector.width - 2))
  1225.    
  1226.     return colorSelector
  1227. end
  1228.  
  1229. local function colorSelectorEventHandler(workspace, object, e1, ...)
  1230.     if e1 == "touch" then
  1231.         local eventData = {...}
  1232.         object.pressed = true
  1233.  
  1234.         local palette = workspace:addChild(GUI.palette(1, 1, object.color))
  1235.         palette.localX, palette.localY = math.floor(workspace.width / 2 - palette.width / 2), math.floor(workspace.height / 2 - palette.height / 2)
  1236.  
  1237.         palette.cancelButton.onTouch = function()
  1238.             object.pressed = false
  1239.             palette:remove()
  1240.             workspace:draw()
  1241.  
  1242.             if object.onColorSelected then
  1243.                 object.onColorSelected(workspace, object, e1, table.unpack(eventData))
  1244.             end
  1245.         end
  1246.  
  1247.         palette.submitButton.onTouch = function()
  1248.             object.color = palette.color.integer
  1249.             palette.cancelButton.onTouch()
  1250.         end
  1251.        
  1252.         workspace:draw()
  1253.     end
  1254. end
  1255.  
  1256. function GUI.colorSelector(x, y, width, height, color, text)
  1257.     local colorSelector = GUI.object(x, y, width, height)
  1258.    
  1259.     colorSelector.drawLine = true
  1260.     colorSelector.eventHandler = colorSelectorEventHandler
  1261.     colorSelector.color = color
  1262.     colorSelector.text = text
  1263.     colorSelector.draw = colorSelectorDraw
  1264.    
  1265.     return colorSelector
  1266. end
  1267.  
  1268. --------------------------------------------------------------------------------
  1269.  
  1270. local function getAxisValue(num, postfix, roundValues)
  1271.     if roundValues then
  1272.         return math.floor(num) .. postfix
  1273.     else
  1274.         local integer, fractional = math.modf(num)
  1275.         local firstPart, secondPart = "", ""
  1276.         if math.abs(integer) >= 1000 then
  1277.             return number.shorten(integer, 2) .. postfix
  1278.         else
  1279.             if math.abs(fractional) > 0 then
  1280.                 return string.format("%.2f", num) .. postfix
  1281.             else
  1282.                 return num .. postfix
  1283.             end
  1284.         end
  1285.     end
  1286. end
  1287.  
  1288. local function drawChart(object)
  1289.     -- Sorting by x value
  1290.     local valuesCopy = {}
  1291.     for i = 1, #object.values do valuesCopy[i] = object.values[i] end
  1292.     table.sort(valuesCopy, function(a, b) return a[1] < b[1] end)
  1293.    
  1294.     if #valuesCopy == 0 then valuesCopy = {{0, 0}} end
  1295.  
  1296.     -- Max, min, deltas
  1297.     local xMin, xMax, yMin, yMax = valuesCopy[1][1], valuesCopy[#valuesCopy][1], valuesCopy[1][2], valuesCopy[1][2]
  1298.    
  1299.     for i = 1, #valuesCopy do
  1300.         yMin, yMax = math.min(yMin, valuesCopy[i][2]), math.max(yMax, valuesCopy[i][2])
  1301.     end
  1302.    
  1303.     local dx, dy = xMax - xMin, yMax - yMin
  1304.  
  1305.     -- y axis values and helpers
  1306.     local value, chartHeight, yAxisValueMaxWidth, yAxisValues = yMin, object.height - 1 - (object.showXAxisValues and 1 or 0), 0, {}
  1307.    
  1308.     for y = object.y + object.height - 3, object.y + 1, -chartHeight * object.yAxisValueInterval do
  1309.         local stringValue = getAxisValue(value, object.yAxisPostfix, object.roundValues)
  1310.        
  1311.         yAxisValueMaxWidth = math.max(yAxisValueMaxWidth, unicode.len(stringValue))
  1312.         table.insert(yAxisValues, {y = math.ceil(y), value = stringValue})
  1313.         value = value + dy * object.yAxisValueInterval
  1314.     end
  1315.    
  1316.     local stringValue = getAxisValue(yMax, object.yAxisPostfix, object.roundValues)
  1317.     table.insert(yAxisValues, {y = object.y, value = stringValue})
  1318.     yAxisValueMaxWidth = math.max(yAxisValueMaxWidth, unicode.len(stringValue))
  1319.  
  1320.     local chartWidth = object.width - (object.showYAxisValues and yAxisValueMaxWidth + 2 or 0)
  1321.     local chartX = object.x + object.width - chartWidth
  1322.    
  1323.     for i = 1, #yAxisValues do
  1324.         if object.showYAxisValues then
  1325.             screen.drawText(chartX - unicode.len(yAxisValues[i].value) - 2, yAxisValues[i].y, object.colors.axisValue, yAxisValues[i].value)
  1326.         end
  1327.         screen.drawText(chartX, yAxisValues[i].y, object.colors.helpers, string.rep("─", chartWidth))
  1328.     end
  1329.  
  1330.     -- x axis values
  1331.     if object.showXAxisValues then
  1332.         value = xMin
  1333.         for x = chartX, chartX + chartWidth - 2, chartWidth * object.xAxisValueInterval do
  1334.             local stringValue = getAxisValue(value, object.xAxisPostfix, object.roundValues)
  1335.             screen.drawText(math.floor(x - unicode.len(stringValue) / 2), object.y + object.height - 1, object.colors.axisValue, stringValue)
  1336.             value = value + dx * object.xAxisValueInterval
  1337.         end
  1338.         local value = getAxisValue(xMax, object.xAxisPostfix, object.roundValues)
  1339.         screen.drawText(object.x + object.width - unicode.len(value), object.y + object.height - 1, object.colors.axisValue, value)
  1340.     end
  1341.  
  1342.     -- Axis lines
  1343.     for y = object.y, object.y + chartHeight - 1 do
  1344.         screen.drawText(chartX - 1, y, object.colors.axis, "┨")
  1345.     end
  1346.     screen.drawText(chartX - 1, object.y + chartHeight, object.colors.axis, "┗" .. string.rep("┯━", math.floor(chartWidth / 2)))
  1347.  
  1348.     local function fillVerticalPart(x1, y1, x2, y2)
  1349.         local dx, dy = x2 - x1, y2 - y1
  1350.         local absdx, absdy = math.abs(dx), math.abs(dy)
  1351.         if absdx >= absdy then
  1352.             local step, y = dy / absdx, y1
  1353.             for x = x1, x2, (x1 < x2 and 1 or -1) do
  1354.                 local yFloor = math.floor(y)
  1355.                 screen.drawSemiPixelRectangle(math.floor(x), yFloor, 1, math.floor(object.y + chartHeight) * 2 - yFloor - 1, object.colors.chart)
  1356.                 y = y + step
  1357.             end
  1358.         else
  1359.             local step, x = dx / absdy, x1
  1360.             for y = y1, y2, (y1 < y2 and 1 or -1) do
  1361.                 local yFloor = math.floor(y)
  1362.                 screen.drawSemiPixelRectangle(math.floor(x), yFloor, 1, math.floor(object.y + chartHeight) * 2 - yFloor - 1, object.colors.chart)
  1363.                 x = x + step
  1364.             end
  1365.         end
  1366.     end
  1367.  
  1368.     -- chart
  1369.     for i = 1, #valuesCopy - 1 do
  1370.         local x = math.floor(chartX + (valuesCopy[i][1] - xMin) / dx * (chartWidth - 1))
  1371.         local y = math.floor(object.y + chartHeight - 1 - (valuesCopy[i][2] - yMin) / dy * (chartHeight - 1)) * 2
  1372.         local xNext = math.floor(chartX + (valuesCopy[i + 1][1] - xMin) / dx * (chartWidth - 1))
  1373.         local yNext = math.floor(object.y + chartHeight - 1 - (valuesCopy[i + 1][2] - yMin) / dy * (chartHeight - 1)) * 2
  1374.         if object.fillChartArea then
  1375.             fillVerticalPart(x, y, xNext, yNext)
  1376.         else
  1377.             screen.drawSemiPixelLine(x, y, xNext, yNext, object.colors.chart)
  1378.         end
  1379.     end
  1380.  
  1381.     return object
  1382. end
  1383.  
  1384. function GUI.chart(x, y, width, height, axisColor, axisValueColor, axisHelpersColor, chartColor, xAxisValueInterval, yAxisValueInterval, xAxisPostfix, yAxisPostfix, fillChartArea, values)
  1385.     local object = GUI.object(x, y, width, height)
  1386.  
  1387.     object.colors = {axis = axisColor, chart = chartColor, axisValue = axisValueColor, helpers = axisHelpersColor}
  1388.     object.draw = drawChart
  1389.     object.values = values or {}
  1390.     object.xAxisPostfix = xAxisPostfix
  1391.     object.yAxisPostfix = yAxisPostfix
  1392.     object.xAxisValueInterval = xAxisValueInterval
  1393.     object.yAxisValueInterval = yAxisValueInterval
  1394.     object.fillChartArea = fillChartArea
  1395.     object.showYAxisValues = true
  1396.     object.showXAxisValues = true
  1397.  
  1398.     return object
  1399. end
  1400.  
  1401. --------------------------------------------------------------------------------
  1402.  
  1403. local function switchAndLabelDraw(switchAndLabel)
  1404.     switchAndLabel.label.width = switchAndLabel.width
  1405.     switchAndLabel.switch.localX = switchAndLabel.width - switchAndLabel.switch.width
  1406.  
  1407.     switchAndLabel.label.x, switchAndLabel.label.y = switchAndLabel.x + switchAndLabel.label.localX - 1, switchAndLabel.y + switchAndLabel.label.localY - 1
  1408.     switchAndLabel.switch.x, switchAndLabel.switch.y = switchAndLabel.x + switchAndLabel.switch.localX - 1, switchAndLabel.y + switchAndLabel.switch.localY - 1
  1409.    
  1410.     switchAndLabel.label:draw()
  1411.     switchAndLabel.switch:draw()
  1412.  
  1413.     return switchAndLabel
  1414. end
  1415.  
  1416. function GUI.switchAndLabel(x, y, width, switchWidth, activeColor, passiveColor, pipeColor, textColor, text, switchState)
  1417.     local switchAndLabel = GUI.container(x, y, width, 1)
  1418.  
  1419.     switchAndLabel.label = switchAndLabel:addChild(GUI.label(1, 1, width, 1, textColor, text))
  1420.     switchAndLabel.switch = switchAndLabel:addChild(GUI.switch(1, 1, switchWidth, activeColor, passiveColor, pipeColor, switchState))
  1421.     switchAndLabel.draw = switchAndLabelDraw
  1422.  
  1423.     return switchAndLabel
  1424. end
  1425.  
  1426. --------------------------------------------------------------------------------
  1427.  
  1428. local function sliderDraw(object)
  1429.     -- На всякий случай делаем значение не меньше минимального и не больше максимального
  1430.     object.value = math.min(math.max(object.value, object.minimumValue), object.maximumValue)
  1431.    
  1432.     if object.showMaximumAndMinimumValues then
  1433.         local stringMaximumValue, stringMinimumValue = tostring(object.roundValues and math.floor(object.maximumValue) or number.roundToDecimalPlaces(object.maximumValue, 2)), tostring(object.roundValues and math.floor(object.minimumValue) or number.roundToDecimalPlaces(object.minimumValue, 2))
  1434.         screen.drawText(object.x - unicode.len(stringMinimumValue) - 1, object.y, object.colors.value, stringMinimumValue)
  1435.         screen.drawText(object.x + object.width + 1, object.y, object.colors.value, stringMaximumValue)
  1436.     end
  1437.  
  1438.     if object.currentValuePrefix or object.currentValuePostfix then
  1439.         local stringCurrentValue = (object.currentValuePrefix or "") .. (object.roundValues and math.floor(object.value) or number.roundToDecimalPlaces(object.value, 2)) .. (object.currentValuePostfix or "")
  1440.         screen.drawText(math.floor(object.x + object.width / 2 - unicode.len(stringCurrentValue) / 2), object.y + 1, object.colors.value, stringCurrentValue)
  1441.     end
  1442.  
  1443.     local activeWidth = number.round((object.value - object.minimumValue) / (object.maximumValue - object.minimumValue) * object.width)
  1444.     screen.drawText(object.x, object.y, object.colors.passive, string.rep("━", object.width))
  1445.     screen.drawText(object.x, object.y, object.colors.active, string.rep("━", activeWidth))
  1446.     screen.drawText(activeWidth >= object.width and object.x + activeWidth - 1 or object.x + activeWidth, object.y, object.colors.pipe, "⬤")
  1447.  
  1448.     return object
  1449. end
  1450.  
  1451. local function sliderEventHandler(workspace, object, e1, e2, e3, e4, e5, ...)
  1452.     if e1 == "touch" or e1 == "drag" then
  1453.         local clickPosition = e3 - object.x
  1454.  
  1455.         if clickPosition <= 0 then
  1456.             object.value = object.minimumValue
  1457.         elseif clickPosition >= object.width - 1 then
  1458.             object.value = object.maximumValue
  1459.         else
  1460.             object.value = object.minimumValue + (clickPosition / object.width * (object.maximumValue - object.minimumValue))
  1461.         end
  1462.  
  1463.         workspace:draw()
  1464.  
  1465.         if object.onValueChanged then
  1466.             object.onValueChanged(workspace, object, e1, e2, e3, e4, e5, ...)
  1467.         end
  1468.     elseif e1 == "scroll" then
  1469.         object.value = object.value + (object.maximumValue - object.minimumValue) * object.scrollSensivity * e5
  1470.  
  1471.         if object.value > object.maximumValue then
  1472.             object.value = object.maximumValue
  1473.         elseif object.value < object.minimumValue then
  1474.             object.value = object.minimumValue
  1475.         end
  1476.  
  1477.         workspace:draw()
  1478.  
  1479.         if object.onValueChanged then
  1480.             object.onValueChanged(workspace, object, e1, e2, e3, e4, e5, ...)
  1481.         end
  1482.     end
  1483. end
  1484.  
  1485. function GUI.slider(x, y, width, activeColor, passiveColor, pipeColor, valueColor, minimumValue, maximumValue, value, showMaximumAndMinimumValues, currentValuePrefix, currentValuePostfix)
  1486.     local object = GUI.object(x, y, width, 1)
  1487.    
  1488.     object.eventHandler = sliderEventHandler
  1489.     object.colors = {active = activeColor, passive = passiveColor, pipe = pipeColor, value = valueColor}
  1490.     object.draw = sliderDraw
  1491.     object.minimumValue = minimumValue
  1492.     object.maximumValue = maximumValue
  1493.     object.value = value
  1494.     object.scrollSensivity = 0.05
  1495.     object.showMaximumAndMinimumValues = showMaximumAndMinimumValues
  1496.     object.currentValuePrefix = currentValuePrefix
  1497.     object.currentValuePostfix = currentValuePostfix
  1498.     object.roundValues = false
  1499.    
  1500.     return object
  1501. end
  1502.  
  1503. --------------------------------------------------------------------------------
  1504.  
  1505. local function switchDraw(switch)
  1506.     screen.drawText(switch.x - 1, switch.y, switch.colors.passive, "⠰")
  1507.     screen.drawRectangle(switch.x, switch.y, switch.width, 1, switch.colors.passive, 0x0, " ")
  1508.     screen.drawText(switch.x + switch.width, switch.y, switch.colors.passive, "⠆")
  1509.  
  1510.     screen.drawText(switch.x - 1, switch.y, switch.colors.active, "⠰")
  1511.     screen.drawRectangle(switch.x, switch.y, switch.pipePosition - 1, 1, switch.colors.active, 0x0, " ")
  1512.  
  1513.     screen.drawText(switch.x + switch.pipePosition - 2, switch.y, switch.colors.pipe, "⠰")
  1514.     screen.drawRectangle(switch.x + switch.pipePosition - 1, switch.y, 2, 1, switch.colors.pipe, 0x0, " ")
  1515.     screen.drawText(switch.x + switch.pipePosition + 1, switch.y, switch.colors.pipe, "⠆")
  1516.    
  1517.     return switch
  1518. end
  1519.  
  1520. local function switchSetState(switch, state)
  1521.     switch.state = state
  1522.     switch.pipePosition = switch.state and switch.width - 1 or 1
  1523.  
  1524.     return switch
  1525. end
  1526.  
  1527. local function switchEventHandler(workspace, switch, e1, ...)
  1528.     if e1 == "touch" then
  1529.         local eventData = {...}
  1530.  
  1531.         switch.state = not switch.state
  1532.        
  1533.         switch:addAnimation(
  1534.             function(animation)
  1535.                 if switch.state then
  1536.                     switch.pipePosition = number.round(1 + animation.position * (switch.width - 2))
  1537.                 else   
  1538.                     switch.pipePosition = number.round(1 + (1 - animation.position) * (switch.width - 2))
  1539.                 end
  1540.             end,
  1541.            
  1542.             function(animation)
  1543.                 animation:remove()
  1544.                 if switch.onStateChanged then
  1545.                     switch.onStateChanged(switch, e1, table.unpack(eventData))
  1546.                 end
  1547.             end
  1548.         ):start(switch.animationDuration)
  1549.     end
  1550. end
  1551.  
  1552. function GUI.switch(x, y, width, activeColor, passiveColor, pipeColor, state)
  1553.     local switch = GUI.object(x, y, width, 1)
  1554.  
  1555.     switch.pipePosition = 1
  1556.     switch.eventHandler = switchEventHandler
  1557.     switch.colors = {
  1558.         active = activeColor,
  1559.         passive = passiveColor,
  1560.         pipe = pipeColor,
  1561.     }
  1562.     switch.draw = switchDraw
  1563.     switch.state = state or false
  1564.     switch.animated = true
  1565.     switch.animationDuration = GUI.SWITCH_ANIMATION_DURATION
  1566.     switch.setState = switchSetState
  1567.  
  1568.     switch:setState(state)
  1569.    
  1570.     return switch
  1571. end
  1572.  
  1573. --------------------------------------------------------------------------------
  1574.  
  1575. local function layoutCheckCell(layout, column, row)
  1576.     if column < 1 or column > #layout.columnSizes or row < 1 or row > #layout.rowSizes then
  1577.         error("Specified grid position (" .. tostring(column) .. "x" .. tostring(row) .. ") is out of layout grid range")
  1578.     end
  1579. end
  1580.  
  1581. local function layoutGetAbsoluteTotalSize(array)
  1582.     local absoluteTotalSize = 0
  1583.     for i = 1, #array do
  1584.         if array[i].sizePolicy == GUI.SIZE_POLICY_ABSOLUTE then
  1585.             absoluteTotalSize = absoluteTotalSize + array[i].size
  1586.         end
  1587.     end
  1588.     return absoluteTotalSize
  1589. end
  1590.  
  1591. local function layoutGetCalculatedSize(array, index, dependency)
  1592.     if array[index].sizePolicy == GUI.SIZE_POLICY_RELATIVE then
  1593.         array[index].calculatedSize = array[index].size * dependency
  1594.     else
  1595.         array[index].calculatedSize = array[index].size
  1596.     end
  1597. end
  1598.  
  1599. local function layoutUpdate(layout)
  1600.     local columnPercentageTotalSize, rowPercentageTotalSize = layout.width - layoutGetAbsoluteTotalSize(layout.columnSizes), layout.height - layoutGetAbsoluteTotalSize(layout.rowSizes)
  1601.     for row = 1, #layout.rowSizes do
  1602.         layoutGetCalculatedSize(layout.rowSizes, row, rowPercentageTotalSize)
  1603.         for column = 1, #layout.columnSizes do
  1604.             layoutGetCalculatedSize(layout.columnSizes, column, columnPercentageTotalSize)
  1605.             layout.cells[row][column].childrenWidth, layout.cells[row][column].childrenHeight = 0, 0
  1606.         end
  1607.     end
  1608.  
  1609.     -- Подготавливаем объекты к расположению и подсчитываем тотальные размеры
  1610.     local child, layoutRow, layoutColumn, cell
  1611.     for i = 1, #layout.children do
  1612.         child = layout.children[i]
  1613.        
  1614.         if not child.hidden then
  1615.             layoutRow, layoutColumn = child.layoutRow, child.layoutColumn
  1616.  
  1617.             -- Проверка на позицию в сетке
  1618.             if layoutRow >= 1 and layoutRow <= #layout.rowSizes and layoutColumn >= 1 and layoutColumn <= #layout.columnSizes then
  1619.                 cell = layout.cells[layoutRow][layoutColumn]
  1620.                 -- Авто-фиттинг объектов
  1621.                 if cell.horizontalFitting then
  1622.                     child.width = number.round(layout.columnSizes[layoutColumn].calculatedSize - cell.horizontalFittingRemove)
  1623.                 end
  1624.  
  1625.                 if cell.verticalFitting then
  1626.                     child.height = number.round(layout.rowSizes[layoutRow].calculatedSize - cell.verticalFittingRemove)
  1627.                 end
  1628.  
  1629.                 -- Направление и расчет размеров
  1630.                 if cell.direction == GUI.DIRECTION_HORIZONTAL then
  1631.                     cell.childrenWidth = cell.childrenWidth + child.width + cell.spacing
  1632.                     cell.childrenHeight = math.max(cell.childrenHeight, child.height)
  1633.                 else
  1634.                     cell.childrenWidth = math.max(cell.childrenWidth, child.width)
  1635.                     cell.childrenHeight = cell.childrenHeight + child.height + cell.spacing
  1636.                 end
  1637.             else
  1638.                 error("Layout child with index " .. i .. " has been assigned to cell (" .. layoutColumn .. "x" .. layoutRow .. ") out of layout grid range")
  1639.             end
  1640.         end
  1641.     end
  1642.  
  1643.     -- Высчитываем стартовую позицию объектов ячейки
  1644.     local x, y = 1, 1
  1645.     for row = 1, #layout.rowSizes do
  1646.         for column = 1, #layout.columnSizes do
  1647.             cell = layout.cells[row][column]
  1648.             cell.x, cell.y = GUI.getAlignmentCoordinates(
  1649.                 x,
  1650.                 y,
  1651.                 layout.columnSizes[column].calculatedSize,
  1652.                 layout.rowSizes[row].calculatedSize,
  1653.                 cell.horizontalAlignment,
  1654.                 cell.verticalAlignment,
  1655.                 cell.childrenWidth - (cell.direction == GUI.DIRECTION_HORIZONTAL and cell.spacing or 0),
  1656.                 cell.childrenHeight - (cell.direction == GUI.DIRECTION_VERTICAL and cell.spacing or 0)
  1657.             )
  1658.  
  1659.             -- Учитываем отступы от краев ячейки
  1660.             if cell.horizontalMargin ~= 0 or cell.verticalMargin ~= 0 then
  1661.                 cell.x, cell.y = GUI.getMarginCoordinates(
  1662.                     cell.x,
  1663.                     cell.y,
  1664.                     cell.horizontalAlignment,
  1665.                     cell.verticalAlignment,
  1666.                     cell.horizontalMargin,
  1667.                     cell.verticalMargin
  1668.                 )
  1669.             end
  1670.  
  1671.             x = x + layout.columnSizes[column].calculatedSize
  1672.         end
  1673.  
  1674.         x, y = 1, y + layout.rowSizes[row].calculatedSize
  1675.     end
  1676.  
  1677.     -- Размещаем все объекты
  1678.     for i = 1, #layout.children do
  1679.         child = layout.children[i]
  1680.        
  1681.         if not child.hidden then
  1682.             cell = layout.cells[child.layoutRow][child.layoutColumn]
  1683.            
  1684.             child.localX, cell.localY = GUI.getAlignmentCoordinates(
  1685.                 cell.x,
  1686.                 cell.y,
  1687.                 cell.childrenWidth,
  1688.                 cell.childrenHeight,
  1689.                 cell.horizontalAlignment,
  1690.                 cell.verticalAlignment,
  1691.                 child.width,
  1692.                 child.height
  1693.             )
  1694.  
  1695.             if cell.direction == GUI.DIRECTION_HORIZONTAL then
  1696.                 child.localX, child.localY = math.floor(cell.x), math.floor(cell.localY)
  1697.                 cell.x = cell.x + child.width + cell.spacing
  1698.             else
  1699.                 child.localX, child.localY = math.floor(child.localX), math.floor(cell.y)
  1700.                 cell.y = cell.y + child.height + cell.spacing
  1701.             end
  1702.         end
  1703.     end
  1704. end
  1705.  
  1706. local function layoutSetPosition(layout, column, row, object)
  1707.     layoutCheckCell(layout, column, row)
  1708.     object.layoutRow = row
  1709.     object.layoutColumn = column
  1710.  
  1711.     return object
  1712. end
  1713.  
  1714. local function layoutSetDirection(layout, column, row, direction)
  1715.     layoutCheckCell(layout, column, row)
  1716.     layout.cells[row][column].direction = direction
  1717.  
  1718.     return layout
  1719. end
  1720.  
  1721. local function layoutSetSpacing(layout, column, row, spacing)
  1722.     layoutCheckCell(layout, column, row)
  1723.     layout.cells[row][column].spacing = spacing
  1724.  
  1725.     return layout
  1726. end
  1727.  
  1728. local function layoutSetAlignment(layout, column, row, horizontalAlignment, verticalAlignment)
  1729.     layoutCheckCell(layout, column, row)
  1730.     layout.cells[row][column].horizontalAlignment, layout.cells[row][column].verticalAlignment = horizontalAlignment, verticalAlignment
  1731.  
  1732.     return layout
  1733. end
  1734.  
  1735. local function layoutGetMargin(layout, column, row)
  1736.     layoutCheckCell(layout, column, row)
  1737.  
  1738.     return layout.cells[row][column].horizontalMargin, layout.cells[row][column].verticalMargin
  1739. end
  1740.  
  1741. local function layoutSetMargin(layout, column, row, horizontalMargin, verticalMargin)
  1742.     layoutCheckCell(layout, column, row)
  1743.     layout.cells[row][column].horizontalMargin = horizontalMargin
  1744.     layout.cells[row][column].verticalMargin = verticalMargin
  1745.  
  1746.     return layout
  1747. end
  1748.  
  1749. local function layoutNewCell()
  1750.     return {
  1751.         horizontalAlignment = GUI.ALIGNMENT_HORIZONTAL_CENTER,
  1752.         verticalAlignment = GUI.ALIGNMENT_VERTICAL_CENTER,
  1753.         horizontalMargin = 0,
  1754.         verticalMargin = 0,
  1755.         direction = GUI.DIRECTION_VERTICAL,
  1756.         spacing = 1
  1757.     }
  1758. end
  1759.  
  1760. local function layoutCalculatePercentageSize(changingExistent, array, index)
  1761.     if array[index].sizePolicy == GUI.SIZE_POLICY_RELATIVE then
  1762.         local allPercents, beforeFromIndexPercents = 0, 0
  1763.         for i = 1, #array do
  1764.             if array[i].sizePolicy == GUI.SIZE_POLICY_RELATIVE then
  1765.                 allPercents = allPercents + array[i].size
  1766.  
  1767.                 if i <= index then
  1768.                     beforeFromIndexPercents = beforeFromIndexPercents + array[i].size
  1769.                 end
  1770.             end
  1771.         end
  1772.  
  1773.         local modifyer
  1774.         if changingExistent then
  1775.             if beforeFromIndexPercents > 1 then
  1776.                 error("Layout summary percentage > 100% at index " .. index)
  1777.             end
  1778.             modifyer = (1 - beforeFromIndexPercents) / (allPercents - beforeFromIndexPercents)
  1779.         else
  1780.             modifyer = (1 - array[index].size) / (allPercents - array[index].size)
  1781.         end
  1782.  
  1783.         for i = changingExistent and index + 1 or 1, #array do
  1784.             if array[i].sizePolicy == GUI.SIZE_POLICY_RELATIVE and i ~= index then
  1785.                 array[i].size = modifyer * array[i].size
  1786.             end
  1787.         end
  1788.     end
  1789. end
  1790.  
  1791. local function layoutSetColumnWidth(layout, column, sizePolicy, size)
  1792.     layout.columnSizes[column].sizePolicy, layout.columnSizes[column].size = sizePolicy, size
  1793.     layoutCalculatePercentageSize(true, layout.columnSizes, column)
  1794.  
  1795.     return layout
  1796. end
  1797.  
  1798. local function layoutSetRowHeight(layout, row, sizePolicy, size)
  1799.     layout.rowSizes[row].sizePolicy, layout.rowSizes[row].size = sizePolicy, size
  1800.     layoutCalculatePercentageSize(true, layout.rowSizes, row)
  1801.  
  1802.     return layout
  1803. end
  1804.  
  1805. local function layoutAddColumn(layout, sizePolicy, size)
  1806.     for i = 1, #layout.rowSizes do
  1807.         table.insert(layout.cells[i], layoutNewCell())
  1808.     end
  1809.  
  1810.     table.insert(layout.columnSizes, {
  1811.         sizePolicy = sizePolicy,
  1812.         size = size
  1813.     })
  1814.     layoutCalculatePercentageSize(false, layout.columnSizes, #layout.columnSizes)
  1815.  
  1816.     return layout
  1817. end
  1818.  
  1819. local function layoutAddRow(layout, sizePolicy, size)
  1820.     local row = {}
  1821.     for i = 1, #layout.columnSizes do
  1822.         table.insert(row, layoutNewCell())
  1823.     end
  1824.  
  1825.     table.insert(layout.cells, row)
  1826.     table.insert(layout.rowSizes, {
  1827.         sizePolicy = sizePolicy,
  1828.         size = size
  1829.     })
  1830.  
  1831.     layoutCalculatePercentageSize(false, layout.rowSizes, #layout.rowSizes)
  1832.  
  1833.     return layout
  1834. end
  1835.  
  1836. local function layoutRemoveRow(layout, row)
  1837.     table.remove(layout.cells, row)
  1838.  
  1839.     layout.rowSizes[row].size = 0
  1840.     layoutCalculatePercentageSize(false, layout.rowSizes, row)
  1841.  
  1842.     table.remove(layout.rowSizes, row)
  1843.  
  1844.     return layout
  1845. end
  1846.  
  1847. local function layoutRemoveColumn(layout, column)
  1848.     for i = 1, #layout.rowSizes do
  1849.         table.remove(layout.cells[i], column)
  1850.     end
  1851.  
  1852.     layout.columnSizes[column].size = 0
  1853.     layoutCalculatePercentageSize(false, layout.columnSizes, column)
  1854.  
  1855.     table.remove(layout.columnSizes, column)
  1856.  
  1857.     return layout
  1858. end
  1859.  
  1860. local function layoutSetGridSize(layout, columnCount, rowCount)
  1861.     layout.cells = {}
  1862.     layout.rowSizes = {}
  1863.     layout.columnSizes = {}
  1864.  
  1865.     local rowSize, columnSize = 1 / rowCount, 1 / columnCount
  1866.     for i = 1, rowCount do
  1867.         layoutAddRow(layout, GUI.SIZE_POLICY_RELATIVE, 1 / i)
  1868.     end
  1869.  
  1870.     for i = 1, columnCount do
  1871.         layoutAddColumn(layout, GUI.SIZE_POLICY_RELATIVE, 1 / i)
  1872.     end
  1873.  
  1874.     return layout
  1875. end
  1876.  
  1877. local function layoutDraw(layout)
  1878.     layout:update()
  1879.     containerDraw(layout)
  1880.    
  1881.     if layout.showGrid then
  1882.         local x, y = layout.x, layout.y
  1883.         for j = 1, #layout.columnSizes do
  1884.             for i = 1, #layout.rowSizes do
  1885.                 screen.drawFrame(
  1886.                     number.round(x),
  1887.                     number.round(y),
  1888.                     number.round(layout.columnSizes[j].calculatedSize),
  1889.                     number.round(layout.rowSizes[i].calculatedSize),
  1890.                     0xFF0000
  1891.                 )
  1892.                 y = y + layout.rowSizes[i].calculatedSize
  1893.             end
  1894.             x, y = x + layout.columnSizes[j].calculatedSize, layout.y
  1895.         end
  1896.     end
  1897. end
  1898.  
  1899. local function layoutFitToChildrenSize(layout, column, row)
  1900.     layout.width, layout.height = 0, 0
  1901.  
  1902.     for i = 1, #layout.children do
  1903.         if not layout.children[i].hidden then
  1904.             if layout.cells[row][column].direction == GUI.DIRECTION_HORIZONTAL then
  1905.                 layout.width = layout.width + layout.children[i].width + layout.cells[row][column].spacing
  1906.                 layout.height = math.max(layout.height, layout.children[i].height)
  1907.             else
  1908.                 layout.width = math.max(layout.width, layout.children[i].width)
  1909.                 layout.height = layout.height + layout.children[i].height + layout.cells[row][column].spacing
  1910.             end
  1911.         end
  1912.     end
  1913.  
  1914.     if layout.cells[row][column].direction == GUI.DIRECTION_HORIZONTAL then
  1915.         layout.width = layout.width - layout.cells[row][column].spacing
  1916.     else
  1917.         layout.height = layout.height - layout.cells[row][column].spacing
  1918.     end
  1919.  
  1920.     return layout
  1921. end
  1922.  
  1923. local function layoutSetFitting(layout, column, row, horizontal, vertical, horizontalRemove, verticalRemove)
  1924.     layoutCheckCell(layout, column, row)
  1925.     layout.cells[row][column].horizontalFitting = horizontal
  1926.     layout.cells[row][column].verticalFitting = vertical
  1927.     layout.cells[row][column].horizontalFittingRemove = horizontalRemove or 0
  1928.     layout.cells[row][column].verticalFittingRemove = verticalRemove or 0
  1929.  
  1930.     return layout
  1931. end
  1932.  
  1933. local function layoutAddChild(layout, object, ...)
  1934.     object.layoutRow = layout.defaultRow
  1935.     object.layoutColumn = layout.defaultColumn
  1936.     containerAddChild(layout, object, ...)
  1937.  
  1938.     return object
  1939. end
  1940.  
  1941. function GUI.layout(x, y, width, height, columnCount, rowCount)
  1942.     local layout = GUI.container(x, y, width, height)
  1943.  
  1944.     layout.defaultRow = 1
  1945.     layout.defaultColumn = 1
  1946.  
  1947.     layout.addRow = layoutAddRow
  1948.     layout.addColumn = layoutAddColumn
  1949.     layout.removeRow = layoutRemoveRow
  1950.     layout.removeColumn = layoutRemoveColumn
  1951.  
  1952.     layout.setRowHeight = layoutSetRowHeight
  1953.     layout.setColumnWidth = layoutSetColumnWidth
  1954.  
  1955.     layout.setPosition = layoutSetPosition
  1956.     layout.setDirection = layoutSetDirection
  1957.     layout.setGridSize = layoutSetGridSize
  1958.     layout.setSpacing = layoutSetSpacing
  1959.     layout.setAlignment = layoutSetAlignment
  1960.     layout.setMargin = layoutSetMargin
  1961.     layout.getMargin = layoutGetMargin
  1962.    
  1963.     layout.fitToChildrenSize = layoutFitToChildrenSize
  1964.     layout.setFitting = layoutSetFitting
  1965.  
  1966.     layout.update = layoutUpdate
  1967.     layout.addChild = layoutAddChild
  1968.     layout.draw = layoutDraw
  1969.  
  1970.     layout:setGridSize(columnCount, rowCount)
  1971.  
  1972.     return layout
  1973. end
  1974.  
  1975. --------------------------------------------------------------------------------
  1976.  
  1977. local function filesystemDialogDraw(filesystemDialog)
  1978.     if filesystemDialog.extensionComboBox.hidden then
  1979.         filesystemDialog.input.width = filesystemDialog.cancelButton.localX - 4
  1980.     else
  1981.         filesystemDialog.input.width = filesystemDialog.extensionComboBox.localX - 3
  1982.     end
  1983.  
  1984.     if filesystemDialog.IOMode == GUI.IO_MODE_SAVE then
  1985.         filesystemDialog.submitButton.disabled = not filesystemDialog.input.text or filesystemDialog.input.text == ""
  1986.     else
  1987.         filesystemDialog.input.text = filesystemDialog.filesystemTree.selectedItem or ""
  1988.         filesystemDialog.submitButton.disabled = not filesystemDialog.filesystemTree.selectedItem
  1989.     end
  1990.    
  1991.     containerDraw(filesystemDialog)
  1992.     GUI.drawShadow(filesystemDialog.x, filesystemDialog.y, filesystemDialog.width, filesystemDialog.height, GUI.CONTEXT_MENU_SHADOW_TRANSPARENCY, true)
  1993.  
  1994.     return filesystemDialog
  1995. end
  1996.  
  1997. local function filesystemDialogSetMode(filesystemDialog, IOMode, filesystemMode)
  1998.     filesystemDialog.IOMode = IOMode
  1999.     filesystemDialog.filesystemMode = filesystemMode
  2000.  
  2001.     if filesystemDialog.IOMode == GUI.IO_MODE_SAVE then
  2002.         filesystemDialog.filesystemTree.showMode = GUI.IO_MODE_DIRECTORY
  2003.         filesystemDialog.filesystemTree.selectionMode = GUI.IO_MODE_DIRECTORY
  2004.         filesystemDialog.input.disabled = false
  2005.         filesystemDialog.extensionComboBox.hidden = filesystemDialog.filesystemMode ~= GUI.IO_MODE_FILE or not filesystemDialog.filesystemTree.extensionFilters
  2006.     else
  2007.         if filesystemDialog.filesystemMode == GUI.IO_MODE_FILE then
  2008.             filesystemDialog.filesystemTree.showMode = GUI.IO_MODE_BOTH
  2009.             filesystemDialog.filesystemTree.selectionMode = GUI.IO_MODE_FILE
  2010.         else
  2011.             filesystemDialog.filesystemTree.showMode = GUI.IO_MODE_DIRECTORY
  2012.             filesystemDialog.filesystemTree.selectionMode = GUI.IO_MODE_DIRECTORY
  2013.         end
  2014.  
  2015.         filesystemDialog.input.disabled = true
  2016.         filesystemDialog.extensionComboBox.hidden = true
  2017.     end
  2018. end
  2019.  
  2020. local function filesystemDialogAddExtensionFilter(filesystemDialog, extension)
  2021.     filesystemDialog.extensionComboBox:addItem(extension)
  2022.     filesystemDialog.extensionComboBox.width = math.max(filesystemDialog.extensionComboBox.width, unicode.len(extension) + 3)
  2023.     filesystemDialog.extensionComboBox.localX = filesystemDialog.cancelButton.localX - filesystemDialog.extensionComboBox.width - 2
  2024.     filesystemDialog.filesystemTree:addExtensionFilter(extension)
  2025.  
  2026.     filesystemDialog:setMode(filesystemDialog.IOMode, filesystemDialog.filesystemMode)
  2027. end
  2028.  
  2029. local function filesystemDialogExpandPath(filesystemDialog, ...)
  2030.     filesystemDialog.filesystemTree:expandPath(...)
  2031. end
  2032.  
  2033. function GUI.filesystemDialog(x, y, width, height, submitButtonText, cancelButtonText, placeholderText, path)
  2034.     local filesystemDialog = GUI.container(x, y, width, height)
  2035.    
  2036.     filesystemDialog:addChild(GUI.panel(1, height - 2, width, 3, 0xD2D2D2))
  2037.    
  2038.     filesystemDialog.cancelButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 1, 0, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xE1E1E1, cancelButtonText))
  2039.     filesystemDialog.submitButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 1, 0, 0x3C3C3C, 0xE1E1E1, 0xE1E1E1, 0x3C3C3C, submitButtonText))
  2040.     filesystemDialog.submitButton.localX = filesystemDialog.width - filesystemDialog.submitButton.width - 1
  2041.     filesystemDialog.cancelButton.localX = filesystemDialog.submitButton.localX - filesystemDialog.cancelButton.width - 2
  2042.  
  2043.     filesystemDialog.extensionComboBox = filesystemDialog:addChild(GUI.comboBox(1, height - 1, 1, 1, 0xE1E1E1, 0x696969, 0xC3C3C3, 0x878787))
  2044.     filesystemDialog.extensionComboBox.hidden = true
  2045.  
  2046.     filesystemDialog.input = filesystemDialog:addChild(GUI.input(2, height - 1, 1, 1, 0xE1E1E1, 0x696969, 0x969696, 0xE1E1E1, 0x3C3C3C, "", placeholderText))
  2047.  
  2048.     filesystemDialog.filesystemTree = filesystemDialog:addChild(GUI.filesystemTree(1, 1, width, height - 3, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xA5A5A5, 0x3C3C3C, 0xE1E1E1, 0xB4B4B4, 0xA5A5A5, 0xC3C3C3, 0x4B4B4B))
  2049.     filesystemDialog.filesystemTree.workPath = path
  2050.     filesystemDialog.animationDuration = GUI.FILESYSTEM_DIALOG_ANIMATION_DURATION
  2051.  
  2052.     filesystemDialog.draw = filesystemDialogDraw
  2053.     filesystemDialog.setMode = filesystemDialogSetMode
  2054.     filesystemDialog.addExtensionFilter = filesystemDialogAddExtensionFilter
  2055.  
  2056.     filesystemDialog.expandPath = filesystemDialogExpandPath
  2057.     filesystemDialog:setMode(GUI.IO_MODE_OPEN, GUI.IO_MODE_FILE)
  2058.  
  2059.     return filesystemDialog
  2060. end
  2061.  
  2062. local function filesystemDialogShow(filesystemDialog)
  2063.     filesystemDialog.filesystemTree:updateFileList()
  2064.     filesystemDialog:addAnimation(
  2065.         function(animation)
  2066.             filesystemDialog.localY = math.floor(1 + (1.0 - animation.position) * (-filesystemDialog.height))
  2067.         end,
  2068.         function(animation)
  2069.             animation:remove()
  2070.         end
  2071.     ):start(filesystemDialog.animationDuration)
  2072.  
  2073.     return filesystemDialog
  2074. end
  2075.  
  2076. --------------------------------------------------------------------------------
  2077.  
  2078. function GUI.addFilesystemDialog(parentContainer, addPanel, ...)
  2079.     local container = GUI.addBackgroundContainer(parentContainer, addPanel, false, nil)
  2080.  
  2081.     local filesystemDialog = container:addChild(GUI.filesystemDialog(1, 1, ...))
  2082.     filesystemDialog.localX = math.floor(container.width / 2 - filesystemDialog.width / 2)
  2083.     filesystemDialog.localY = -filesystemDialog.height
  2084.  
  2085.     local function onAnyTouch()
  2086.         container:remove()
  2087.         filesystemDialog.firstParent:draw()
  2088.     end
  2089.  
  2090.     filesystemDialog.cancelButton.onTouch = function()
  2091.         onAnyTouch()
  2092.  
  2093.         if filesystemDialog.onCancel then
  2094.             filesystemDialog.onCancel()
  2095.         end
  2096.     end
  2097.  
  2098.     filesystemDialog.submitButton.onTouch = function()
  2099.         onAnyTouch()
  2100.        
  2101.         local path = filesystemDialog.filesystemTree.selectedItem or filesystemDialog.filesystemTree.workPath or "/"
  2102.         if filesystemDialog.IOMode == GUI.IO_MODE_SAVE then
  2103.             path = path .. filesystemDialog.input.text
  2104.            
  2105.             if filesystemDialog.filesystemMode == GUI.IO_MODE_FILE then
  2106.                 local selectedItem = filesystemDialog.extensionComboBox:getItem(filesystemDialog.extensionComboBox.selectedItem)
  2107.                 path = path .. (selectedItem and selectedItem.text or "")
  2108.             else
  2109.                 path = path .. "/"
  2110.             end
  2111.         end
  2112.  
  2113.         if filesystemDialog.onSubmit then
  2114.             filesystemDialog.onSubmit(path)
  2115.         end
  2116.     end
  2117.  
  2118.     filesystemDialog.show = filesystemDialogShow
  2119.  
  2120.     return filesystemDialog
  2121. end
  2122.  
  2123. --------------------------------------------------------------------------------
  2124.  
  2125. local function filesystemChooserDraw(object)
  2126.     local tipWidth = object.height * 2 - 1
  2127.     local y = math.floor(object.y + object.height / 2)
  2128.    
  2129.     screen.drawRectangle(object.x, object.y, object.width - tipWidth, object.height, object.colors.background, object.colors.text, " ")
  2130.     screen.drawRectangle(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, " ")
  2131.     screen.drawText(object.x + object.width - math.floor(tipWidth / 2) - 1, y, object.pressed and object.colors.tipBackground or object.colors.tipText, "…")
  2132.     screen.drawText(object.x + 1, y, object.colors.text, text.limit(object.path or object.placeholderText, object.width - tipWidth - 2, "left"))
  2133.  
  2134.     return object
  2135. end
  2136.  
  2137. local function filesystemChooserAddExtensionFilter(object, extension)
  2138.     object.extensionFilters[unicode.lower(extension)] = true
  2139. end
  2140.  
  2141. local function filesystemChooserSetMode(object, IOMode, filesystemMode)
  2142.     object.IOMode = IOMode
  2143.     object.filesystemMode = filesystemMode
  2144. end
  2145.  
  2146. local function filesystemChooserEventHandler(workspace, object, e1)
  2147.     if e1 == "touch" then
  2148.         object.pressed = true
  2149.         workspace:draw()
  2150.  
  2151.         local filesystemDialog = GUI.addFilesystemDialog(workspace, false, 50, math.floor(workspace.height * 0.8), object.submitButtonText, object.cancelButtonText, object.placeholderText, object.filesystemDialogPath)
  2152.  
  2153.         for key in pairs(object.extensionFilters) do
  2154.             filesystemDialog:addExtensionFilter(key)
  2155.         end
  2156.  
  2157.         filesystemDialog:setMode(object.IOMode, object.filesystemMode)
  2158.  
  2159.         if object.path and #object.path > 0 then
  2160.             -- local path = object.path:gsub("/+", "/")
  2161.             filesystemDialog.filesystemTree.selectedItem = object.IOMode == GUI.IO_MODE_OPEN and object.path or filesystem.path(object.path)
  2162.             filesystemDialog.input.text = filesystem.name(object.path)
  2163.             filesystemDialog:expandPath(object.IOMode == GUI.IO_MODE_OPEN and filesystem.path(object.path) or filesystem.path(filesystem.path(object.path)))
  2164.         end
  2165.        
  2166.         filesystemDialog.onCancel = function()
  2167.             object.pressed = false
  2168.             workspace:draw()
  2169.         end
  2170.  
  2171.         filesystemDialog.onSubmit = function(path)
  2172.             object.path = path
  2173.             filesystemDialog.onCancel()
  2174.             if object.onSubmit then
  2175.                 object.onSubmit(object.path)
  2176.             end
  2177.         end
  2178.  
  2179.         filesystemDialog:show()
  2180.     end
  2181. end
  2182.  
  2183. function GUI.filesystemChooser(x, y, width, height, backgroundColor, textColor, tipBackgroundColor, tipTextColor, path, submitButtonText, cancelButtonText, placeholderText, filesystemDialogPath)
  2184.     local object = GUI.object(x, y, width, height)
  2185.    
  2186.     object.colors = {
  2187.         tipBackground = tipBackgroundColor,
  2188.         tipText = tipTextColor,
  2189.         text = textColor,
  2190.         background = backgroundColor
  2191.     }
  2192.  
  2193.     object.submitButtonText = submitButtonText
  2194.     object.cancelButtonText = cancelButtonText
  2195.     object.placeholderText = placeholderText
  2196.     object.pressed = false
  2197.     object.path = path
  2198.     object.filesystemDialogPath = filesystemDialogPath
  2199.     object.filesystemMode = GUI.IO_MODE_FILE
  2200.     object.IOMode = GUI.IO_MODE_OPEN
  2201.     object.extensionFilters = {}
  2202.  
  2203.     object.draw = filesystemChooserDraw
  2204.     object.eventHandler = filesystemChooserEventHandler
  2205.     object.addExtensionFilter = filesystemChooserAddExtensionFilter
  2206.     object.setMode = filesystemChooserSetMode
  2207.  
  2208.     return object
  2209. end
  2210.  
  2211. --------------------------------------------------------------------------------
  2212.  
  2213. local function resizerDraw(object)
  2214.     local horizontalMode, x, y, symbol = object.width >= object.height
  2215.  
  2216.     if horizontalMode then
  2217.         screen.drawText(object.x, math.floor(object.y + object.height / 2), object.colors.helper, string.rep("━", object.width))
  2218.        
  2219.         if object.lastTouchX then
  2220.             screen.drawText(object.lastTouchX, object.lastTouchY, object.colors.arrow, "↑")
  2221.         end
  2222.     else
  2223.         local x = math.floor(object.x + object.width / 2)
  2224.         local bufferWidth, bufferHeight, index = screen.getResolution()
  2225.        
  2226.         for i = object.y, object.y + object.height - 1 do
  2227.             if x >= 1 and x <= bufferWidth and i >= 1 and i <= bufferHeight then
  2228.                 index = screen.getIndex(x, i)
  2229.                 screen.rawSet(index, screen.rawGet(index), object.colors.helper, "┃")
  2230.             end
  2231.         end
  2232.  
  2233.         if object.lastTouchX then
  2234.             screen.drawText(object.lastTouchX - 1, object.lastTouchY, object.colors.arrow, "←→")
  2235.         end
  2236.     end
  2237. end
  2238.  
  2239. local function resizerEventHandler(workspace, object, e1, e2, e3, e4)
  2240.     if e1 == "touch" then
  2241.         object.lastTouchX, object.lastTouchY = math.ceil(e3), math.ceil(e4)
  2242.         workspace:draw()
  2243.    
  2244.     elseif e1 == "drag" and object.lastTouchX then
  2245.         e3, e4 = math.ceil(e3), math.ceil(e4)
  2246.  
  2247.         if object.onResize then
  2248.             object.onResize(e3 - object.lastTouchX, e4 - object.lastTouchY)
  2249.         end
  2250.        
  2251.         object.lastTouchX, object.lastTouchY = e3, e4
  2252.         workspace:draw()
  2253.    
  2254.     elseif e1 == "drop" then
  2255.         if object.onResizeFinished then
  2256.             object.onResizeFinished()
  2257.         end
  2258.  
  2259.         object.lastTouchX, object.lastTouchY = nil, nil
  2260.         workspace:draw()
  2261.     end
  2262. end
  2263.  
  2264. function GUI.resizer(x, y, width, height, helperColor, arrowColor)
  2265.     local object = GUI.object(x, y, width, height)
  2266.    
  2267.     object.colors = {
  2268.         helper = helperColor,
  2269.         arrow = arrowColor
  2270.     }
  2271.  
  2272.     object.draw = resizerDraw
  2273.     object.eventHandler = resizerEventHandler
  2274.  
  2275.     return object
  2276. end
  2277.  
  2278. --------------------------------------------------------------------------------
  2279.  
  2280. local function scrollBarDraw(scrollBar)
  2281.     local isVertical = scrollBar.height > scrollBar.width
  2282.     local valuesDelta = scrollBar.maximumValue - scrollBar.minimumValue
  2283.     local part = scrollBar.value / valuesDelta
  2284.  
  2285.     if isVertical then
  2286.         local barSize = math.ceil(scrollBar.shownValueCount / valuesDelta * scrollBar.height)
  2287.         local halfBarSize = math.floor(barSize / 2)
  2288.        
  2289.         scrollBar.ghostPosition.y = scrollBar.y + halfBarSize
  2290.         scrollBar.ghostPosition.height = scrollBar.height - barSize
  2291.  
  2292.         if scrollBar.thin then
  2293.             local y1 = math.floor(scrollBar.ghostPosition.y + part * scrollBar.ghostPosition.height - halfBarSize)
  2294.             local y2 = y1 + barSize - 1
  2295.             local background
  2296.  
  2297.             for y = scrollBar.y, scrollBar.y + scrollBar.height - 1 do
  2298.                 background = screen.get(scrollBar.x, y)
  2299.                 screen.set(scrollBar.x, y, background, y >= y1 and y <= y2 and scrollBar.colors.foreground or scrollBar.colors.background, "┃")
  2300.             end
  2301.         else
  2302.             screen.drawRectangle(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ")
  2303.             screen.drawRectangle(
  2304.                 scrollBar.x,
  2305.                 math.floor(scrollBar.ghostPosition.y + part * scrollBar.ghostPosition.height - halfBarSize),
  2306.                 scrollBar.width,
  2307.                 barSize,
  2308.                 scrollBar.colors.foreground, 0x0, " "
  2309.             )
  2310.         end
  2311.     else
  2312.         local barSize = math.ceil(scrollBar.shownValueCount / valuesDelta * scrollBar.width)
  2313.         local halfBarSize = math.floor(barSize / 2)
  2314.        
  2315.         scrollBar.ghostPosition.x = scrollBar.x + halfBarSize
  2316.         scrollBar.ghostPosition.width = scrollBar.width - barSize
  2317.  
  2318.         if scrollBar.thin then
  2319.             local x1 = math.floor(scrollBar.ghostPosition.x + part * scrollBar.ghostPosition.width - halfBarSize)
  2320.             local x2 = x1 + barSize - 1
  2321.             local background
  2322.  
  2323.             for x = scrollBar.x, scrollBar.x + scrollBar.width - 1 do
  2324.                 background = screen.get(x, scrollBar.y)
  2325.                 screen.set(x, scrollBar.y, background, x >= x1 and x <= x2 and scrollBar.colors.foreground or scrollBar.colors.background, "⠤")
  2326.             end
  2327.         else
  2328.             screen.drawRectangle(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ")
  2329.             screen.drawRectangle(
  2330.                 math.floor(scrollBar.ghostPosition.x + part * scrollBar.ghostPosition.width - halfBarSize),
  2331.                 scrollBar.y,
  2332.                 barSize,
  2333.                 scrollBar.height,
  2334.                 scrollBar.colors.foreground, 0x0, " "
  2335.             )
  2336.         end
  2337.     end
  2338.  
  2339.     return scrollBar
  2340. end
  2341.  
  2342. local function scrollBarEventHandler(workspace, object, e1, e2, e3, e4, e5, ...)
  2343.     local newValue = object.value
  2344.  
  2345.     if e1 == "touch" or e1 == "drag" then
  2346.         e3, e4 = math.ceil(e3), math.ceil(e4)
  2347.  
  2348.         if object.height > object.width then
  2349.             if e4 == object.y + object.height - 1 then
  2350.                 newValue = object.maximumValue
  2351.             else
  2352.                 newValue = object.minimumValue + (e4 - object.y) / object.height * (object.maximumValue - object.minimumValue)
  2353.             end
  2354.         else
  2355.             if e3 == object.x + object.width - 1 then
  2356.                 newValue = object.maximumValue
  2357.             else
  2358.                 newValue = object.minimumValue + (e3 - object.x) / object.width * (object.maximumValue - object.minimumValue)
  2359.             end
  2360.         end
  2361.    
  2362.     elseif e1 == "scroll" then
  2363.         if e5 == 1 then
  2364.             if object.value >= object.minimumValue + object.onScrollValueIncrement then
  2365.                 newValue = object.value - object.onScrollValueIncrement
  2366.             else
  2367.                 newValue = object.minimumValue
  2368.             end
  2369.         else
  2370.             if object.value <= object.maximumValue - object.onScrollValueIncrement then
  2371.                 newValue = object.value + object.onScrollValueIncrement
  2372.             else
  2373.                 newValue = object.maximumValue
  2374.             end
  2375.         end
  2376.     end
  2377.  
  2378.     if e1 == "touch" or e1 == "drag" or e1 == "scroll" then
  2379.         object.value = newValue
  2380.  
  2381.         if object.onTouch then
  2382.             object.onTouch(workspace, object, e1, e2, e3, e4, e5, ...)
  2383.         end
  2384.  
  2385.         workspace:draw()
  2386.     end
  2387. end
  2388.  
  2389. function GUI.scrollBar(x, y, width, height, backgroundColor, foregroundColor, minimumValue, maximumValue, value, shownValueCount, onScrollValueIncrement, thin)
  2390.     local scrollBar = GUI.object(x, y, width, height)
  2391.  
  2392.     scrollBar.eventHandler = scrollBarEventHandler
  2393.     scrollBar.maximumValue = maximumValue
  2394.     scrollBar.minimumValue = minimumValue
  2395.     scrollBar.value = value
  2396.     scrollBar.onScrollValueIncrement = onScrollValueIncrement
  2397.     scrollBar.shownValueCount = shownValueCount
  2398.     scrollBar.thin = thin
  2399.     scrollBar.colors = {
  2400.         background = backgroundColor,
  2401.         foreground = foregroundColor,
  2402.     }
  2403.     scrollBar.ghostPosition = {}
  2404.     scrollBar.draw = scrollBarDraw
  2405.  
  2406.     return scrollBar
  2407. end
  2408.  
  2409. --------------------------------------------------------------------------------
  2410.  
  2411. local function treeDraw(tree)  
  2412.     local y, yEnd, showScrollBar = tree.y, tree.y + tree.height - 1, #tree.items > tree.height
  2413.     local textLimit = tree.width - (showScrollBar and 1 or 0)
  2414.  
  2415.     if tree.colors.default.background then
  2416.         screen.drawRectangle(tree.x, tree.y, tree.width, tree.height, tree.colors.default.background, tree.colors.default.expandable, " ")
  2417.     end
  2418.  
  2419.     for i = tree.fromItem, #tree.items do
  2420.         local textColor, arrowColor, text = tree.colors.default.notExpandable, tree.colors.default.arrow, tree.items[i].expandable and "■ " or "□ "
  2421.  
  2422.         if tree.selectedItem == tree.items[i].definition then
  2423.             textColor, arrowColor = tree.colors.selected.any, tree.colors.selected.arrow
  2424.             screen.drawRectangle(tree.x, y, tree.width, 1, tree.colors.selected.background, textColor, " ")
  2425.         else
  2426.             if tree.items[i].expandable then
  2427.                 textColor = tree.colors.default.expandable
  2428.             elseif tree.items[i].disabled then
  2429.                 textColor = tree.colors.disabled
  2430.             end
  2431.         end
  2432.  
  2433.         if tree.items[i].expandable then
  2434.             screen.drawText(tree.x + tree.items[i].offset, y, arrowColor, tree.expandedItems[tree.items[i].definition] and "▽" or "▷")
  2435.         end
  2436.  
  2437.         screen.drawText(tree.x + tree.items[i].offset + 2, y, textColor, unicode.sub(text .. tree.items[i].name, 1, textLimit - tree.items[i].offset - 2))
  2438.  
  2439.         y = y + 1
  2440.         if y > yEnd then break end
  2441.     end
  2442.  
  2443.     if showScrollBar then
  2444.         local scrollBar = tree.scrollBar
  2445.         scrollBar.x = tree.x + tree.width - 1
  2446.         scrollBar.y = tree.y
  2447.         scrollBar.width = 1
  2448.         scrollBar.height = tree.height
  2449.         scrollBar.colors.background = tree.colors.scrollBar.background
  2450.         scrollBar.colors.foreground = tree.colors.scrollBar.foreground
  2451.         scrollBar.minimumValue = 1
  2452.         scrollBar.maximumValue = #tree.items
  2453.         scrollBar.value = tree.fromItem
  2454.         scrollBar.shownValueCount = tree.height
  2455.         scrollBar.onScrollValueIncrement = 1
  2456.         scrollBar.thin = true
  2457.  
  2458.         scrollBar:draw()
  2459.     end
  2460.  
  2461.     return tree
  2462. end
  2463.  
  2464. local function treeEventHandler(workspace, tree, e1, e2, e3, e4, e5, ...)
  2465.     if e1 == "touch" then
  2466.         e3, e4 = math.ceil(e3), math.ceil(e4)
  2467.  
  2468.         local i = e4 - tree.y + tree.fromItem
  2469.        
  2470.         if tree.items[i] then
  2471.             if
  2472.                 tree.items[i].expandable and
  2473.                 (
  2474.                     tree.selectionMode == GUI.IO_MODE_FILE or
  2475.                     e3 >= tree.x + tree.items[i].offset - 1 and e3 <= tree.x + tree.items[i].offset + 1
  2476.                 )
  2477.             then
  2478.                 if tree.expandedItems[tree.items[i].definition] then
  2479.                     tree.expandedItems[tree.items[i].definition] = nil
  2480.                 else
  2481.                     tree.expandedItems[tree.items[i].definition] = true
  2482.                 end
  2483.  
  2484.                 if tree.onItemExpanded then
  2485.                     tree.onItemExpanded(tree.selectedItem, e1, e2, e3, e4, e5, ...)
  2486.                 end
  2487.             else
  2488.                 if
  2489.                     (
  2490.                         tree.selectionMode == GUI.IO_MODE_BOTH or
  2491.                         tree.selectionMode == GUI.IO_MODE_DIRECTORY and tree.items[i].expandable or
  2492.                         tree.selectionMode == GUI.IO_MODE_FILE
  2493.                     ) and not tree.items[i].disabled
  2494.                 then
  2495.                     tree.selectedItem = tree.items[i].definition
  2496.  
  2497.                     if tree.onItemSelected then
  2498.                         tree.onItemSelected(tree.selectedItem, e1, e2, e3, e4, e5, ...)
  2499.                     end
  2500.                 end
  2501.             end
  2502.  
  2503.             workspace:draw()
  2504.         end
  2505.    
  2506.     elseif e1 == "scroll" then
  2507.         if e5 == 1 then
  2508.             if tree.fromItem > 1 then
  2509.                 tree.fromItem = tree.fromItem - 1
  2510.                 workspace:draw()
  2511.             end
  2512.         else
  2513.             if tree.fromItem < #tree.items then
  2514.                 tree.fromItem = tree.fromItem + 1
  2515.                 workspace:draw()
  2516.             end
  2517.         end
  2518.     end
  2519. end
  2520.  
  2521. local function treeAddItem(tree, name, definition, offset, expandable, disabled)
  2522.     local item = {
  2523.         name = name,
  2524.         expandable = expandable,
  2525.         offset = offset or 0,
  2526.         definition = definition,
  2527.         disabled = disabled
  2528.     }
  2529.     table.insert(tree.items, item)
  2530.  
  2531.     return item
  2532. end
  2533.  
  2534. function GUI.tree(x, y, width, height, backgroundColor, expandableColor, notExpandableColor, arrowColor, backgroundSelectedColor, anySelectionColor, arrowSelectionColor, disabledColor, scrollBarBackground, scrollBarForeground, showMode, selectionMode)
  2535.     local tree = GUI.object(x, y, width, height)
  2536.    
  2537.     tree.eventHandler = treeEventHandler
  2538.     tree.colors = {
  2539.         default = {
  2540.             background = backgroundColor,
  2541.             expandable = expandableColor,
  2542.             notExpandable = notExpandableColor,
  2543.             arrow = arrowColor,
  2544.         },
  2545.         selected = {
  2546.             background = backgroundSelectedColor,
  2547.             any = anySelectionColor,
  2548.             arrow = arrowSelectionColor,
  2549.         },
  2550.         scrollBar = {
  2551.             background = scrollBarBackground,
  2552.             foreground = scrollBarForeground
  2553.         },
  2554.         disabled = disabledColor
  2555.     }
  2556.     tree.items = {}
  2557.     tree.fromItem = 1
  2558.     tree.selectedItem = nil
  2559.     tree.expandedItems = {}
  2560.  
  2561.     tree.scrollBar = GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1)
  2562.  
  2563.     tree.showMode = showMode
  2564.     tree.selectionMode = selectionMode
  2565.     tree.eventHandler = treeEventHandler
  2566.     tree.addItem = treeAddItem
  2567.     tree.draw = treeDraw
  2568.  
  2569.     return tree
  2570. end
  2571.  
  2572. --------------------------------------------------------------------------------
  2573.  
  2574. local function filesystemTreeUpdateFileListRecursively(tree, path, offset)
  2575.     local list = filesystem.list(path)
  2576.  
  2577.     local i, expandables = 1, {}
  2578.     while i <= #list do
  2579.         if filesystem.isDirectory(path .. list[i]) then
  2580.             table.insert(expandables, list[i])
  2581.             table.remove(list, i)
  2582.         else
  2583.             i = i + 1
  2584.         end
  2585.     end
  2586.  
  2587.     table.sort(expandables, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
  2588.     table.sort(list, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
  2589.  
  2590.     if tree.showMode == GUI.IO_MODE_BOTH or tree.showMode == GUI.IO_MODE_DIRECTORY then
  2591.         for i = 1, #expandables do
  2592.             tree:addItem(filesystem.name(expandables[i]):sub(1, -2), path .. expandables[i], offset, true)
  2593.  
  2594.             if tree.expandedItems[path .. expandables[i]] then
  2595.                 filesystemTreeUpdateFileListRecursively(tree, path .. expandables[i], offset + 2)
  2596.             end
  2597.         end
  2598.     end
  2599.  
  2600.     if tree.showMode == GUI.IO_MODE_BOTH or tree.showMode == GUI.IO_MODE_FILE then
  2601.         for i = 1, #list do
  2602.             tree:addItem(list[i], path .. list[i], offset, false, tree.extensionFilters and not tree.extensionFilters[filesystem.extension(path .. list[i], true)] or false)
  2603.         end
  2604.     end
  2605. end
  2606.  
  2607. local function filesystemTreeUpdateFileList(tree)
  2608.     tree.items = {}
  2609.     filesystemTreeUpdateFileListRecursively(tree, tree.workPath, 1)
  2610. end
  2611.  
  2612. local function filesystemTreeAddExtensionFilter(tree, extensionFilter)
  2613.     tree.extensionFilters = tree.extensionFilters or {}
  2614.     tree.extensionFilters[unicode.lower(extensionFilter)] = true
  2615. end
  2616.  
  2617. local function filesystemTreeExpandPath(tree, path)
  2618.     local blyadina = tree.workPath
  2619.     for pizda in path:gmatch("[^/]+") do
  2620.         blyadina = blyadina .. pizda .. "/"
  2621.         tree.expandedItems[blyadina] = true
  2622.     end
  2623. end
  2624.  
  2625. function GUI.filesystemTree(...)
  2626.     local tree = GUI.tree(...)
  2627.  
  2628.     tree.workPath = "/"
  2629.     tree.updateFileList = filesystemTreeUpdateFileList
  2630.     tree.addExtensionFilter = filesystemTreeAddExtensionFilter
  2631.     tree.expandPath = filesystemTreeExpandPath
  2632.     tree.onItemExpanded = function()
  2633.         tree:updateFileList()
  2634.     end
  2635.  
  2636.     return tree
  2637. end
  2638.  
  2639. --------------------------------------------------------------------------------
  2640.  
  2641. local function textBoxUpdate(object)
  2642.     local doubleVerticalOffset = object.offset.vertical * 2
  2643.     object.textWidth = object.width - object.offset.horizontal * 2 - (object.scrollBarEnabled and 1 or 0)
  2644.  
  2645.     object.linesCopy = {}
  2646.  
  2647.     if object.autoWrap then
  2648.         for i = 1, #object.lines do
  2649.             local isTable = type(object.lines[i]) == "table"
  2650.             for subLine in (isTable and object.lines[i].text or object.lines[i]):gmatch("[^\n]+") do
  2651.                 local wrappedLine = text.wrap(subLine, object.textWidth)
  2652.                 for j = 1, #wrappedLine do
  2653.                     table.insert(object.linesCopy, isTable and {text = wrappedLine[j], color = object.lines[i].color} or wrappedLine[j])
  2654.                 end
  2655.             end
  2656.         end
  2657.     else
  2658.         for i = 1, #object.lines do
  2659.             table.insert(object.linesCopy, object.lines[i])
  2660.         end
  2661.     end
  2662.  
  2663.     if object.autoHeight then
  2664.         object.height = #object.linesCopy + doubleVerticalOffset
  2665.     end
  2666.  
  2667.     object.textHeight = object.height - doubleVerticalOffset
  2668. end
  2669.  
  2670. local function textBoxDraw(object)
  2671.     object:update()
  2672.  
  2673.     if object.colors.background then
  2674.         screen.drawRectangle(object.x, object.y, object.width, object.height, object.colors.background, object.colors.text, " ", object.colors.transparency)
  2675.     end
  2676.  
  2677.     local x, y = nil, object.y + object.offset.vertical
  2678.     local lineType, line, textColor
  2679.     for i = object.currentLine, object.currentLine + object.textHeight - 1 do
  2680.         if object.linesCopy[i] then
  2681.             lineType = type(object.linesCopy[i])
  2682.             if lineType == "string" then
  2683.                 line, textColor = text.limit(object.linesCopy[i], object.textWidth), object.colors.text
  2684.             elseif lineType == "table" then
  2685.                 line, textColor = text.limit(object.linesCopy[i].text, object.textWidth), object.linesCopy[i].color
  2686.             else
  2687.                 error("Unknown TextBox line type: " .. tostring(lineType))
  2688.             end
  2689.  
  2690.             x = GUI.getAlignmentCoordinates(
  2691.                 object.x + object.offset.horizontal,
  2692.                 1,
  2693.                 object.textWidth,
  2694.                 1,
  2695.                 object.horizontalAlignment,
  2696.                 object.verticalAlignment,
  2697.                 unicode.len(line),
  2698.                 1
  2699.             )
  2700.  
  2701.             screen.drawText(math.floor(x), y, textColor, line)
  2702.             y = y + 1
  2703.         else
  2704.             break
  2705.         end
  2706.     end
  2707.  
  2708.     if object.scrollBarEnabled and object.textHeight < #object.lines then
  2709.         object.scrollBar.x = object.x + object.width - 1
  2710.         object.scrollBar.y = object.y
  2711.         object.scrollBar.height = object.height
  2712.         object.scrollBar.maximumValue = #object.lines - object.textHeight + 1
  2713.         object.scrollBar.value = object.currentLine
  2714.         object.scrollBar.shownValueCount = object.textHeight
  2715.  
  2716.         object.scrollBar:draw()
  2717.     end
  2718.  
  2719.     return object
  2720. end
  2721.  
  2722. local function scrollDownTextBox(object, count)
  2723.     count = math.min(count or 1, #object.lines - object.height - object.currentLine + object.offset.vertical * 2 + 1)
  2724.     if #object.lines >= object.height and object.currentLine < #object.lines - count then
  2725.         object.currentLine = object.currentLine + count
  2726.     end
  2727.  
  2728.     return object
  2729. end
  2730.  
  2731. local function scrollUpTextBox(object, count)
  2732.     count = count or 1
  2733.     if object.currentLine > count and object.currentLine >= 1 then object.currentLine = object.currentLine - count end
  2734.     return object
  2735. end
  2736.  
  2737. local function scrollToStartTextBox(object)
  2738.     object.currentLine = 1
  2739.     return object
  2740. end
  2741.  
  2742. local function scrollToEndTextBox(object)
  2743.     if #object.lines > object.textHeight then
  2744.         object.currentLine = #object.lines - object.textHeight + 1
  2745.     end
  2746.  
  2747.     return object
  2748. end
  2749.  
  2750. local function textBoxScrollEventHandler(workspace, object, e1, e2, e3, e4, e5)
  2751.     if e1 == "scroll" then
  2752.         if e5 == 1 then
  2753.             object:scrollUp()
  2754.         else
  2755.             object:scrollDown()
  2756.         end
  2757.  
  2758.         workspace:draw()
  2759.     end
  2760. end
  2761.  
  2762. function GUI.textBox(x, y, width, height, backgroundColor, textColor, lines, currentLine, horizontalOffset, verticalOffset, autoWrap, autoHeight)
  2763.     local object = GUI.object(x, y, width, height)
  2764.    
  2765.     object.colors = {
  2766.         text = textColor,
  2767.         background = backgroundColor
  2768.     }
  2769.     object.lines = lines
  2770.     object.currentLine = currentLine or 1
  2771.     object.scrollUp = scrollUpTextBox
  2772.     object.scrollDown = scrollDownTextBox
  2773.     object.scrollToStart = scrollToStartTextBox
  2774.     object.scrollToEnd = scrollToEndTextBox
  2775.     object.offset = {horizontal = horizontalOffset or 0, vertical = verticalOffset or 0}
  2776.     object.autoWrap = autoWrap
  2777.     object.autoHeight = autoHeight
  2778.     object.scrollBar = GUI.scrollBar(1, 1, 1, 1, 0xC3C3C3, 0x4B4B4B, 1, 1, 1, 1, 1, true)
  2779.     object.scrollBarEnabled = false
  2780.    
  2781.     object.eventHandler = textBoxScrollEventHandler
  2782.     object.draw = textBoxDraw
  2783.     object.update = textBoxUpdate
  2784.  
  2785.     object.setAlignment = GUI.setAlignment
  2786.     object:setAlignment(GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP)
  2787.     object:update()
  2788.  
  2789.     return object
  2790. end
  2791.  
  2792. --------------------------------------------------------------------------------
  2793.  
  2794. local function inputSetCursorPosition(input, newPosition)
  2795.     if newPosition < 1 then
  2796.         newPosition = 1
  2797.     elseif newPosition > unicode.len(input.text) + 1 then
  2798.         newPosition = unicode.len(input.text) + 1
  2799.     end
  2800.  
  2801.     if newPosition > input.textCutFrom + input.width - 1 - input.textOffset * 2 then
  2802.         input.textCutFrom = input.textCutFrom + newPosition - (input.textCutFrom + input.width - 1 - input.textOffset * 2)
  2803.     elseif newPosition < input.textCutFrom then
  2804.         input.textCutFrom = newPosition
  2805.     end
  2806.  
  2807.     input.cursorPosition = newPosition
  2808.  
  2809.     return input
  2810. end
  2811.  
  2812. local function inputTextDrawMethod(x, y, color, text)
  2813.     screen.drawText(x, y, color, text)
  2814. end
  2815.  
  2816. local function inputDraw(input)
  2817.     local background, foreground, transparency, text
  2818.    
  2819.     if input.focused then
  2820.         background, transparency = input.colors.focused.background, input.colors.focused.transparency
  2821.         if input.text == "" then
  2822.             input.textCutFrom = 1
  2823.             foreground, text = input.colors.placeholderText, input.text
  2824.         else
  2825.             foreground = input.colors.focused.text
  2826.             if input.textMask then
  2827.                 text = string.rep(input.textMask, unicode.len(input.text))
  2828.             else
  2829.                 text = input.text
  2830.             end
  2831.         end
  2832.     else
  2833.         background, transparency = input.colors.default.background, input.colors.default.transparency
  2834.         if input.text == "" then
  2835.             input.textCutFrom = 1
  2836.             foreground, text = input.colors.placeholderText, input.placeholderText
  2837.         else
  2838.             foreground = input.colors.default.text
  2839.             if input.textMask then
  2840.                 text = string.rep(input.textMask, unicode.len(input.text))
  2841.             else
  2842.                 text = input.text
  2843.             end
  2844.         end
  2845.     end
  2846.  
  2847.     if background then
  2848.         screen.drawRectangle(input.x, input.y, input.width, input.height, background, foreground, " ", transparency)
  2849.     end
  2850.  
  2851.     local y = input.y + math.floor(input.height / 2)
  2852.  
  2853.     input.textDrawMethod(
  2854.         input.x + input.textOffset,
  2855.         y,
  2856.         foreground,
  2857.         unicode.sub(
  2858.             text or "",
  2859.             input.textCutFrom,
  2860.             input.textCutFrom + input.width - 1 - input.textOffset * 2
  2861.         )
  2862.     )
  2863.  
  2864.     if input.cursorBlinkState then
  2865.         local index = screen.getIndex(input.x + input.cursorPosition - input.textCutFrom + input.textOffset, y)
  2866.         local background = screen.rawGet(index)
  2867.         screen.rawSet(index, background, input.colors.cursor, input.cursorSymbol)
  2868.     end
  2869. end
  2870.  
  2871. local function inputCursorBlink(workspace, input, state)
  2872.     input.cursorBlinkState = state
  2873.     input.cursorBlinkUptime = computer.uptime()
  2874.     workspace:draw()
  2875. end
  2876.  
  2877. local function inputStopInput(workspace, input)
  2878.     input.stopInputObject:remove()
  2879.     input.focused = false
  2880.  
  2881.     if input.validator then
  2882.         if not input.validator(input.text) then
  2883.             input.text = input.startText
  2884.             input.startText = nil
  2885.  
  2886.             input:setCursorPosition(unicode.len(input.text) + 1)
  2887.         end
  2888.     end
  2889.    
  2890.     if input.onInputFinished then
  2891.         input.onInputFinished(workspace, input)
  2892.     end
  2893.  
  2894.     inputCursorBlink(workspace, input, false)
  2895. end
  2896.  
  2897. local function inputStartInput(input)
  2898.     input.startText = input.text
  2899.     input.focused = true
  2900.  
  2901.     if input.historyEnabled then
  2902.         input.historyIndex = input.historyIndex + 1
  2903.     end
  2904.  
  2905.     if input.eraseTextOnFocus then
  2906.         input.text = ""
  2907.     end
  2908.    
  2909.     input:setCursorPosition(input.cursorPosition)
  2910.  
  2911.     input.stopInputObject.width, input.stopInputObject.height = input.firstParent.width, input.firstParent.height
  2912.     input.firstParent:addChild(input.stopInputObject)
  2913.  
  2914.     inputCursorBlink(input.firstParent, input, true)
  2915. end
  2916.  
  2917. local function inputEventHandler(workspace, input, e1, e2, e3, e4, e5, e6, ...)
  2918.     if e1 == "touch" or e1 == "drag" then
  2919.         input:setCursorPosition(input.textCutFrom + math.ceil(e3) - input.x - input.textOffset)
  2920.  
  2921.         if input.focused then
  2922.             inputCursorBlink(workspace, input, true)
  2923.         else
  2924.             input:startInput()
  2925.         end
  2926.    
  2927.     elseif e1 == "key_down" and input.focused then
  2928.         workspace:consumeEvent()
  2929.  
  2930.         -- Return
  2931.         if e4 == 28 then
  2932.             if input.historyEnabled then
  2933.                 -- Очистка истории
  2934.                 for i = 1, (#input.history - input.historyLimit) do
  2935.                     table.remove(input.history, 1)
  2936.                 end
  2937.  
  2938.                 -- Добавление введенных данных в историю
  2939.                 if input.history[#input.history] ~= input.text and unicode.len(input.text) > 0 then
  2940.                     table.insert(input.history, input.text)
  2941.                 end
  2942.                 input.historyIndex = #input.history
  2943.             end
  2944.  
  2945.             inputStopInput(workspace, input)
  2946.  
  2947.             if input.onKeyDown then
  2948.                 input.onKeyDown(workspace, input, e1, e2, e3, e4, e5, e6, ...)
  2949.             end
  2950.  
  2951.             return
  2952.        
  2953.         -- Arrows up/down/left/right
  2954.         elseif e4 == 200 then
  2955.             if input.historyEnabled and #input.history > 0 then
  2956.                 -- Добавление уже введенного текста в историю при стрелке вверх
  2957.                 if input.historyIndex == #input.history + 1 and unicode.len(input.text) > 0 then
  2958.                     input.history[input.historyIndex] = input.text
  2959.                 end
  2960.  
  2961.                 input.historyIndex = input.historyIndex - 1
  2962.                 if input.historyIndex > #input.history then
  2963.                     input.historyIndex = #input.history
  2964.                 elseif input.historyIndex < 1 then
  2965.                     input.historyIndex = 1
  2966.                 end
  2967.  
  2968.                 input.text = input.history[input.historyIndex]
  2969.                 input:setCursorPosition(unicode.len(input.text) + 1)
  2970.             end
  2971.        
  2972.         elseif e4 == 208 then
  2973.             if input.historyEnabled and #input.history > 0 then
  2974.                 input.historyIndex = input.historyIndex + 1
  2975.                 if input.historyIndex > #input.history then
  2976.                     input.historyIndex = #input.history
  2977.                 elseif input.historyIndex < 1 then
  2978.                     input.historyIndex = 1
  2979.                 end
  2980.                
  2981.                 input.text = input.history[input.historyIndex]
  2982.                 input:setCursorPosition(unicode.len(input.text) + 1)
  2983.             end
  2984.        
  2985.         elseif e4 == 203 then
  2986.             input:setCursorPosition(input.cursorPosition - 1)
  2987.        
  2988.         elseif e4 == 205 then  
  2989.             input:setCursorPosition(input.cursorPosition + 1)
  2990.        
  2991.         -- Backspace
  2992.         elseif e4 == 14 then
  2993.             input.text = unicode.sub(unicode.sub(input.text, 1, input.cursorPosition - 1), 1, -2) .. unicode.sub(input.text, input.cursorPosition, -1)
  2994.             input:setCursorPosition(input.cursorPosition - 1)
  2995.        
  2996.         -- Delete
  2997.         elseif e4 == 211 then
  2998.             input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. unicode.sub(input.text, input.cursorPosition + 1, -1)
  2999.        
  3000.         -- Home
  3001.         elseif e4 == 199 then
  3002.             input:setCursorPosition(1)
  3003.        
  3004.         -- End
  3005.         elseif e4 == 207 then
  3006.             input:setCursorPosition(unicode.len(input.text) + 1)
  3007.        
  3008.         else
  3009.             local char = unicode.char(e3)
  3010.            
  3011.             if not keyboard.isControl(e3) then
  3012.                 input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. char .. unicode.sub(input.text, input.cursorPosition, -1)
  3013.                 input:setCursorPosition(input.cursorPosition + 1)
  3014.             end
  3015.         end
  3016.  
  3017.         if input.onKeyDown then
  3018.             input.onKeyDown(workspace, input, e1, e2, e3, e4, e5, e6, ...)
  3019.         end
  3020.  
  3021.         inputCursorBlink(workspace, input, true)
  3022.    
  3023.     elseif e1 == "clipboard" and input.focused then
  3024.         input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. e3 .. unicode.sub(input.text, input.cursorPosition, -1)
  3025.         input:setCursorPosition(input.cursorPosition + unicode.len(e3))
  3026.        
  3027.         inputCursorBlink(workspace, input, true)
  3028.         workspace:consumeEvent()
  3029.    
  3030.     elseif not e1 and input.focused and computer.uptime() - input.cursorBlinkUptime > input.cursorBlinkDelay then
  3031.         inputCursorBlink(workspace, input, not input.cursorBlinkState)
  3032.     end
  3033. end
  3034.  
  3035. function GUI.input(x, y, width, height, backgroundColor, textColor, placeholderTextColor, backgroundFocusedColor, textFocusedColor, text, placeholderText, eraseTextOnFocus, textMask)
  3036.     local input = GUI.object(x, y, width, height)
  3037.    
  3038.     input.colors = {
  3039.         default = {
  3040.             background = backgroundColor,
  3041.             text = textColor
  3042.         },
  3043.         focused = {
  3044.             background = backgroundFocusedColor,
  3045.             text = textFocusedColor
  3046.         },
  3047.         placeholderText = placeholderTextColor,
  3048.         cursor = 0x00A8FF
  3049.     }
  3050.  
  3051.     input.text = text or ""
  3052.     input.placeholderText = placeholderText
  3053.     input.eraseTextOnFocus = eraseTextOnFocus
  3054.     input.textMask = textMask
  3055.  
  3056.     input.textOffset = 1
  3057.     input.textCutFrom = 1
  3058.     input.cursorPosition = 1
  3059.     input.cursorSymbol = "┃"
  3060.     input.cursorBlinkDelay = 0.4
  3061.     input.cursorBlinkState = false
  3062.     input.textMask = textMask
  3063.     input.setCursorPosition = inputSetCursorPosition
  3064.  
  3065.     input.history = {}
  3066.     input.historyLimit = 20
  3067.     input.historyIndex = 0
  3068.     input.historyEnabled = false
  3069.  
  3070.     input.stopInputObject = GUI.object(1, 1, 1, 1)
  3071.     input.stopInputObject.eventHandler = function(workspace, object, e1, e2, e3, e4, ...)
  3072.         if e1 == "touch" or e1 == "drop" then
  3073.             if input:isPointInside(math.ceil(e3), math.ceil(e4)) then
  3074.                 input.eventHandler(workspace, input, e1, e2, e3, e4, ...)
  3075.             else
  3076.                 inputStopInput(workspace, input)
  3077.             end
  3078.         end
  3079.     end
  3080.  
  3081.     input.textDrawMethod = inputTextDrawMethod
  3082.     input.draw = inputDraw
  3083.     input.eventHandler = inputEventHandler
  3084.     input.startInput = inputStartInput
  3085.  
  3086.     return input
  3087. end
  3088.  
  3089. --------------------------------------------------------------------------------
  3090.  
  3091. local function autoCompleteDraw(object)
  3092.     local y, yEnd = object.y, object.y + object.height - 1
  3093.  
  3094.     screen.drawRectangle(object.x, object.y, object.width, object.height, object.colors.default.background, object.colors.default.text, " ")
  3095.  
  3096.     for i = object.fromItem, object.itemCount do
  3097.         local textColor, textMatchColor = object.colors.default.text, object.colors.default.textMatch
  3098.         if i == object.selectedItem then
  3099.             screen.drawRectangle(object.x, y, object.width, 1, object.colors.selected.background, object.colors.selected.text, " ")
  3100.             textColor, textMatchColor = object.colors.selected.text, object.colors.selected.textMatch
  3101.         end
  3102.  
  3103.         screen.drawText(object.x + 1, y, textMatchColor, unicode.sub(object.matchText, 1, object.width - 2))
  3104.         screen.drawText(object.x + object.matchTextLength + 1, y, textColor, unicode.sub(object.items[i], object.matchTextLength + 1, object.matchTextLength + object.width - object.matchTextLength - 2))
  3105.  
  3106.         y = y + 1
  3107.        
  3108.         if y > yEnd then
  3109.             break
  3110.         end
  3111.     end
  3112.  
  3113.     if object.itemCount > object.height then
  3114.         object.scrollBar.x = object.x + object.width - 1
  3115.         object.scrollBar.y = object.y
  3116.         object.scrollBar.height = object.height
  3117.         object.scrollBar.maximumValue = object.itemCount - object.height + 2
  3118.         object.scrollBar.value = object.fromItem
  3119.         object.scrollBar.shownValueCount = object.height
  3120.  
  3121.         object.scrollBar:draw()
  3122.     end
  3123. end
  3124.  
  3125. local function autoCompleteScroll(workspace, object, direction)
  3126.     if object.itemCount >= object.height then
  3127.         object.fromItem = object.fromItem + direction
  3128.         if object.fromItem < 1 then
  3129.             object.fromItem = 1
  3130.         elseif object.fromItem > object.itemCount - object.height + 1 then
  3131.             object.fromItem = object.itemCount - object.height + 1
  3132.         end
  3133.     end
  3134. end
  3135.  
  3136. local function autoCompleteEventHandler(workspace, object, e1, e2, e3, e4, e5, ...)
  3137.     if e1 == "touch" then
  3138.         object.selectedItem = math.ceil(e4) - object.y + object.fromItem
  3139.         workspace:draw()
  3140.  
  3141.         if object.onItemSelected then
  3142.             event.sleep(0.2)
  3143.             object.onItemSelected(workspace, object, e1, e2, e3, e4, e5, ...)
  3144.         end
  3145.    
  3146.     elseif e1 == "scroll" then
  3147.         autoCompleteScroll(workspace, object, -e5)
  3148.         workspace:draw()
  3149.    
  3150.     elseif e1 == "key_down" then
  3151.         if e4 == 28 then
  3152.             if object.onItemSelected then
  3153.                 object.onItemSelected(workspace, object, e1, e2, e3, e4, e5, ...)
  3154.             end
  3155.        
  3156.         elseif e4 == 200 then
  3157.             object.selectedItem = object.selectedItem - 1
  3158.             if object.selectedItem < 1 then
  3159.                 object.selectedItem = 1
  3160.             end
  3161.  
  3162.             if object.selectedItem == object.fromItem - 1 then
  3163.                 autoCompleteScroll(workspace, object, -1)
  3164.             end
  3165.  
  3166.             workspace:draw()
  3167.        
  3168.         elseif e4 == 208 then
  3169.             object.selectedItem = object.selectedItem + 1
  3170.             if object.selectedItem > object.itemCount then
  3171.                 object.selectedItem = object.itemCount
  3172.             end
  3173.  
  3174.             if object.selectedItem == object.fromItem + object.height then
  3175.                 autoCompleteScroll(workspace, object, 1)
  3176.             end
  3177.            
  3178.             workspace:draw()
  3179.         end
  3180.     end
  3181. end
  3182.  
  3183. local function autoCompleteClear(object)
  3184.     object.items = {}
  3185.     object.itemCount = 0
  3186.     object.fromItem = 1
  3187.     object.selectedItem = 1
  3188.     object.height = 0
  3189. end
  3190.  
  3191. local function autoCompleteMatch(object, variants, text, asKey)
  3192.     object:clear()
  3193.    
  3194.     if asKey then
  3195.         if text then
  3196.             for key in pairs(variants) do
  3197.                 if key ~= text and key:match("^" .. text) then
  3198.                     table.insert(object.items,key)
  3199.                 end
  3200.             end
  3201.         else
  3202.             for key in pairs(variants) do
  3203.                 table.insert(object.items, key)
  3204.             end
  3205.         end
  3206.     else
  3207.         if text then
  3208.             for i = 1, #variants do
  3209.                 if variants[i] ~= text and variants[i]:match("^" .. text) then
  3210.                     table.insert(object.items, variants[i])
  3211.                 end
  3212.             end
  3213.         else
  3214.             for i = 1, #variants do
  3215.                 table.insert(object.items, variants[i])
  3216.             end
  3217.         end
  3218.     end
  3219.  
  3220.     object.matchText = text or ""
  3221.     object.matchTextLength = unicode.len(object.matchText)
  3222.  
  3223.     table.sort(object.items, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
  3224.  
  3225.     object.itemCount = #object.items
  3226.     object.height = math.min(object.itemCount, object.maximumHeight)
  3227.  
  3228.     return object
  3229. end
  3230.  
  3231. function GUI.autoComplete(x, y, width, maximumHeight, backgroundColor, textColor, textMatchColor, backgroundSelectedColor, textSelectedColor, textMatchSelectedColor, scrollBarBackground, scrollBarForeground)
  3232.     local object = GUI.object(x, y, width, maximumHeight)
  3233.  
  3234.     object.colors = {
  3235.         default = {
  3236.             background = backgroundColor,
  3237.             text = textColor,
  3238.             textMatch = textMatchColor 
  3239.         },
  3240.         selected = {
  3241.             background = backgroundSelectedColor,
  3242.             text = textSelectedColor,
  3243.             textMatch = textMatchSelectedColor
  3244.         }
  3245.     }
  3246.  
  3247.     object.maximumHeight = maximumHeight
  3248.     object.fromItem = 1
  3249.     object.selectedItem = 1
  3250.     object.items = {}
  3251.     object.matchText = " "
  3252.     object.matchTextLength = 1
  3253.     object.itemCount = 0
  3254.  
  3255.     object.scrollBar = GUI.scrollBar(1, 1, 1, 1, scrollBarBackground, scrollBarForeground, 1, 1, 1, 1, 1, true)
  3256.  
  3257.     object.match = autoCompleteMatch
  3258.     object.draw = autoCompleteDraw
  3259.     object.eventHandler = autoCompleteEventHandler
  3260.     object.clear = autoCompleteClear
  3261.  
  3262.     object:clear()
  3263.  
  3264.     return object
  3265. end
  3266.  
  3267. --------------------------------------------------------------------------------
  3268.  
  3269. local function brailleCanvasDraw(brailleCanvas)
  3270.     local index, background, foreground, symbol
  3271.     for y = 1, brailleCanvas.height do
  3272.         for x = 1, brailleCanvas.width do
  3273.             index = screen.getIndex(brailleCanvas.x + x - 1, brailleCanvas.y + y - 1)
  3274.             background, foreground, symbol = screen.rawGet(index)
  3275.             screen.rawSet(index, background, brailleCanvas.pixels[y][x][9], brailleCanvas.pixels[y][x][10])
  3276.         end
  3277.     end
  3278.  
  3279.     return brailleCanvas
  3280. end
  3281.  
  3282. local function brailleCanvasSet(brailleCanvas, x, y, state, color)
  3283.     local xReal, yReal = math.ceil(x / 2), math.ceil(y / 4)
  3284.    
  3285.     brailleCanvas.pixels[yReal][xReal][(y - (yReal - 1) * 4 - 1) * 2 + x - (xReal - 1) * 2] = state and 1 or 0
  3286.     brailleCanvas.pixels[yReal][xReal][9] = color or brailleCanvas.pixels[yReal][xReal][9]
  3287.     brailleCanvas.pixels[yReal][xReal][10] = unicode.char(
  3288.         10240 +
  3289.         128 * brailleCanvas.pixels[yReal][xReal][8] +
  3290.         64 * brailleCanvas.pixels[yReal][xReal][7] +
  3291.         32 * brailleCanvas.pixels[yReal][xReal][6] +
  3292.         16 * brailleCanvas.pixels[yReal][xReal][4] +
  3293.         8 * brailleCanvas.pixels[yReal][xReal][2] +
  3294.         4 * brailleCanvas.pixels[yReal][xReal][5] +
  3295.         2 * brailleCanvas.pixels[yReal][xReal][3] +
  3296.         brailleCanvas.pixels[yReal][xReal][1]
  3297.     )
  3298.  
  3299.     return brailleCanvas
  3300. end
  3301.  
  3302. local function brailleCanvasGet(brailleCanvas, x, y)
  3303.     local xReal, yReal = math.ceil(x / 2), math.ceil(y / 4)
  3304.     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]
  3305. end
  3306.  
  3307. local function brailleCanvasFill(brailleCanvas, x, y, width, height, state, color)
  3308.     for j = y, y + height - 1 do
  3309.         for i = x, x + width - 1 do
  3310.             brailleCanvas:set(i, j, state, color)
  3311.         end
  3312.     end
  3313. end
  3314.  
  3315. local function brailleCanvasClear(brailleCanvas)
  3316.     for j = 1, brailleCanvas.height * 4 do
  3317.         brailleCanvas.pixels[j] = {}
  3318.         for i = 1, brailleCanvas.width * 2 do
  3319.             brailleCanvas.pixels[j][i] = { 0, 0, 0, 0, 0, 0, 0, 0, 0x0, " " }
  3320.         end
  3321.     end
  3322. end
  3323.  
  3324. function GUI.brailleCanvas(x, y, width, height)
  3325.     local brailleCanvas = GUI.object(x, y, width, height)
  3326.    
  3327.     brailleCanvas.pixels = {}
  3328.  
  3329.     brailleCanvas.get = brailleCanvasGet
  3330.     brailleCanvas.set = brailleCanvasSet
  3331.     brailleCanvas.fill = brailleCanvasFill
  3332.     brailleCanvas.clear = brailleCanvasClear
  3333.  
  3334.     brailleCanvas.draw = brailleCanvasDraw
  3335.  
  3336.     brailleCanvas:clear()
  3337.  
  3338.     return brailleCanvas
  3339. end
  3340.  
  3341. --------------------------------------------------------------------------------
  3342.  
  3343. local function paletteShow(palette)
  3344.     local workspace = GUI.workspace()
  3345.    
  3346.     workspace:addChild(palette)
  3347.  
  3348.     palette.submitButton.onTouch = function()
  3349.         workspace:stop()
  3350.     end
  3351.     palette.cancelButton.onTouch = palette.submitButton.onTouch
  3352.  
  3353.     workspace:draw()
  3354.     workspace:start()  
  3355.  
  3356.     return palette.color.integer
  3357. end
  3358.  
  3359. function GUI.palette(x, y, startColor)
  3360.     local palette = GUI.window(x, y, 71, 25)
  3361.    
  3362.     palette.color = {hsb = {}, rgb = {}}
  3363.     palette:addChild(GUI.panel(1, 1, palette.width, palette.height, 0xF0F0F0))
  3364.    
  3365.     local bigImage = palette:addChild(GUI.image(1, 1, image.create(50, 25)))
  3366.     local bigCrest = palette:addChild(GUI.object(1, 1, 5, 3))
  3367.  
  3368.     local function paletteDrawBigCrestPixel(x, y, symbol)
  3369.         local background, foreground = screen.get(x, y)
  3370.         local r, g, b = color.integerToRGB(background)
  3371.         screen.set(x, y, background, (r + g + b) / 3 >= 127 and 0x0 or 0xFFFFFF, symbol)
  3372.     end
  3373.  
  3374.     bigCrest.draw = function(object)
  3375.         paletteDrawBigCrestPixel(object.x, object.y + 1, "─")
  3376.         paletteDrawBigCrestPixel(object.x + 1, object.y + 1, "─")
  3377.         paletteDrawBigCrestPixel(object.x + 3, object.y + 1, "─")
  3378.         paletteDrawBigCrestPixel(object.x + 4, object.y + 1, "─")
  3379.         paletteDrawBigCrestPixel(object.x + 2, object.y, "│")
  3380.         paletteDrawBigCrestPixel(object.x + 2, object.y + 2, "│")
  3381.     end
  3382.    
  3383.     bigCrest.passScreenEvents = true
  3384.    
  3385.     local miniImage = palette:addChild(GUI.image(53, 1, image.create(3, 25)))
  3386.    
  3387.     local miniCrest = palette:addChild(GUI.object(52, 1, 5, 1))
  3388.    
  3389.     miniCrest.draw = function(object)
  3390.         screen.drawText(object.x, object.y, 0x0, ">")
  3391.         screen.drawText(object.x + 4, object.y, 0x0, "<")
  3392.     end
  3393.  
  3394.     local colorPanel = palette:addChild(GUI.panel(58, 2, 12, 3, 0x0))
  3395.     palette.submitButton = palette:addChild(GUI.roundedButton(58, 6, 12, 1, 0x4B4B4B, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "OK"))
  3396.     palette.cancelButton = palette:addChild(GUI.roundedButton(58, 8, 12, 1, 0xFFFFFF, 0x696969, 0x2D2D2D, 0xFFFFFF, "Cancel"))
  3397.  
  3398.     local function paletteRefreshBigImage()
  3399.         local saturationStep, brightnessStep, saturation, brightness = 1 / bigImage.width, 1 / bigImage.height, 0, 1
  3400.        
  3401.         for j = 1, bigImage.height do
  3402.             for i = 1, bigImage.width do
  3403.                 image.set(bigImage.image, i, j, color.optimize(color.HSBToInteger(palette.color.hsb.hue, saturation, brightness)), 0x0, 0x0, " ")
  3404.                 saturation = saturation + saturationStep
  3405.             end
  3406.            
  3407.             saturation, brightness = 0, brightness - brightnessStep
  3408.         end
  3409.     end
  3410.  
  3411.     local function paletteRefreshMiniImage()
  3412.         local hueStep, hue = 360 / miniImage.height, 0
  3413.        
  3414.         for j = 1, miniImage.height do
  3415.             for i = 1, miniImage.width do
  3416.                 image.set(miniImage.image, i, j, color.optimize(color.HSBToInteger(hue, 1, 1)), 0x0, 0, " ")
  3417.             end
  3418.            
  3419.             hue = hue + hueStep
  3420.         end
  3421.     end
  3422.  
  3423.     local function paletteUpdateCrestsCoordinates()
  3424.         bigCrest.localX = math.floor((bigImage.width - 1) * palette.color.hsb.saturation) - 1
  3425.         bigCrest.localY = math.floor((bigImage.height - 1) - (bigImage.height - 1) * palette.color.hsb.brightness)
  3426.         miniCrest.localY = math.ceil(palette.color.hsb.hue / 360 * miniImage.height + 0.5)
  3427.     end
  3428.  
  3429.     local inputs
  3430.  
  3431.     local function paletteUpdateInputs()
  3432.         inputs[1].text = tostring(palette.color.rgb.red)
  3433.         inputs[2].text = tostring(palette.color.rgb.green)
  3434.         inputs[3].text = tostring(palette.color.rgb.blue)
  3435.         inputs[4].text = tostring(math.floor(palette.color.hsb.hue))
  3436.         inputs[5].text = tostring(math.floor(palette.color.hsb.saturation * 100))
  3437.         inputs[6].text = tostring(math.floor(palette.color.hsb.brightness * 100))
  3438.         inputs[7].text = string.format("%06X", palette.color.integer)
  3439.         colorPanel.colors.background = palette.color.integer
  3440.     end
  3441.  
  3442.     local function paletteSwitchColorFromHex(hex)
  3443.         palette.color.integer = hex
  3444.         palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = color.integerToRGB(hex)
  3445.         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)
  3446.         paletteUpdateInputs()
  3447.     end
  3448.  
  3449.     local function paletteSwitchColorFromHsb(hue, saturation, brightness)
  3450.         palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = hue, saturation, brightness
  3451.         palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = color.HSBToRGB(hue, saturation, brightness)
  3452.         palette.color.integer = color.RGBToInteger(palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue)
  3453.         paletteUpdateInputs()
  3454.     end
  3455.  
  3456.     local function paletteSwitchColorFromRgb(red, green, blue)
  3457.         palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = red, green, blue
  3458.         palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = color.RGBToHSB(red, green, blue)
  3459.         palette.color.integer = color.RGBToInteger(red, green, blue)
  3460.         paletteUpdateInputs()
  3461.     end
  3462.  
  3463.     local function onAnyInputFinished()
  3464.         paletteRefreshBigImage()
  3465.         paletteUpdateCrestsCoordinates()
  3466.         palette.firstParent:draw()
  3467.     end
  3468.  
  3469.     local function onHexInputFinished()
  3470.         paletteSwitchColorFromHex(tonumber("0x" .. inputs[7].text))
  3471.         onAnyInputFinished()
  3472.     end
  3473.  
  3474.     local function onRgbInputFinished()
  3475.         paletteSwitchColorFromRgb(tonumber(inputs[1].text), tonumber(inputs[2].text), tonumber(inputs[3].text))
  3476.         onAnyInputFinished()
  3477.     end
  3478.  
  3479.     local function onHsbInputFinished()
  3480.         paletteSwitchColorFromHsb(tonumber(inputs[4].text), tonumber(inputs[5].text) / 100, tonumber(inputs[6].text) / 100)
  3481.         onAnyInputFinished()
  3482.     end
  3483.  
  3484.     local function rgbValidaror(text)
  3485.         local number = tonumber(text) if number and number >= 0 and number <= 255 then return true end
  3486.     end
  3487.  
  3488.     local function hValidator(text)
  3489.         local number = tonumber(text) if number and number >= 0 and number <= 359 then return true end
  3490.     end
  3491.  
  3492.     local function sbValidator(text)
  3493.         local number = tonumber(text) if number and number >= 0 and number <= 100 then return true end
  3494.     end
  3495.  
  3496.     local function hexValidator(text)
  3497.         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
  3498.             return true
  3499.         end
  3500.     end
  3501.  
  3502.     inputs = {
  3503.         { shortcut = "R:", validator = rgbValidaror, onInputFinished = onRgbInputFinished },
  3504.         { shortcut = "G:", validator = rgbValidaror, onInputFinished = onRgbInputFinished },
  3505.         { shortcut = "B:", validator = rgbValidaror, onInputFinished = onRgbInputFinished },
  3506.         { shortcut = "H:", validator = hValidator,   onInputFinished = onHsbInputFinished },
  3507.         { shortcut = "S:", validator = sbValidator,  onInputFinished = onHsbInputFinished },
  3508.         { shortcut = "L:", validator = sbValidator,  onInputFinished = onHsbInputFinished },
  3509.         { shortcut = "0x", validator = hexValidator, onInputFinished = onHexInputFinished }
  3510.     }
  3511.  
  3512.     local y = 10
  3513.     for i = 1, #inputs do
  3514.         palette:addChild(GUI.label(58, y, 2, 1, 0x0, inputs[i].shortcut))
  3515.        
  3516.         local validator, onInputFinished = inputs[i].validator, inputs[i].onInputFinished
  3517.         inputs[i] = palette:addChild(GUI.input(61, y, 9, 1, 0xFFFFFF, 0x696969, 0x696969, 0xFFFFFF, 0x0, "", "", true))
  3518.         inputs[i].validator = validator
  3519.         inputs[i].onInputFinished = onInputFinished
  3520.        
  3521.         y = y + 2
  3522.     end
  3523.    
  3524.     local paletteConfigPath = paths.user.applicationData .. "GUI/Palette.cfg"
  3525.    
  3526.     local favourites
  3527.     if filesystem.exists(paletteConfigPath) then
  3528.         favourites = filesystem.readTable(paletteConfigPath)
  3529.     else
  3530.         favourites = {}
  3531.         for i = 1, 6 do favourites[i] = color.HSBToInteger(math.random(0, 360), 1, 1) end
  3532.         filesystem.writeTable(paletteConfigPath, favourites)
  3533.     end
  3534.  
  3535.     local favouritesContainer = palette:addChild(GUI.container(58, 24, 12, 1))
  3536.     for i = 1, #favourites do
  3537.         favouritesContainer:addChild(GUI.button(i * 2 - 1, 1, 2, 1, favourites[i], 0x0, 0x0, 0x0, " ")).onTouch = function(workspace)
  3538.             paletteSwitchColorFromHex(favourites[i])
  3539.             paletteRefreshBigImage()
  3540.             paletteUpdateCrestsCoordinates()
  3541.             workspace:draw()
  3542.         end
  3543.     end
  3544.    
  3545.     palette:addChild(GUI.button(58, 25, 12, 1, 0xFFFFFF, 0x4B4B4B, 0x2D2D2D, 0xFFFFFF, "+")).onTouch = function(workspace)
  3546.         local favouriteExists = false
  3547.         for i = 1, #favourites do
  3548.             if favourites[i] == palette.color.integer then
  3549.                 favouriteExists = true
  3550.                 break
  3551.             end
  3552.         end
  3553.        
  3554.         if not favouriteExists then
  3555.             table.insert(favourites, 1, palette.color.integer)
  3556.             table.remove(favourites, #favourites)
  3557.             for i = 1, #favourites do
  3558.                 favouritesContainer.children[i].colors.default.background = favourites[i]
  3559.                 favouritesContainer.children[i].colors.pressed.background = 0x0
  3560.             end
  3561.            
  3562.             filesystem.writeTable(paletteConfigPath, favourites)
  3563.  
  3564.             workspace:draw()
  3565.         end
  3566.     end
  3567.  
  3568.     bigImage.eventHandler = function(workspace, object, e1, e2, e3, e4)
  3569.         if e1 == "touch" or e1 == "drag" then
  3570.             e3, e4 = math.ceil(e3), math.ceil(e4)
  3571.  
  3572.             bigCrest.localX, bigCrest.localY = e3 - palette.x - 1, e4 - palette.y
  3573.             paletteSwitchColorFromHex(select(3, component.invoke(screen.getGPUAddress(), "get", e3, e4)))
  3574.            
  3575.             workspace:draw()
  3576.         end
  3577.     end
  3578.    
  3579.     miniImage.eventHandler = function(workspace, object, e1, e2, e3, e4)
  3580.         if e1 == "touch" or e1 == "drag" then
  3581.             e4 = math.ceil(e4)
  3582.  
  3583.             miniCrest.localY = e4 - palette.y + 1
  3584.             paletteSwitchColorFromHsb((e4 - miniImage.y) * 360 / miniImage.height, palette.color.hsb.saturation, palette.color.hsb.brightness)
  3585.             paletteRefreshBigImage()
  3586.            
  3587.             workspace:draw()
  3588.         end
  3589.     end
  3590.  
  3591.     palette.show = paletteShow
  3592.  
  3593.     paletteSwitchColorFromHex(startColor)
  3594.     paletteUpdateCrestsCoordinates()
  3595.     paletteRefreshBigImage()
  3596.     paletteRefreshMiniImage()
  3597.  
  3598.     return palette
  3599. end
  3600.  
  3601. --------------------------------------------------------------------------------
  3602.  
  3603. local function textUpdate(object)
  3604.     object.width = unicode.len(object.text)
  3605.    
  3606.     return object
  3607. end
  3608.  
  3609. local function textDraw(object)
  3610.     object:update()
  3611.     screen.drawText(object.x, object.y, object.color, object.text, object.transparency)
  3612.  
  3613.     return object
  3614. end
  3615.  
  3616. function GUI.text(x, y, color, text, transparency)
  3617.     local object = GUI.object(x, y, 1, 1)
  3618.  
  3619.     object.text = text
  3620.     object.color = color
  3621.     object.transparency = transparency
  3622.     object.update = textUpdate
  3623.     object.draw = textDraw
  3624.     object:update()
  3625.  
  3626.     return object
  3627. end
  3628.  
  3629. --------------------------------------------------------------------------------
  3630.  
  3631. function GUI.addBackgroundContainer(parentContainer, addPanel, addLayout, title)
  3632.     local container = parentContainer:addChild(GUI.container(1, 1, parentContainer.width, parentContainer.height))
  3633.    
  3634.     if addPanel then
  3635.         container.panel = container:addChild(GUI.panel(1, 1, container.width, container.height, GUI.BACKGROUND_CONTAINER_PANEL_COLOR, GUI.BACKGROUND_CONTAINER_PANEL_TRANSPARENCY))
  3636.         container.panel.eventHandler = function(parentContainer, object, e1)
  3637.             if e1 == "touch" then
  3638.                 container:remove()
  3639.                 parentContainer:draw()
  3640.             end
  3641.         end
  3642.     end
  3643.  
  3644.     if addLayout then
  3645.         container.layout = container:addChild(GUI.layout(1, 1, container.width, container.height, 1, 1))
  3646.  
  3647.         if title then
  3648.             container.label = container.layout:addChild(GUI.label(1, 1, 1, 1, GUI.BACKGROUND_CONTAINER_TITLE_COLOR, title)):setAlignment(GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP)
  3649.         end
  3650.     end
  3651.  
  3652.     return container
  3653. end
  3654.  
  3655. --------------------------------------------------------------------------------
  3656.  
  3657. local function listUpdate(list)
  3658.     local step, child = false
  3659.  
  3660.     for i = 1, #list.children do
  3661.         child = list.children[i]
  3662.         -- Жмяканье пизды
  3663.         child.pressed = i == list.selectedItem
  3664.        
  3665.         -- Цвет залупы
  3666.         if step then
  3667.             child.colors.default = list.colors.alternative
  3668.         else
  3669.             child.colors.default = list.colors.default
  3670.         end
  3671.  
  3672.         child.colors.pressed, step = list.colors.selected, not step
  3673.        
  3674.         -- Размеры хуйни
  3675.         if list.cells[1][1].direction == GUI.DIRECTION_HORIZONTAL then
  3676.             if list.offsetMode then
  3677.                 child.width, child.height = list.itemSize * 2 + unicode.len(child.text), list.height
  3678.             else
  3679.                 child.width, child.height = list.itemSize, list.height
  3680.             end
  3681.         else
  3682.             if list.offsetMode then
  3683.                 child.width, child.height = list.width, list.itemSize * 2 + 1
  3684.             else
  3685.                 child.width, child.height = list.width, list.itemSize
  3686.             end
  3687.         end
  3688.     end
  3689.  
  3690.     layoutUpdate(list)
  3691. end
  3692.  
  3693. local function listItemEventHandler(workspace, item, e1, ...)
  3694.     if e1 == "touch" or e1 == "drag" then
  3695.         item.parent.selectedItem = item:indexOf()
  3696.         item.parent:update()
  3697.         workspace:draw()
  3698.  
  3699.         if item.onTouch then
  3700.             item.onTouch(workspace, item, e1, ...)
  3701.         end
  3702.     end
  3703. end
  3704.  
  3705. local function listAddItem(list, text)
  3706.     local item = list:addChild(pressable(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, text))
  3707.    
  3708.     item.switchMode = true
  3709.     item.eventHandler = listItemEventHandler
  3710.  
  3711.     return item
  3712. end
  3713.  
  3714. local function listSetAlignment(list, ...)
  3715.     layoutSetAlignment(list, 1, 1, ...)
  3716.     return list
  3717. end
  3718.  
  3719. local function listSetSpacing(list, ...)
  3720.     layoutSetSpacing(list, 1, 1, ...)
  3721.     return list
  3722. end
  3723.  
  3724. local function listSetDirection(list, ...)
  3725.     layoutSetDirection(list, 1, 1, ...)
  3726.     return list
  3727. end
  3728.  
  3729. local function listSetFitting(list, ...)
  3730.     layoutSetFitting(list, 1, 1, ...)
  3731.     return list
  3732. end
  3733.  
  3734. local function listSetMargin(list, ...)
  3735.     layoutSetMargin(list, 1, 1, ...)
  3736.     return list
  3737. end
  3738.  
  3739. local function listGetMargin(list, ...)
  3740.     return layoutGetMargin(list, 1, 1, ...)
  3741. end
  3742.  
  3743. local function listGetItem(list, what)
  3744.     if type(what) == "number" then
  3745.         return list.children[what]
  3746.     else
  3747.         for i = 1, #list.children do
  3748.             if list.children[i].text == what then
  3749.                 return list.children[i], i
  3750.             end
  3751.         end
  3752.     end
  3753. end
  3754.  
  3755. local function listCount(list)
  3756.     return #list.children
  3757. end
  3758.  
  3759. local function listDraw(list)
  3760.     if list.colors.default.background then
  3761.         screen.drawRectangle(list.x, list.y, list.width, list.height, list.colors.default.background, list.colors.default.text, " ")
  3762.     end
  3763.  
  3764.     layoutDraw(list)
  3765. end
  3766.  
  3767. function GUI.list(x, y, width, height, itemSize, spacing, backgroundColor, textColor, backgroundAlternatingColor, textAlternatingColor, backgroundSelectedColor, textSelectedColor, offsetMode)
  3768.     local list = GUI.layout(x, y, width, height, 1, 1)
  3769.  
  3770.     list.colors = {
  3771.         default = {
  3772.             background = backgroundColor,
  3773.             text = textColor
  3774.         },
  3775.         alternative = {
  3776.             background = backgroundAlternatingColor,
  3777.             text = textAlternatingColor
  3778.         },
  3779.         selected = {
  3780.             background = backgroundSelectedColor,
  3781.             text = textSelectedColor
  3782.         },
  3783.     }
  3784.  
  3785.     list.passScreenEvents = false
  3786.     list.selectedItem = 1
  3787.     list.offsetMode = offsetMode
  3788.     list.itemSize = itemSize
  3789.    
  3790.     list.addItem = listAddItem
  3791.     list.getItem = listGetItem
  3792.     list.count = listCount
  3793.     list.setAlignment = listSetAlignment
  3794.     list.setSpacing = listSetSpacing
  3795.     list.setDirection = listSetDirection
  3796.     list.setFitting = listSetFitting
  3797.     list.setMargin = listSetMargin
  3798.     list.getMargin = listGetMargin
  3799.     list.update = listUpdate
  3800.     list.draw = listDraw
  3801.  
  3802.     list:setAlignment(GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP)
  3803.     list:setSpacing(spacing)
  3804.     list:setDirection(GUI.DIRECTION_VERTICAL)
  3805.  
  3806.     return list
  3807. end
  3808.  
  3809. ---------------------------------------------------------------------------------------------------
  3810.  
  3811. local function keyAndValueUpdate(object)
  3812.     object.keyLength, object.valueLength = unicode.len(object.key), unicode.len(object.value)
  3813.     object.width = object.keyLength + object.valueLength
  3814. end
  3815.  
  3816. local function keyAndValueDraw(object)
  3817.     keyAndValueUpdate(object)
  3818.     screen.drawText(object.x, object.y, object.colors.key, object.key)
  3819.     screen.drawText(object.x + object.keyLength, object.y, object.colors.value, object.value)
  3820. end
  3821.  
  3822. function GUI.keyAndValue(x, y, keyColor, valueColor, key, value)
  3823.     local object = GUI.object(x, y, 1, 1)
  3824.    
  3825.     object.colors = {
  3826.         key = keyColor,
  3827.         value = valueColor
  3828.     }
  3829.     object.key = key
  3830.     object.value = value
  3831.  
  3832.     object.update = keyAndValueUpdate
  3833.     object.draw = keyAndValueDraw
  3834.  
  3835.     object:update()
  3836.  
  3837.     return object
  3838. end
  3839.  
  3840. ---------------------------------------------------------------------------------------------------
  3841.  
  3842. function GUI.highlightString(x, y, fromChar, indentationWidth, patterns, colorScheme, s)   
  3843.     local stringLength, x1, y1, x2, y2 = unicode.len(s), screen.getDrawLimit()
  3844.  
  3845.     fromChar = fromChar or 1
  3846.     if x < x1 then
  3847.         fromChar = fromChar + x1 - x
  3848.         x = x1
  3849.     end
  3850.  
  3851.     -- local toChar, endX = stringLength, x + stringLength - 1
  3852.     -- if endX > x2 then
  3853.     --  toChar = toChar - endX + x2
  3854.     -- end
  3855.     local toChar = fromChar + x2 - x
  3856.     if toChar > stringLength then
  3857.         toChar = stringLength
  3858.     end
  3859.  
  3860.     local counter, symbols, colors, bufferIndex, newFrameBackgrounds, newFrameForegrounds, newFrameSymbols, searchFrom, starting, ending = indentationWidth, {}, {}, screen.getIndex(x, y), screen.getNewFrameTables()
  3861.  
  3862.     -- Пидорасим на символы
  3863.     for i = fromChar, toChar do
  3864.         symbols[i] = unicode.sub(s, i, i)
  3865.     end
  3866.  
  3867.     -- Вгоняем в цветовую карту синтаксическую подсветку
  3868.     for j = 1, #patterns, 4 do
  3869.         searchFrom = 1
  3870.        
  3871.         while true do
  3872.             starting, ending = text.unicodeFind(s, patterns[j], searchFrom)
  3873.            
  3874.             if starting then
  3875.                 for i = starting + patterns[j + 2], ending - patterns[j + 3] do
  3876.                     colors[i] = colorScheme[patterns[j + 1]]
  3877.                 end
  3878.             else
  3879.                 break
  3880.             end
  3881.  
  3882.             searchFrom = ending + 1 - patterns[j + 3]
  3883.         end
  3884.     end
  3885.  
  3886.     -- Ебошим индентейшны
  3887.     for i = fromChar, toChar do
  3888.         if symbols[i] == " " then
  3889.             colors[i] = colorScheme.indentation
  3890.            
  3891.             if counter == indentationWidth then
  3892.                 symbols[i], counter = "│", 0
  3893.             end
  3894.  
  3895.             counter = counter + 1
  3896.         else
  3897.             break
  3898.         end
  3899.     end
  3900.  
  3901.     -- Рисуем текст
  3902.     for i = fromChar, toChar do
  3903.         newFrameForegrounds[bufferIndex], newFrameSymbols[bufferIndex] = colors[i] or colorScheme.text, symbols[i] or " "
  3904.         bufferIndex = bufferIndex + 1
  3905.     end
  3906. end
  3907.  
  3908. --------------------------------------------------------------------------------
  3909.  
  3910. local function dropDownMenuItemDraw(item)
  3911.     local yText = item.y + math.floor(item.height / 2)
  3912.  
  3913.     if item.type == 1 then
  3914.         local textColor = item.color or item.parent.parent.colors.default.text
  3915.  
  3916.         if item.pressed then
  3917.             textColor = item.parent.parent.colors.selected.text
  3918.             screen.drawRectangle(item.x, item.y, item.width, item.height, item.parent.parent.colors.selected.background, textColor, " ")
  3919.         elseif item.disabled then
  3920.             textColor = item.parent.parent.colors.disabled.text
  3921.         end
  3922.  
  3923.         screen.drawText(item.x + 1, yText, textColor, item.text)
  3924.         if item.shortcut then
  3925.             screen.drawText(item.x + item.width - unicode.len(item.shortcut) - 1, yText, textColor, item.shortcut)
  3926.         end
  3927.     else
  3928.         screen.drawText(item.x, yText, item.parent.parent.colors.separator, string.rep("─", item.width))
  3929.     end
  3930.  
  3931.     return item
  3932. end
  3933.  
  3934. local function dropDownMenuReleaseItems(menu)
  3935.     for i = 1, #menu.itemsContainer.children do
  3936.         menu.itemsContainer.children[i].pressed = false
  3937.     end
  3938.  
  3939.     return menu
  3940. end
  3941.  
  3942. local function dropDownMenuItemEventHandler(workspace, object, e1, ...)
  3943.     if e1 == "touch" then
  3944.         if object.type == 1 and not object.pressed then
  3945.  
  3946.             dropDownMenuReleaseItems(object.parent.parent)
  3947.             if #object.parent.parent.subMenus then
  3948.                 for i, subMenu in ipairs(object.parent.parent.subMenus) do
  3949.                     subMenu:remove()
  3950.                     table.remove(object.parent.parent.subMenus, i)
  3951.                 end
  3952.             end
  3953.  
  3954.             object.pressed = true
  3955.             workspace:draw()
  3956.  
  3957.             if object.subMenu then
  3958.                 object.parent.parent.parent:addChild(object.subMenu:releaseItems())
  3959.                 object.subMenu.localX = object.parent.parent.localX + object.parent.parent.width
  3960.                 object.subMenu.localY = object.parent.parent.localY + object.localY - 1
  3961.                 if screen.getWidth() - object.parent.parent.localX - object.parent.parent.width + 1 < object.subMenu.width then
  3962.                     object.subMenu.localX = object.parent.parent.localX - object.subMenu.width
  3963.                     object.parent.parent:moveToFront()
  3964.                 end
  3965.                 table.insert(object.parent.parent.subMenus, object.subMenu)
  3966.                 workspace:draw()
  3967.             else
  3968.                 event.sleep(0.2)
  3969.  
  3970.                 object.parent.parent.parent:remove()
  3971.                
  3972.                 local objectIndex = object:indexOf()
  3973.                 for i = 2, #object.parent.parent.parent.children do
  3974.                     if object.parent.parent.parent.children[i].onMenuClosed then
  3975.                         object.parent.parent.parent.children[i].onMenuClosed(objectIndex)
  3976.                     end
  3977.                 end
  3978.  
  3979.                 if object.onTouch then
  3980.                     object.onTouch(workspace, object, e1, ...)
  3981.                 end
  3982.  
  3983.                 workspace:draw()
  3984.             end
  3985.         end
  3986.     end
  3987. end
  3988.  
  3989. local function dropDownMenuGetHeight(menu)
  3990.     local height = 0
  3991.     for i = 1, #menu.itemsContainer.children do
  3992.         height = height + (menu.itemsContainer.children[i].type == 2 and 1 or menu.itemHeight)
  3993.     end
  3994.  
  3995.     return height
  3996. end
  3997.  
  3998. local function dropDownMenuReposition(menu)
  3999.     menu.itemsContainer.width, menu.itemsContainer.height = menu.width, menu.height
  4000.     menu.prevButton.width, menu.nextButton.width = menu.width, menu.width
  4001.     menu.nextButton.localY = menu.height
  4002.  
  4003.     local y = menu.itemsContainer.children[1].localY
  4004.     for i = 1, #menu.itemsContainer.children do
  4005.         menu.itemsContainer.children[i].localY = y
  4006.         menu.itemsContainer.children[i].width = menu.itemsContainer.width
  4007.         y = y + menu.itemsContainer.children[i].height
  4008.     end
  4009.  
  4010.     menu.prevButton.hidden = menu.itemsContainer.children[1].localY >= 1
  4011.     menu.nextButton.hidden = menu.itemsContainer.children[#menu.itemsContainer.children].localY + menu.itemsContainer.children[#menu.itemsContainer.children].height - 1 <= menu.height
  4012. end
  4013.  
  4014. local function dropDownMenuUpdate(menu)
  4015.     if #menu.itemsContainer.children > 0 then
  4016.         menu.height = math.min(dropDownMenuGetHeight(menu), menu.maximumHeight, screen.getHeight() - menu.y)
  4017.         dropDownMenuReposition(menu)
  4018.     end
  4019. end
  4020.  
  4021. local function dropDownMenuRemoveItem(menu, index)
  4022.     table.remove(menu.itemsContainer.children, index)
  4023.  
  4024.     menu:update()
  4025.  
  4026.     return menu
  4027. end
  4028.  
  4029. local function dropDownMenuAddItem(menu, text, disabled, shortcut, color)
  4030.     local item = menu.itemsContainer:addChild(GUI.object(1, 1, 1, menu.itemHeight))
  4031.     item.type = 1
  4032.     item.text = text
  4033.     item.disabled = disabled
  4034.     item.shortcut = shortcut
  4035.     item.color = color
  4036.     item.draw = dropDownMenuItemDraw
  4037.     item.eventHandler = dropDownMenuItemEventHandler
  4038.  
  4039.     menu:update()
  4040.  
  4041.     return item
  4042. end
  4043.  
  4044. local function dropDownMenuAddSeparator(menu)
  4045.     local item = menu.itemsContainer:addChild(GUI.object(1, 1, 1, 1))
  4046.     item.type = 2
  4047.     item.draw = dropDownMenuItemDraw
  4048.     item.eventHandler = dropDownMenuItemEventHandler
  4049.  
  4050.     menu:update()
  4051.  
  4052.     return item
  4053. end
  4054.  
  4055. local function dropDownMenuScrollDown(workspace, menu)
  4056.     local limit, first = 1, menu.itemsContainer.children[1]
  4057.  
  4058.     first.localY = first.localY + menu.scrollSpeed
  4059.     if first.localY > limit then
  4060.         first.localY = limit
  4061.     end
  4062.  
  4063.     dropDownMenuReposition(menu)
  4064.     workspace:draw()
  4065. end
  4066.  
  4067. local function dropDownMenuScrollUp(workspace, menu)
  4068.     local limit, first = -(#menu.itemsContainer.children * menu.itemHeight - menu.height - 1), menu.itemsContainer.children[1]
  4069.  
  4070.     first.localY = first.localY - menu.scrollSpeed
  4071.     if first.localY < limit then
  4072.         first.localY = limit
  4073.     end
  4074.  
  4075.     dropDownMenuReposition(menu)
  4076.     workspace:draw()
  4077. end
  4078.  
  4079. local function dropDownMenuEventHandler(workspace, menu, e1, e2, e3, e4, e5)
  4080.     if e1 == "scroll" then
  4081.         if e5 == 1 then
  4082.             dropDownMenuScrollDown(workspace, menu)
  4083.         else
  4084.             dropDownMenuScrollUp(workspace, menu)
  4085.         end
  4086.     end
  4087. end
  4088.  
  4089. local function dropDownMenuPrevButtonOnTouch(workspace, button)
  4090.     dropDownMenuScrollDown(workspace, button.parent)
  4091. end
  4092.  
  4093. local function dropDownMenuNextButtonOnTouch(workspace, button)
  4094.     dropDownMenuScrollUp(workspace, button.parent)
  4095. end
  4096.  
  4097. local function dropDownMenuDraw(menu)
  4098.     screen.drawRectangle(menu.x, menu.y, menu.width, menu.height, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency.background)
  4099.     GUI.drawShadow(menu.x, menu.y, menu.width, menu.height, menu.colors.transparency.shadow, true)
  4100.     containerDraw(menu)
  4101. end
  4102.  
  4103. local function dropDownMenuBackgroundObjectEventHandler(workspace, object, e1)
  4104.     if e1 == "touch" then
  4105.         for i = 2, #object.parent.children do
  4106.             if object.parent.children[i].onMenuClosed then
  4107.                 object.parent.children[i].onMenuClosed()
  4108.             end
  4109.         end
  4110.  
  4111.         object.parent:remove()
  4112.         workspace:draw()
  4113.     end
  4114. end
  4115.  
  4116. local function dropDownMenuAdd(parentContainer, menu)
  4117.     local container = parentContainer:addChild(GUI.container(1, 1, parentContainer.width, parentContainer.height))
  4118.     container:addChild(GUI.object(1, 1, container.width, container.height)).eventHandler = dropDownMenuBackgroundObjectEventHandler
  4119.    
  4120.     return container:addChild(menu:releaseItems())
  4121. end
  4122.  
  4123. function GUI.dropDownMenu(x, y, width, maximumHeight, itemHeight, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, backgroundTransparency, shadowTransparency)
  4124.     local menu = GUI.container(x, y, width, 1)
  4125.    
  4126.     menu.colors = {
  4127.         default = {
  4128.             background = backgroundColor,
  4129.             text = textColor
  4130.         },
  4131.         selected = {
  4132.             background = backgroundPressedColor,
  4133.             text = textPressedColor
  4134.         },
  4135.         disabled = {
  4136.             text = disabledColor
  4137.         },
  4138.         separator = separatorColor,
  4139.         transparency = {
  4140.             background = backgroundTransparency,
  4141.             shadow = shadowTransparency
  4142.         }
  4143.     }
  4144.  
  4145.     menu.scrollSpeed = 1
  4146.  
  4147.     menu.itemsContainer = menu:addChild(GUI.container(1, 1, menu.width, menu.height))
  4148.     menu.prevButton = menu:addChild(GUI.button(1, 1, menu.width, 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, "▲"))
  4149.     menu.nextButton = menu:addChild(GUI.button(1, 1, menu.width, 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, "▼"))
  4150.     menu.prevButton.colors.transparency, menu.nextButton.colors.transparency = backgroundTransparency, backgroundTransparency
  4151.     menu.prevButton.onTouch = dropDownMenuPrevButtonOnTouch
  4152.     menu.nextButton.onTouch = dropDownMenuNextButtonOnTouch
  4153.  
  4154.     menu.releaseItems = dropDownMenuReleaseItems
  4155.     menu.itemHeight = itemHeight
  4156.     menu.addSeparator = dropDownMenuAddSeparator
  4157.     menu.addItem = dropDownMenuAddItem
  4158.     menu.removeItem = dropDownMenuRemoveItem
  4159.     menu.draw = dropDownMenuDraw
  4160.     menu.maximumHeight = maximumHeight
  4161.     menu.eventHandler = dropDownMenuEventHandler
  4162.     menu.update = dropDownMenuUpdate
  4163.  
  4164.     menu.subMenus = {} 
  4165.     return menu
  4166. end
  4167.  
  4168. --------------------------------------------------------------------------------
  4169.  
  4170. local function contextMenuUpdate(menu)
  4171.     if #menu.itemsContainer.children > 0 then
  4172.         local widestItem, widestShortcut = 0, 0
  4173.         for i = 1, #menu.itemsContainer.children do
  4174.             if menu.itemsContainer.children[i].type == 1 then
  4175.                 widestItem = math.max(widestItem, unicode.len(menu.itemsContainer.children[i].text))
  4176.                 if menu.itemsContainer.children[i].shortcut then
  4177.                     widestShortcut = math.max(widestShortcut, unicode.len(menu.itemsContainer.children[i].shortcut))
  4178.                 end
  4179.             end
  4180.         end
  4181.  
  4182.         menu.width, menu.height = 2 + widestItem + (widestShortcut > 0 and 3 + widestShortcut or 0), math.min(dropDownMenuGetHeight(menu), menu.maximumHeight)
  4183.         dropDownMenuReposition(menu)
  4184.  
  4185.         local bufferWidth, bufferHeight = screen.getResolution()
  4186.         if menu.x + menu.width + 1 >= bufferWidth then
  4187.             menu.localX = bufferWidth - menu.width - 1
  4188.         end
  4189.         if menu.y + menu.height >= bufferHeight then
  4190.             menu.localY = bufferHeight - menu.height
  4191.         end
  4192.     end
  4193. end
  4194.  
  4195. local contextMenuCreate, contextMenuaddSubMenuItem
  4196.  
  4197. contextMenuaddSubMenuItem = function(menu, text, disabled)
  4198.     local item = menu:addItem(text, disabled, "►")
  4199.     item.subMenu = contextMenuCreate(1, 1)
  4200.     item.subMenu.colors = menu.colors
  4201.    
  4202.     return item.subMenu
  4203. end
  4204.  
  4205. contextMenuCreate = function(x, y, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, backgroundTransparency, shadowTransparency)
  4206.     local menu = GUI.dropDownMenu(
  4207.         x,
  4208.         y,
  4209.         1,
  4210.         math.ceil(screen.getHeight() * 0.5),
  4211.         1,
  4212.         backgroundColor or GUI.CONTEXT_MENU_DEFAULT_BACKGROUND_COLOR,
  4213.         textColor or GUI.CONTEXT_MENU_DEFAULT_TEXT_COLOR,
  4214.         backgroundPressedColor or GUI.CONTEXT_MENU_PRESSED_BACKGROUND_COLOR,
  4215.         textPressedColor or GUI.CONTEXT_MENU_PRESSED_TEXT_COLOR,
  4216.         disabledColor or GUI.CONTEXT_MENU_DISABLED_COLOR,
  4217.         separatorColor or GUI.CONTEXT_MENU_SEPARATOR_COLOR,
  4218.         backgroundTransparency or GUI.CONTEXT_MENU_BACKGROUND_TRANSPARENCY,
  4219.         shadowTransparency or GUI.CONTEXT_MENU_SHADOW_TRANSPARENCY
  4220.     )
  4221.  
  4222.     menu.update = contextMenuUpdate
  4223.     menu.addSubMenuItem = contextMenuaddSubMenuItem
  4224.  
  4225.     return menu
  4226. end
  4227.  
  4228. function GUI.addContextMenu(parentContainer, arg1, ...)
  4229.     if type(arg1) == "table" then
  4230.         return dropDownMenuAdd(parentContainer, arg1, ...)
  4231.     else
  4232.         return dropDownMenuAdd(parentContainer, contextMenuCreate(arg1, ...))
  4233.     end
  4234. end
  4235.  
  4236. --------------------------------------------------------------------------------
  4237.  
  4238. local function comboBoxDraw(object)
  4239.     local arrowSize = object.height * 2 - 1
  4240.     local width = object.width - arrowSize
  4241.  
  4242.     -- Background
  4243.     screen.drawRectangle(object.x, object.y, width, object.height, object.colors.default.background, object.colors.default.text, " ")
  4244.    
  4245.     -- Item
  4246.     if object.dropDownMenu.itemsContainer.children[object.selectedItem] then
  4247.         screen.drawText(
  4248.             object.x + 1,
  4249.             math.floor(object.y + object.height / 2),
  4250.             object.colors.default.text,
  4251.             text.limit(object.dropDownMenu.itemsContainer.children[object.selectedItem].text, object.width - object.height - 2, "right")
  4252.         )
  4253.     end
  4254.  
  4255.     -- Arrow
  4256.     width = object.x + width
  4257.     screen.drawRectangle(width, object.y, arrowSize, object.height, object.colors.arrow.background, object.colors.arrow.text, " ")
  4258.     screen.drawText(math.floor(width + arrowSize / 2), math.floor(object.y + object.height / 2), object.colors.arrow.text, object.pressed and "▲" or "▼")
  4259.  
  4260.     return object
  4261. end
  4262.  
  4263. local function comboBoxGetItem(object, what)
  4264.     if type(what) == "number" then
  4265.         return object.dropDownMenu.itemsContainer.children[what]
  4266.     else
  4267.         local children = object.dropDownMenu.itemsContainer.children
  4268.        
  4269.         for i = 1, #children do
  4270.             if children[i].text == what then
  4271.                 return children[i], i
  4272.             end
  4273.         end
  4274.     end
  4275. end
  4276.  
  4277. local function comboBoxRemoveItem(object, index)
  4278.     object.dropDownMenu:removeItem(index)
  4279.     if object.selectedItem > #object.dropDownMenu.itemsContainer.children then
  4280.         object.selectedItem = #object.dropDownMenu.itemsContainer.children
  4281.     end
  4282. end
  4283.  
  4284. local function comboBoxCount(object)
  4285.     return #object.dropDownMenu.itemsContainer.children
  4286. end
  4287.  
  4288. local function comboBoxClear(object)
  4289.     object.dropDownMenu.itemsContainer:removeChildren()
  4290.     object.selectedItem = 1
  4291.  
  4292.     return object
  4293. end
  4294.  
  4295. local function comboBoxEventHandler(workspace, object, e1, ...)
  4296.     if e1 == "touch" and #object.dropDownMenu.itemsContainer.children > 0 then
  4297.         object.pressed = true
  4298.         object.dropDownMenu.x, object.dropDownMenu.y, object.dropDownMenu.width = object.x, object.y + object.height, object.width
  4299.         object.dropDownMenu:update()
  4300.         dropDownMenuAdd(workspace, object.dropDownMenu)
  4301.         workspace:draw()
  4302.     end
  4303. end
  4304.  
  4305. local function comboBoxAddItem(object, ...)
  4306.     return object.dropDownMenu:addItem(...)
  4307. end
  4308.  
  4309. local function comboBoxAddSeparator(object)
  4310.     return object.dropDownMenu:addSeparator()
  4311. end
  4312.  
  4313. function GUI.comboBox(x, y, width, itemSize, backgroundColor, textColor, arrowBackgroundColor, arrowTextColor)
  4314.     local comboBox = GUI.object(x, y, width, itemSize)
  4315.    
  4316.     comboBox.colors = {
  4317.         default = {
  4318.             background = backgroundColor,
  4319.             text = textColor
  4320.         },
  4321.         selected = {
  4322.             background = GUI.CONTEXT_MENU_PRESSED_BACKGROUND_COLOR,
  4323.             text = GUI.CONTEXT_MENU_PRESSED_TEXT_COLOR
  4324.         },
  4325.         arrow = {
  4326.             background = arrowBackgroundColor,
  4327.             text = arrowTextColor
  4328.         }
  4329.     }
  4330.  
  4331.     comboBox.dropDownMenu = GUI.dropDownMenu(
  4332.         1,
  4333.         1,
  4334.         1,
  4335.         math.ceil(screen.getHeight() * 0.5),
  4336.         itemSize,
  4337.         comboBox.colors.default.background,
  4338.         comboBox.colors.default.text,
  4339.         comboBox.colors.selected.background,
  4340.         comboBox.colors.selected.text,
  4341.         GUI.CONTEXT_MENU_DISABLED_COLOR,
  4342.         GUI.CONTEXT_MENU_SEPARATOR_COLOR,
  4343.         GUI.CONTEXT_MENU_BACKGROUND_TRANSPARENCY,
  4344.         GUI.CONTEXT_MENU_SHADOW_TRANSPARENCY
  4345.     )
  4346.  
  4347.     comboBox.dropDownMenu.onMenuClosed = function(index)
  4348.         comboBox.pressed = false
  4349.         comboBox.selectedItem = index or comboBox.selectedItem
  4350.         comboBox.firstParent:draw()
  4351.        
  4352.         if index and comboBox.onItemSelected then
  4353.             comboBox.onItemSelected(index)
  4354.         end
  4355.     end
  4356.  
  4357.     comboBox.selectedItem = 1
  4358.     comboBox.addItem = comboBoxAddItem
  4359.     comboBox.removeItem = comboBoxRemoveItem
  4360.     comboBox.addSeparator = comboBoxAddSeparator
  4361.     comboBox.draw = comboBoxDraw
  4362.     comboBox.clear = comboBoxClear
  4363.     comboBox.getItem = comboBoxGetItem
  4364.     comboBox.count = comboBoxCount
  4365.     comboBox.eventHandler = comboBoxEventHandler
  4366.  
  4367.     return comboBox
  4368. end
  4369.  
  4370. ---------------------------------------------------------------------------------------------------
  4371.  
  4372. local function windowDraw(window)
  4373.     containerDraw(window)
  4374.     GUI.drawShadow(window.x, window.y, window.width, window.height, GUI.WINDOW_SHADOW_TRANSPARENCY, true)
  4375.  
  4376.     return window
  4377. end
  4378.  
  4379. local function windowCheck(window, x, y)
  4380.     local child, result
  4381.  
  4382.     for i = #window.children, 1, -1 do
  4383.         child = window.children[i]
  4384.        
  4385.         if
  4386.             not child.hidden and
  4387.             not child.disabled and
  4388.             child:isPointInside(x, y)
  4389.         then
  4390.             if not child.passScreenEvents and child.eventHandler then
  4391.                 return true
  4392.  
  4393.             elseif child.children then
  4394.                 result = windowCheck(child, x, y)
  4395.                
  4396.                 -- Nil causes next child processing
  4397.                 if result == true then
  4398.                     return true
  4399.                
  4400.                 elseif result == false then
  4401.                     return false
  4402.                 end
  4403.             end
  4404.         end
  4405.     end
  4406. end
  4407.  
  4408. local function windowEventHandler(workspace, window, e1, e2, e3, e4, ...)
  4409.     if window.movingEnabled then
  4410.         if e1 == "touch" then
  4411.             e3, e4 = math.ceil(e3), math.ceil(e4)
  4412.  
  4413.             if not windowCheck(window, e3, e4) then
  4414.                 window.lastTouchX, window.lastTouchY = e3, e4
  4415.             end
  4416.  
  4417.             if window ~= window.parent.children[#window.parent.children] then
  4418.                 window:focus()
  4419.                
  4420.                 workspace:draw()
  4421.             end
  4422.        
  4423.         elseif e1 == "drag" and window.lastTouchX then
  4424.             e3, e4 = math.ceil(e3), math.ceil(e4)
  4425.  
  4426.             if windowCheck(window, e3, e4) then
  4427.                 return
  4428.             end
  4429.  
  4430.             window.localX, window.localY = window.localX + e3 - window.lastTouchX, window.localY + e4 - window.lastTouchY
  4431.             window.lastTouchX, window.lastTouchY = e3, e4
  4432.            
  4433.             workspace:draw()
  4434.        
  4435.         elseif e1 == "drop" then
  4436.             window.lastTouchX, window.lastTouchY = nil, nil
  4437.         end
  4438.     end
  4439. end
  4440.  
  4441. local function windowResize(window, width, height, ignoreOnResizeFinished)
  4442.     window.width, window.height = width, height
  4443.    
  4444.     if window.onResize then
  4445.         window.onResize(width, height)
  4446.     end
  4447.  
  4448.     if window.onResizeFinished and not ignoreOnResizeFinished then
  4449.         window.onResizeFinished()
  4450.     end
  4451.  
  4452.     return window
  4453. end
  4454.  
  4455. function GUI.windowMaximize(window, animationDisabled)
  4456.     local fromX, fromY, fromWidth, fromHeight, toX, toY, toWidth, toHeight = window.localX, window.localY, window.width, window.height
  4457.    
  4458.     if window.maximized then
  4459.         toX, toY, toWidth, toHeight = window.oldGeometryX, window.oldGeometryY, window.oldGeometryWidth, window.oldGeometryHeight
  4460.  
  4461.         window.oldGeometryX, window.oldGeometryY, window.oldGeometryWidth, window.oldGeometryHeight = nil, nil, nil, nil
  4462.         window.maximized = nil
  4463.     else
  4464.         toWidth, toHeight = window.parent.width, window.parent.height
  4465.        
  4466.         if window.maxWidth then
  4467.             toWidth = math.min(toWidth, window.maxWidth)
  4468.         end
  4469.        
  4470.         if window.maxHeight then
  4471.             toHeight = math.min(toHeight, window.maxHeight)
  4472.         end
  4473.  
  4474.         toX, toY = math.floor(1 + window.parent.width / 2 - toWidth / 2), math.floor(1 + window.parent.height / 2 - toHeight / 2)
  4475.  
  4476.         window.oldGeometryX, window.oldGeometryY, window.oldGeometryWidth, window.oldGeometryHeight = window.localX, window.localY, window.width, window.height
  4477.         window.maximized = true
  4478.     end
  4479.  
  4480.     if animationDisabled then
  4481.         window.localX, window.localY = toX, toY
  4482.         window:resize(toWidth, toHeight)
  4483.     else
  4484.         window:addAnimation(
  4485.             function(animation)
  4486.                 window.localX, window.localY =
  4487.                     math.floor(fromX + (toX - fromX) * animation.position),
  4488.                     math.floor(fromY + (toY - fromY) * animation.position)
  4489.  
  4490.                 window:resize(
  4491.                     math.floor(fromWidth + (toWidth - fromWidth) * animation.position),
  4492.                     math.floor(fromHeight + (toHeight - fromHeight) * animation.position),
  4493.                     animation.position < 1
  4494.                 )
  4495.             end,
  4496.             function(animation)
  4497.                 animation:remove()
  4498.             end
  4499.         ):start(0.5)
  4500.     end
  4501. end
  4502.  
  4503. local function windowFocus(window)
  4504.     GUI.focusedObject = window
  4505.     window.hidden = false
  4506.     window:moveToFront()
  4507.  
  4508.     if window.onFocus then
  4509.         window.onFocus()
  4510.     end
  4511. end
  4512.  
  4513. function GUI.windowMinimize(window)
  4514.     window.hidden = not window.hidden
  4515. end
  4516.  
  4517. function GUI.window(x, y, width, height)
  4518.     local window = GUI.container(x, y, width, height)
  4519.    
  4520.     window.passScreenEvents = false
  4521.     window.movingEnabled = true
  4522.  
  4523.     window.resize = windowResize
  4524.     window.maximize = GUI.windowMaximize
  4525.     window.minimize = GUI.windowMinimize
  4526.  
  4527.     window.eventHandler = windowEventHandler
  4528.     window.draw = windowDraw
  4529.     window.focus = windowFocus
  4530.  
  4531.     return window
  4532. end
  4533.  
  4534. function GUI.filledWindow(x, y, width, height, backgroundColor, transparency)
  4535.     local window = GUI.window(x, y, width, height)
  4536.  
  4537.     window.backgroundPanel = window:addChild(GUI.panel(1, 1, width, height, backgroundColor, transparency))
  4538.     window.actionButtons = window:addChild(GUI.actionButtons(2, 2, true))
  4539.  
  4540.     return window
  4541. end
  4542.  
  4543. function GUI.titledWindow(x, y, width, height, title, addTitlePanel)
  4544.     local window = GUI.filledWindow(x, y, width, height, GUI.WINDOW_BACKGROUND_PANEL_COLOR)
  4545.  
  4546.     if addTitlePanel then
  4547.         window.titlePanel = window:addChild(GUI.panel(1, 1, width, 1, GUI.WINDOW_TITLE_BACKGROUND_COLOR))
  4548.         window.backgroundPanel.localY, window.backgroundPanel.height = 2, window.height - 1
  4549.     end
  4550.  
  4551.     window.titleLabel = window:addChild(GUI.label(1, 1, width, height, GUI.WINDOW_TITLE_TEXT_COLOR, title)):setAlignment(GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP)
  4552.     window.actionButtons.localY = 1
  4553.     window.actionButtons:moveToFront()
  4554.  
  4555.     return window
  4556. end
  4557.  
  4558. function GUI.tabbedWindow(x, y, width, height, ...)
  4559.     local window = GUI.filledWindow(x, y, width, height, GUI.WINDOW_BACKGROUND_PANEL_COLOR)
  4560.  
  4561.     window.tabBar = window:addChild(GUI.tabBar(1, 1, window.width, 3, 2, 0, GUI.WINDOW_TAB_BAR_DEFAULT_BACKGROUND_COLOR, GUI.WINDOW_TAB_BAR_DEFAULT_TEXT_COLOR, GUI.WINDOW_TAB_BAR_DEFAULT_BACKGROUND_COLOR, GUI.WINDOW_TAB_BAR_DEFAULT_TEXT_COLOR, GUI.WINDOW_TAB_BAR_SELECTED_BACKGROUND_COLOR, GUI.WINDOW_TAB_BAR_SELECTED_TEXT_COLOR, true))
  4562.    
  4563.     window.backgroundPanel.localY, window.backgroundPanel.height = 4, window.height - 3
  4564.     window.actionButtons:moveToFront()
  4565.     window.actionButtons.localY = 2
  4566.  
  4567.     return window
  4568. end
  4569.  
  4570. ---------------------------------------------------------------------------------------------------
  4571.  
  4572. function GUI.tabBar(...)
  4573.     local tabBar = GUI.list(...)
  4574.  
  4575.     tabBar:setDirection(GUI.DIRECTION_HORIZONTAL)
  4576.     tabBar:setAlignment(GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP)
  4577.  
  4578.     return tabBar
  4579. end
  4580.  
  4581. --------------------------------------------------------------------------------
  4582.  
  4583. local function menuDraw(menu)
  4584.     screen.drawRectangle(menu.x, menu.y, menu.width, 1, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency)
  4585.     layoutDraw(menu)
  4586. end
  4587.  
  4588. local function menuAddItem(menu, text, textColor)
  4589.     local item = menu:addChild(pressable(1, 1, unicode.len(text) + 2, 1, nil, textColor or menu.colors.default.text, menu.colors.selected.background, menu.colors.selected.text, 0x0, 0x0, text))
  4590.     item.eventHandler = pressableEventHandler
  4591.  
  4592.     return item
  4593. end
  4594.  
  4595. local function menuGetItem(menu, what)
  4596.     if type(what) == "number" then
  4597.         return menu.children[what]
  4598.     else
  4599.         for i = 1, #menu.children do
  4600.             if menu.children[i].text == what then
  4601.                 return menu.children[i], i
  4602.             end
  4603.         end
  4604.     end
  4605. end
  4606.  
  4607. local function menuContextMenuItemOnTouch(workspace, item)
  4608.     item.contextMenu.x, item.contextMenu.y = item.x, item.y + 1
  4609.     dropDownMenuAdd(workspace, item.contextMenu)
  4610.  
  4611.     workspace:draw()
  4612. end
  4613.  
  4614. local function menuAddContextMenuItem(menu, ...)
  4615.     local item = menu:addItem(...)
  4616.  
  4617.     item.switchMode = true
  4618.     item.onTouch = menuContextMenuItemOnTouch
  4619.     item.contextMenu = contextMenuCreate(1, 1)
  4620.     item.contextMenu.onMenuClosed = function()
  4621.         item.pressed = false
  4622.         item.firstParent:draw()
  4623.     end
  4624.  
  4625.     return item.contextMenu
  4626. end
  4627.  
  4628. function GUI.menu(x, y, width, backgroundColor, textColor, backgroundPressedColor, textPressedColor, backgroundTransparency)
  4629.     local menu = GUI.layout(x, y, width, 1, 1, 1)
  4630.    
  4631.     menu.colors = {
  4632.         default = {
  4633.             background = backgroundColor,
  4634.             text = textColor,
  4635.         },
  4636.         selected = {
  4637.             background = backgroundPressedColor,
  4638.             text = textPressedColor,
  4639.         },
  4640.         transparency = backgroundTransparency
  4641.     }
  4642.    
  4643.     menu.passScreenEvents = false
  4644.     menu.addContextMenuItem = menuAddContextMenuItem
  4645.     menu.addItem = menuAddItem
  4646.     menu.getItem = menuGetItem
  4647.     menu.draw = menuDraw
  4648.  
  4649.     menu:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL)
  4650.     menu:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP)
  4651.     menu:setSpacing(1, 1, 0)
  4652.     menu:setMargin(1, 1, 1, 0)
  4653.  
  4654.     return menu
  4655. end
  4656.  
  4657. ---------------------------------------------------------------------------------------------------
  4658.  
  4659. local function progressIndicatorDraw(self)
  4660.     local color = self.active and (self.position == 1 and self.colors.secondary or self.colors.primary) or self.colors.passive
  4661.     screen.drawText(self.x + 1, self.y, color, "⢀")
  4662.     screen.drawText(self.x + 2, self.y, color, "⡀")
  4663.  
  4664.     color = self.active and (self.position == 2 and self.colors.secondary or self.colors.primary) or self.colors.passive
  4665.     screen.drawText(self.x + 3, self.y + 1, color, "⠆")
  4666.     screen.drawText(self.x + 2, self.y + 1, color, "⢈")
  4667.  
  4668.     color = self.active and (self.position == 3 and self.colors.secondary or self.colors.primary) or self.colors.passive
  4669.     screen.drawText(self.x + 1, self.y + 2, color, "⠈")
  4670.     screen.drawText(self.x + 2, self.y + 2, color, "⠁")
  4671.  
  4672.     color = self.active and (self.position == 4 and self.colors.secondary or self.colors.primary) or self.colors.passive
  4673.     screen.drawText(self.x, self.y + 1, color, "⠰")
  4674.     screen.drawText(self.x + 1, self.y + 1, color, "⡁")
  4675. end
  4676.  
  4677. local function progressIndicatorRoll(self)
  4678.     self.position = self.position + 1
  4679.     if self.position > 4 then
  4680.         self.position = 1
  4681.     end
  4682. end
  4683.  
  4684. local function progressIndicatorReset(self, state)
  4685.     self.active = state
  4686.     self.position = 1
  4687. end
  4688.  
  4689. function GUI.progressIndicator(x, y, passiveColor, primaryColor, secondaryColor)
  4690.     local object = GUI.object(x, y, 4, 3)
  4691.    
  4692.     object.colors = {
  4693.         passive = passiveColor,
  4694.         primary = primaryColor,
  4695.         secondary = secondaryColor
  4696.     }
  4697.  
  4698.     object.active = false
  4699.     object.reset = progressIndicatorReset
  4700.     object.draw = progressIndicatorDraw
  4701.     object.roll = progressIndicatorRoll
  4702.  
  4703.     object:reset()
  4704.  
  4705.     return object
  4706. end
  4707.  
  4708. ---------------------------------------------------------------------------------------------------
  4709.  
  4710. local function tableHeaderDraw(self)
  4711.     screen.drawRectangle(self.x, self.y, self.width, self.height, self.parent.colors.headerBackground, self.parent.colors.headerText, " ")
  4712.     screen.drawText(self.x + 1, self.y, self.parent.colors.headerText, self.text)
  4713. end
  4714.  
  4715. local function tableAddColumn(self, headerText, sizePolicy, size)
  4716.     layoutAddColumn(self, sizePolicy, size)
  4717.    
  4718.     local lastColumn = #self.columnSizes
  4719.    
  4720.     local header = self:setPosition(lastColumn, 1, self:addChild(GUI.object(1, 1, 1, self.itemHeight)))
  4721.     header.text = headerText
  4722.     header.draw = tableHeaderDraw
  4723.  
  4724.     for row = 1, 2 do
  4725.         self:setAlignment(lastColumn, row, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP)
  4726.         self:setSpacing(lastColumn, row, 0)
  4727.         self:setFitting(lastColumn, row, true, false)
  4728.     end
  4729. end
  4730.  
  4731. local function tableAddRow(self, ...)
  4732.     local objects, columnCount = {...}, #self.columnSizes
  4733.     local index = #self.children - columnCount + 1
  4734.    
  4735.     if #objects == columnCount then
  4736.         for i = #objects, 1, -1 do
  4737.             local object = self:setPosition(i, 2, self:addChild(objects[i], index))
  4738.  
  4739.             object.height = self.itemHeight
  4740.             object.alternative = self.nextRowAlternative
  4741.         end
  4742.  
  4743.         self.nextRowAlternative = not self.nextRowAlternative
  4744.     else
  4745.         error("Failed to add row: count of columns ~= count of objects in row")
  4746.     end
  4747. end
  4748.  
  4749. local function tableUpdateSelection(self)
  4750.     local columnCount, row = #self.columnSizes, 1
  4751.    
  4752.     for i = 1, #self.children - columnCount, columnCount do
  4753.         for j = i, i + columnCount - 1 do
  4754.             self.children[j].selected = self.selectedRows[row]
  4755.         end
  4756.  
  4757.         row = row + 1
  4758.     end
  4759. end
  4760.  
  4761. local function tableClear(self)
  4762.     local columnCount, childrenCount = #self.columnSizes, #self.children
  4763.     if childrenCount > columnCount then
  4764.         self:removeChildren(1, childrenCount - columnCount)
  4765.     end
  4766.  
  4767.     self.selectedRows, self.nextRowAlternative = {}, nil
  4768. end
  4769.  
  4770. function GUI.tableCellEventHandler(workspace, self, e1, e2, e3, e4, e5, ...)
  4771.     if e1 == "touch" or e1 == "drag" or e1 == "double_touch" then
  4772.         local row = math.ceil(self:indexOf() / #self.parent.columnSizes)
  4773.  
  4774.         -- Deselecting all rows
  4775.         if (e5 == 0 or not self.parent.selectedRows[row]) and not (keyboard.isControlDown() or keyboard.isCommandDown()) then
  4776.             self.parent.selectedRows = {}
  4777.         end
  4778.  
  4779.         -- Selecting this row
  4780.         self.parent.selectedRows[row] = true
  4781.         tableUpdateSelection(self.parent)
  4782.  
  4783.         if self.parent.onCellTouch then
  4784.             self.parent.onCellTouch(workspace, self, e1, e2, e3, e4, e5, ...)
  4785.         end
  4786.  
  4787.         workspace:draw()
  4788.     end
  4789. end
  4790.  
  4791. function GUI.tableCellDraw(self)
  4792.     local background, foreground
  4793.     if self.selected then
  4794.         background, foreground = self.colors.selectionBackground, self.colors.selectionText
  4795.     elseif self.alternative then
  4796.         background, foreground = self.colors.alternativeBackground, self.colors.alternativeText
  4797.     else
  4798.         background, foreground = self.colors.defaultBackground, self.colors.defaultText
  4799.     end
  4800.  
  4801.     if background then
  4802.         screen.drawRectangle(self.x, self.y, self.width, self.height,
  4803.             background,
  4804.             foreground,
  4805.         " ")
  4806.     end
  4807.  
  4808.     return foreground
  4809. end
  4810.  
  4811. function GUI.tableCell(colors)
  4812.     local cell = GUI.object(1, 1, 1, 1)
  4813.  
  4814.     cell.colors = colors
  4815.     cell.draw = GUI.tableCellDraw
  4816.     cell.eventHandler = GUI.tableCellEventHandler
  4817.  
  4818.     return cell
  4819. end
  4820.  
  4821. local function tableTextCellDraw(self)
  4822.     screen.drawText(self.x + 1, self.y, GUI.tableCellDraw(self), self.text)
  4823. end
  4824.  
  4825. function GUI.tableTextCell(colors, text)
  4826.     local cell = GUI.tableCell(colors)
  4827.  
  4828.     cell.text = text
  4829.     cell.draw = tableTextCellDraw
  4830.  
  4831.     return cell
  4832. end
  4833.  
  4834. local function tableDraw(self)
  4835.     -- Items background
  4836.     screen.drawRectangle(self.x, self.y + self.itemHeight, self.width, self.height - self.itemHeight, self.colors.background, 0x0, " ")
  4837.     -- Content
  4838.     layoutDraw(self)
  4839. end
  4840.  
  4841. function onBackgroundTouch(workspace, self, e1, e2, e3, e4, e5, ...)
  4842.     -- Deselecting all rows
  4843.     self.selectedRows = {}
  4844.     tableUpdateSelection(self)
  4845.     workspace:draw()
  4846. end
  4847.  
  4848. function GUI.tableEventHandler(workspace, self, e1, e2, e3, e4, e5, ...)
  4849.     if e1 == "touch" then
  4850.         local itemTouched = false
  4851.  
  4852.         for i = 1, #self.children do
  4853.             if self.children[i]:isPointInside(math.ceil(e3), math.ceil(e4)) then
  4854.                 itemTouched = true
  4855.                 break
  4856.             end
  4857.         end
  4858.  
  4859.         if not itemTouched then
  4860.             onBackgroundTouch(workspace, self, e1, e2, e3, e4, e5, ...)
  4861.         end
  4862.     elseif e1 == "scroll" then
  4863.         local columnCount = #self.columnSizes
  4864.         local horizontalMargin, verticalMargin = self:getMargin(1, 2)
  4865.  
  4866.         for i = 1, columnCount do
  4867.             self:setMargin(i, 2, horizontalMargin,
  4868.                 math.max(
  4869.                     -self.itemHeight * (#self.children - columnCount) / columnCount + 1,
  4870.                     math.min(
  4871.                         0,
  4872.                         verticalMargin + e5
  4873.                     )
  4874.                 )
  4875.             )
  4876.         end
  4877.  
  4878.         workspace:draw()
  4879.     end
  4880. end
  4881.  
  4882. function GUI.table(x, y, width, height, itemHeight, backgroundColor, headerBackgroundColor, headerTextColor)
  4883.     local table = GUI.layout(x, y, width, height, 0, 2)
  4884.  
  4885.     table.colors = {
  4886.         background = backgroundColor,
  4887.         headerBackground = headerBackgroundColor,
  4888.         headerText = headerTextColor
  4889.     }
  4890.  
  4891.     table.itemHeight = itemHeight
  4892.     table.selectedRows = {}
  4893.  
  4894.     table.addColumn = tableAddColumn
  4895.     table.addRow = tableAddRow
  4896.     table.clear = tableClear
  4897.     table.draw = tableDraw
  4898.     table.onBackgroundTouch = onBackgroundTouch
  4899.     table.eventHandler = GUI.tableEventHandler
  4900.  
  4901.     table:setRowHeight(1, GUI.SIZE_POLICY_ABSOLUTE, itemHeight)
  4902.     table:setRowHeight(2, GUI.SIZE_POLICY_RELATIVE, 1.0)
  4903.  
  4904.     return table
  4905. end
  4906.  
  4907. ---------------------------------------------------------------------------------------------------
  4908.  
  4909. -- local workspace = GUI.workspace()
  4910.  
  4911. -- workspace:addChild(GUI.panel(1, 1, workspace.width, workspace.height, 0x2D2D2D))
  4912.  
  4913. -- local t = workspace:addChild(GUI.table(3, 2, 80, 30, 1,
  4914. --  0xF0F0F0,
  4915. --  0xFFFFFF,
  4916. --  0x000000
  4917. -- ))
  4918.  
  4919. -- t:addColumn("Name", GUI.SIZE_POLICY_RELATIVE, 0.6)
  4920. -- t:addColumn("Date", GUI.SIZE_POLICY_RELATIVE, 0.4)
  4921. -- t:addColumn("Size", GUI.SIZE_POLICY_ABSOLUTE, 16)
  4922. -- t:addColumn("Type", GUI.SIZE_POLICY_ABSOLUTE, 10)
  4923.  
  4924. -- local colors1 = {
  4925. --  defaultBackground = nil,
  4926. --  defaultText = 0x3C3C3C,
  4927. --  alternativeBackground = 0xE1E1E1,
  4928. --  alternativeText = 0x3C3C3C,
  4929. --  selectionBackground = 0xCC2440,
  4930. --  selectionText = 0xFFFFFF,
  4931. -- }
  4932.  
  4933. -- local colors2 = {}
  4934. -- for key, value in pairs(colors1) do
  4935. --  colors2[key] = value
  4936. -- end
  4937. -- colors2.defaultText, colors2.alternativeText = 0xA5A5A5, 0xA5A5A5
  4938.  
  4939. -- for i = 1, 10 do
  4940. --  t:addRow(
  4941. --      GUI.tableTextCell(colors1, "Ehehehe " .. i),
  4942. --      GUI.tableTextCell(colors2, "12.02.2018"),
  4943. --      GUI.tableTextCell(colors2, "114.23 KB"),
  4944. --      GUI.tableTextCell(colors2, ".lua")
  4945. --  )
  4946. -- end
  4947.  
  4948. -- workspace:draw()
  4949. -- workspace:start()
  4950.  
  4951.  
  4952. ---------------------------------------------------------------------------------------------------
  4953.  
  4954. return GUI
  4955.  
Tags: ORMS
Add Comment
Please, Sign In to add comment