local keyboard = require("Keyboard") local filesystem = require("Filesystem") local event = require("Event") local color = require("Color") local image = require("Image") local screen = require("Screen") local paths = require("Paths") local text = require("Text") local number = require("Number") ----------------------------------------------------------------------------------------- local GUI = { ALIGNMENT_HORIZONTAL_LEFT = 1, ALIGNMENT_HORIZONTAL_CENTER = 2, ALIGNMENT_HORIZONTAL_RIGHT = 3, ALIGNMENT_VERTICAL_TOP = 4, ALIGNMENT_VERTICAL_CENTER = 5, ALIGNMENT_VERTICAL_BOTTOM = 6, DIRECTION_HORIZONTAL = 7, DIRECTION_VERTICAL = 8, SIZE_POLICY_ABSOLUTE = 9, SIZE_POLICY_RELATIVE = 10, IO_MODE_FILE = 11, IO_MODE_DIRECTORY = 12, IO_MODE_BOTH = 13, IO_MODE_OPEN = 14, IO_MODE_SAVE = 15, BUTTON_PRESS_DURATION = 0.2, BUTTON_ANIMATION_DURATION = 0.2, SWITCH_ANIMATION_DURATION = 0.3, FILESYSTEM_DIALOG_ANIMATION_DURATION = 0.5, CONTEXT_MENU_DEFAULT_BACKGROUND_COLOR = 0x1E1E1E, CONTEXT_MENU_DEFAULT_ICON_COLOR = 0x696969, CONTEXT_MENU_DEFAULT_TEXT_COLOR = 0xD2D2D2, CONTEXT_MENU_PRESSED_BACKGROUND_COLOR = 0x3366CC, CONTEXT_MENU_PRESSED_ICON_COLOR = 0xB4B4B4, CONTEXT_MENU_PRESSED_TEXT_COLOR = 0xFFFFFF, CONTEXT_MENU_DISABLED_ICON_COLOR = 0x5A5A5A, CONTEXT_MENU_DISABLED_TEXT_COLOR = 0x5A5A5A, CONTEXT_MENU_BACKGROUND_TRANSPARENCY = nil, CONTEXT_MENU_SHADOW_TRANSPARENCY = 0.4, CONTEXT_MENU_SEPARATOR_COLOR = 0x2D2D2D, BACKGROUND_CONTAINER_PANEL_COLOR = 0x0, BACKGROUND_CONTAINER_TITLE_COLOR = 0xE1E1E1, BACKGROUND_CONTAINER_PANEL_TRANSPARENCY = 0.3, WINDOW_BACKGROUND_PANEL_COLOR = 0xF0F0F0, WINDOW_SHADOW_TRANSPARENCY = 0.6, WINDOW_TITLE_BACKGROUND_COLOR = 0xE1E1E1, WINDOW_TITLE_TEXT_COLOR = 0x2D2D2D, WINDOW_TAB_BAR_DEFAULT_BACKGROUND_COLOR = 0x2D2D2D, WINDOW_TAB_BAR_DEFAULT_TEXT_COLOR = 0xF0F0F0, WINDOW_TAB_BAR_SELECTED_BACKGROUND_COLOR = 0xF0F0F0, WINDOW_TAB_BAR_SELECTED_TEXT_COLOR = 0x2D2D2D, LUA_SYNTAX_COLOR_SCHEME = { background = 0x1E1E1E, text = 0xE1E1E1, strings = 0x99FF80, loops = 0xFFFF98, comments = 0x898989, boolean = 0xFFDB40, logic = 0xFFCC66, numbers = 0x66DBFF, functions = 0xFFCC66, compares = 0xFFCC66, lineNumbersBackground = 0x2D2D2D, lineNumbersText = 0xC3C3C3, scrollBarBackground = 0x2D2D2D, scrollBarForeground = 0x5A5A5A, selection = 0x4B4B4B, indentation = 0x2D2D2D }, LUA_SYNTAX_PATTERNS = { "[%.%,%>%<%=%~%+%-%*%/%^%#%%%&]", "compares", 0, 0, "[^%a%d][%.%d]+[^%a%d]", "numbers", 1, 1, "[^%a%d][%.%d]+$", "numbers", 1, 0, "0x%w+", "numbers", 0, 0, " not ", "logic", 0, 1, " or ", "logic", 0, 1, " and ", "logic", 0, 1, "function%(", "functions", 0, 1, "function%s[^%s%(%)%{%}%[%]]+%(", "functions", 9, 1, "nil", "boolean", 0, 0, "false", "boolean", 0, 0, "true", "boolean", 0, 0, " break$", "loops", 0, 0, "elseif ", "loops", 0, 1, "else[%s%;]", "loops", 0, 1, "else$", "loops", 0, 0, "function ", "loops", 0, 1, "local ", "loops", 0, 1, "return", "loops", 0, 0, "until ", "loops", 0, 1, "then", "loops", 0, 0, "if ", "loops", 0, 1, "repeat$", "loops", 0, 0, " in ", "loops", 0, 1, "for ", "loops", 0, 1, "end[%s%;]", "loops", 0, 1, "end$", "loops", 0, 0, "do ", "loops", 0, 1, "do$", "loops", 0, 0, "while ", "loops", 0, 1, "\'[^\']+\'", "strings", 0, 0, "\"[^\"]+\"", "strings", 0, 0, "%-%-.+", "comments", 0, 0, }, } -------------------------------------------------------------------------------- function GUI.setAlignment(object, horizontalAlignment, verticalAlignment) object.horizontalAlignment, object.verticalAlignment = horizontalAlignment, verticalAlignment return object end function GUI.getAlignmentCoordinates(x, y, width1, height1, horizontalAlignment, verticalAlignment, width2, height2) if horizontalAlignment == GUI.ALIGNMENT_HORIZONTAL_CENTER then x = x + width1 / 2 - width2 / 2 elseif horizontalAlignment == GUI.ALIGNMENT_HORIZONTAL_RIGHT then x = x + width1 - width2 elseif horizontalAlignment ~= GUI.ALIGNMENT_HORIZONTAL_LEFT then error("Unknown horizontal alignment: " .. tostring(horizontalAlignment)) end if verticalAlignment == GUI.ALIGNMENT_VERTICAL_CENTER then y = y + height1 / 2 - height2 / 2 elseif verticalAlignment == GUI.ALIGNMENT_VERTICAL_BOTTOM then y = y + height1 - height2 elseif verticalAlignment ~= GUI.ALIGNMENT_VERTICAL_TOP then error("Unknown vertical alignment: " .. tostring(verticalAlignment)) end return x, y end function GUI.getMarginCoordinates(x, y, horizontalAlignment, verticalAlignment, horizontalMargin, verticalMargin) if horizontalAlignment == GUI.ALIGNMENT_HORIZONTAL_RIGHT then x = x - horizontalMargin else x = x + horizontalMargin end if verticalAlignment == GUI.ALIGNMENT_VERTICAL_BOTTOM then y = y - verticalMargin else y = y + verticalMargin end return x, y end -------------------------------------------------------------------------------- local function objectIsPointInside(object, x, y) return x >= object.x and x < object.x + object.width and y >= object.y and y < object.y + object.height end local function objectDraw(object) return object end function GUI.object(x, y, width, height) return { x = x, y = y, width = width, height = height, isPointInside = objectIsPointInside, blockScreenEvents = true, draw = objectDraw } end -------------------------------------------------------------------------------- local function containerObjectIndexOf(object) if not object.parent then error("Object doesn't have a parent container") end for objectIndex = 1, #object.parent.children do if object.parent.children[objectIndex] == object then return objectIndex end end end local function containerObjectMoveForward(object) local objectIndex = containerObjectIndexOf(object) if objectIndex < #object.parent.children then object.parent.children[objectIndex], object.parent.children[objectIndex + 1] = object.parent.children[objectIndex + 1], object.parent.children[objectIndex] end return object end local function containerObjectMoveBackward(object) local objectIndex = containerObjectIndexOf(object) if objectIndex > 1 then object.parent.children[objectIndex], object.parent.children[objectIndex - 1] = object.parent.children[objectIndex - 1], object.parent.children[objectIndex] end return object end local function containerObjectMoveToFront(object) table.remove(object.parent.children, containerObjectIndexOf(object)) table.insert(object.parent.children, object) return object end local function containerObjectMoveToBack(object) table.remove(object.parent.children, containerObjectIndexOf(object)) table.insert(object.parent.children, 1, object) return object end local function containerObjectRemove(object) table.remove(object.parent.children, containerObjectIndexOf(object)) end local function containerObjectAnimationStart(animation, duration) animation.position = 0 animation.duration = duration animation.started = true animation.startUptime = computer.uptime() computer.pushSignal("GUI", "animationStarted") end local function containerObjectAnimationStop(animation) animation.position = 0 animation.started = false end local function containerObjectAnimationRemove(animation) animation.removeLater = true end local function containerObjectAddAnimation(object, frameHandler, onFinish) local animation = { object = object, position = 0, start = containerObjectAnimationStart, stop = containerObjectAnimationStop, remove = containerObjectAnimationRemove, frameHandler = frameHandler, onFinish = onFinish, } object.workspace.animations = object.workspace.animations or {} table.insert(object.workspace.animations, animation) return animation end local function containerAddChild(container, object, atIndex) -- Parent containers object.parent = container local function updateWorkspace(object, workspace) object.workspace = workspace if object.children then for i = 1, #object.children do updateWorkspace(object.children[i], workspace) end end end updateWorkspace(object, container.workspace or container) -- Position object.localX = object.x object.localY = object.y -- Additional methods after adding to parent container object.indexOf = containerObjectIndexOf object.moveToFront = containerObjectMoveToFront object.moveToBack = containerObjectMoveToBack object.moveForward = containerObjectMoveForward object.moveBackward = containerObjectMoveBackward object.remove = containerObjectRemove object.addAnimation = containerObjectAddAnimation if atIndex then table.insert(container.children, atIndex, object) else table.insert(container.children, object) end return object end local function containerRemoveChildren(container, from, to) from = from or 1 for objectIndex = from, to or #container.children do table.remove(container.children, from) end end local function getRectangleBounds(R1X1, R1Y1, R1X2, R1Y2, R2X1, R2Y1, R2X2, R2Y2) if R2X1 <= R1X2 and R2Y1 <= R1Y2 and R2X2 >= R1X1 and R2Y2 >= R1Y1 then return math.max(R2X1, R1X1), math.max(R2Y1, R1Y1), math.min(R2X2, R1X2), math.min(R2Y2, R1Y2) else return end end local function containerDraw(container) local R1X1, R1Y1, R1X2, R1Y2, child = screen.getDrawLimit() local boundsX1, boundsY1, boundsX2, boundsY2 = getRectangleBounds( R1X1, R1Y1, R1X2, R1Y2, container.x, container.y, container.x + container.width - 1, container.y + container.height - 1 ) if boundsX1 then screen.setDrawLimit(boundsX1, boundsY1, boundsX2, boundsY2) for i = 1, #container.children do child = container.children[i] if not child.hidden then child.x, child.y = container.x + child.localX - 1, container.y + child.localY - 1 child:draw() end end screen.setDrawLimit(R1X1, R1Y1, R1X2, R1Y2) end -- if container.workspace and container.workspace.capturedObject == container then -- screen.drawRectangle(container.x, container.y, container.width, 1, 0xFF0000, 0x0, " ") -- end return container end function GUI.container(x, y, width, height) local container = GUI.object(x, y, width, height) container.children = {} container.blockScreenEvents = false container.draw = containerDraw container.removeChildren = containerRemoveChildren container.addChild = containerAddChild return container end -------------------------------------------------------------------------------- local function workspaceStart(workspace, eventPullTimeout) local 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, isScreenEvent, roundedX, roundedY, capturedObject, animation, animationIndex, animationOnFinishMethodsIndex, animationOnFinishMethods local function processObject(object, boundsX1, boundsY1, boundsX2, boundsY2) local govno = false if isScreenEvent then if workspace.capturedObject then if workspace.capturedObject == object then govno = object.blockScreenEvents elseif object.ignoresCapturedObject then if not boundsX1 or not ( roundedX >= boundsX1 and roundedX <= boundsX2 and roundedY >= boundsY1 and roundedY <= boundsY2 ) then goto skipEventHandling end else goto skipEventHandling end else if not boundsX1 or not ( roundedX >= boundsX1 and roundedX <= boundsX2 and roundedY >= boundsY1 and roundedY <= boundsY2 ) then goto skipEventHandling else govno = object.blockScreenEvents end end if object.eventHandler and not object.disabled then object.eventHandler(workspace, object, 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) end else if not workspace.capturedObject or workspace.capturedObject == object or object.ignoresCapturedObject then if object.eventHandler and not object.disabled then object.eventHandler(workspace, object, 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) end end end -- Container ::skipEventHandling:: if object.children and boundsX1 then local child, newBoundsX1, newBoundsY1, newBoundsX2, newBoundsY2 for i = #object.children, 1, -1 do child = object.children[i] if not child.hidden then newBoundsX1, newBoundsY1, newBoundsX2, newBoundsY2 = getRectangleBounds( boundsX1, boundsY1, boundsX2, boundsY2, child.x, child.y, child.x + child.width - 1, child.y + child.height - 1 ) if processObject( child, newBoundsX1, newBoundsY1, newBoundsX2, newBoundsY2 ) then return true end end end end if govno then return true end end workspace.eventPullTimeout = eventPullTimeout repeat 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) isScreenEvent = e1 == "touch" or e1 == "drag" or e1 == "drop" or e1 == "scroll" or e1 == "double_touch" if isScreenEvent then roundedX, roundedY = math.ceil(e3), math.ceil(e4) end processObject( workspace, workspace.x, workspace.y, workspace.x + workspace.width - 1, workspace.y + workspace.height - 1 ) if workspace.animations then animationIndex, animationOnFinishMethodsIndex, animationOnFinishMethods = 1, 1, {} -- Продрачиваем анимации и вызываем обработчики кадров while animationIndex <= #workspace.animations do animation = workspace.animations[animationIndex] if animation.removeLater then table.remove(workspace.animations, animationIndex) if #workspace.animations == 0 then workspace.animations = nil break end else if animation.started then animation.position = (computer.uptime() - animation.startUptime) / animation.duration if animation.position < 1 then animation.frameHandler(animation) else animation.position, animation.started = 1, false animation.frameHandler(animation) if animation.onFinish then animationOnFinishMethods[animationOnFinishMethodsIndex] = animation animationOnFinishMethodsIndex = animationOnFinishMethodsIndex + 1 end end end animationIndex = animationIndex + 1 end end -- По завершению продрочки отрисовываем изменения на экране workspace:draw() -- Вызываем поочередно все методы .onFinish for i = 1, #animationOnFinishMethods do animationOnFinishMethods[i].onFinish(animationOnFinishMethods[i]) end end until workspace.needClose workspace.needClose = nil end local function workspaceStop(workspace) workspace.needClose = true end local function workspaceDraw(object, ...) containerDraw(object) screen.update(...) end function GUI.workspace(x, y, width, height) local workspace = GUI.container(x or 1, y or 1, width or screen.getWidth(), height or screen.getHeight()) workspace.draw = workspaceDraw workspace.start = workspaceStart workspace.stop = workspaceStop return workspace end -------------------------------------------------------------------------------- local function pressableDraw(pressable) local background = pressable.pressed and pressable.colors.pressed.background or pressable.disabled and pressable.colors.disabled.background or pressable.colors.default.background local text = pressable.pressed and pressable.colors.pressed.text or pressable.disabled and pressable.colors.disabled.text or pressable.colors.default.text if background then screen.drawRectangle(pressable.x, pressable.y, pressable.width, pressable.height, background, text, " ") end screen.drawText(math.floor(pressable.x + pressable.width / 2 - unicode.wlen(pressable.text) / 2), math.floor(pressable.y + pressable.height / 2), text, pressable.text) end local function pressableHandlePress(workspace, pressable, ...) pressable.pressed = not pressable.pressed workspace:draw() if not pressable.switchMode then pressable.pressed = not pressable.pressed event.sleep(GUI.BUTTON_PRESS_DURATION) workspace:draw() end if pressable.onTouch then pressable.onTouch(workspace, pressable, ...) end end local function pressableEventHandler(workspace, pressable, e1, ...) if e1 == "touch" then pressableHandlePress(workspace, pressable, e1, ...) end end local function pressable(x, y, width, height, backgroundColor, textColor, backgroundPressedColor, textPressedColor, backgroundDisabledColor, textDisabledColor, text) local pressable = GUI.object(x, y, width, height) pressable.colors = { default = { background = backgroundColor, text = textColor }, pressed = { background = backgroundPressedColor, text = textPressedColor }, disabled = { background = backgroundDisabledColor, text = textDisabledColor } } pressable.pressed = false pressable.text = text pressable.draw = pressableDraw pressable.eventHandler = pressableEventHandler return pressable end -------------------------------------------------------------------------------- local function buttonPlayAnimation(button, onFinish) button.animationStarted = true button:addAnimation( function(animation) if button.pressed then if button.colors.default.background and button.colors.pressed.background then button.animationCurrentBackground = color.transition(button.colors.pressed.background, button.colors.default.background, animation.position) end button.animationCurrentText = color.transition(button.colors.pressed.text, button.colors.default.text, animation.position) else if button.colors.default.background and button.colors.pressed.background then button.animationCurrentBackground = color.transition(button.colors.default.background, button.colors.pressed.background, animation.position) end button.animationCurrentText = color.transition(button.colors.default.text, button.colors.pressed.text, animation.position) end end, function(animation) button.animationStarted = false button.pressed = not button.pressed onFinish(animation) end ):start(button.animationDuration) end local function buttonPress(button, workspace, object, ...) if button.animated then local eventData = {...} buttonPlayAnimation(button, function(animation) if button.onTouch then button.onTouch(workspace, button, table.unpack(eventData)) end animation:remove() if not button.switchMode then buttonPlayAnimation(button, function(animation) animation:remove() end) end end) else pressableHandlePress(workspace, button, ...) end end local function buttonEventHandler(workspace, button, e1, ...) if e1 == "touch" and (not button.animated or not button.animationStarted) then button:press(workspace, button, e1, ...) end end local function buttonGetColors(button) if button.disabled then return button.colors.disabled.background, button.colors.disabled.text else if button.animated and button.animationStarted then return button.animationCurrentBackground, button.animationCurrentText else if button.pressed then return button.colors.pressed.background, button.colors.pressed.text else return button.colors.default.background, button.colors.default.text end end end end local function buttonDrawText(button, textColor) screen.drawText(math.floor(button.x + button.width / 2 - unicode.wlen(button.text) / 2), math.floor(button.y + button.height / 2), textColor, button.text) end local function buttonDraw(button) local backgroundColor, textColor = buttonGetColors(button) if backgroundColor then screen.drawRectangle(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ", button.colors.transparency) end buttonDrawText(button, textColor) end local function framedButtonDraw(button) local backgroundColor, textColor = buttonGetColors(button) if backgroundColor then screen.drawFrame(button.x, button.y, button.width, button.height, backgroundColor) end buttonDrawText(button, textColor) end local function roundedButtonDraw(button) local backgroundColor, textColor = buttonGetColors(button) if backgroundColor then local x2, y2 = button.x + button.width - 1, button.y + button.height - 1 if button.height > 1 then screen.drawText(button.x + 1, button.y, backgroundColor, string.rep("▄", button.width - 2)) screen.drawText(button.x, button.y, backgroundColor, "⣠") screen.drawText(x2, button.y, backgroundColor, "⣄") screen.drawRectangle(button.x, button.y + 1, button.width, button.height - 2, backgroundColor, textColor, " ") screen.drawText(button.x + 1, y2, backgroundColor, string.rep("▀", button.width - 2)) screen.drawText(button.x, y2, backgroundColor, "⠙") screen.drawText(x2, y2, backgroundColor, "⠋") else screen.drawRectangle(button.x + 1, button.y, button.width - 2, button.height, backgroundColor, textColor, " ") GUI.drawRoundedCorners(button.x, button.y, button.width, button.height, backgroundColor) end end buttonDrawText(button, textColor) end local function tagButtonDraw(button) local backgroundColor, textColor = buttonGetColors(button) screen.drawRectangle(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ") screen.drawText(button.x - 1, button.y, backgroundColor, "◀") buttonDrawText(button, textColor) end local function buttonCreate(x, y, width, height, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text) local button = pressable(x, y, width, height, backgroundColor, textColor, backgroundPressedColor, textPressedColor, 0x878787, 0xA5A5A5, text) button.animationDuration = GUI.BUTTON_ANIMATION_DURATION button.animated = true button.animationCurrentBackground = backgroundColor button.animationCurrentText = textColor button.press = buttonPress button.eventHandler = buttonEventHandler return button end local function adaptiveButtonCreate(x, y, xOffset, yOffset, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text) return buttonCreate(x, y, unicode.wlen(text) + xOffset * 2, yOffset * 2 + 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text) end function GUI.button(...) local button = buttonCreate(...) button.draw = buttonDraw return button end function GUI.adaptiveButton(...) local button = adaptiveButtonCreate(...) button.draw = buttonDraw return button end function GUI.framedButton(...) local button = buttonCreate(...) button.draw = framedButtonDraw return button end function GUI.adaptiveFramedButton(...) local button = adaptiveButtonCreate(...) button.draw = framedButtonDraw return button end function GUI.roundedButton(...) local button = buttonCreate(...) button.draw = roundedButtonDraw return button end function GUI.adaptiveRoundedButton(...) local button = adaptiveButtonCreate(...) button.draw = roundedButtonDraw return button end function GUI.tagButton(...) local button = buttonCreate(...) button.draw = tagButtonDraw return button end function GUI.adaptiveTagButton(...) local button = adaptiveButtonCreate(...) button.draw = tagButtonDraw return button end -------------------------------------------------------------------------------- local function drawPanel(object) screen.drawRectangle(object.x, object.y, object.width, object.height, object.colors.background, 0x0, " ", object.colors.transparency) return object end function GUI.panel(x, y, width, height, color, transparency) local object = GUI.object(x, y, width, height) object.colors = { background = color, transparency = transparency } object.draw = drawPanel return object end -------------------------------------------------------------------------------- local function blurredPanelDraw(object) screen.blur(object.x, object.y, object.width, object.height, object.radius, object.color, object.transparency) end function GUI.blurredPanel(x, y, width, height, radius, color, transparency) local object = GUI.object(x, y, width, height) object.radius = radius or 3 object.color = color object.transparency = transparency object.draw = blurredPanelDraw return object end -------------------------------------------------------------------------------- local function drawLabel(object) local xText, yText = GUI.getAlignmentCoordinates( object.x, object.y, object.width, object.height, object.horizontalAlignment, object.verticalAlignment, unicode.wlen(object.text), 1 ) screen.drawText(math.floor(xText), math.floor(yText), object.colors.text, object.text) return object end function GUI.label(x, y, width, height, textColor, text) local object = GUI.object(x, y, width, height) object.setAlignment = GUI.setAlignment object:setAlignment(GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) object.colors = {text = textColor} object.text = text object.draw = drawLabel return object end -------------------------------------------------------------------------------- local function drawImage(object) screen.drawImage(object.x, object.y, object.image) return object end function GUI.image(x, y, image) local object = GUI.object(x, y, image[1], image[2]) object.image = image object.draw = drawImage return object end -------------------------------------------------------------------------------- function GUI.actionButtons(x, y, fatSymbol) local symbol = "●" local container = GUI.container(x, y, 5, 1) container.close = container:addChild(GUI.button(1, 1, 1, 1, nil, 0xFF4940, nil, 0x992400, symbol)) container.minimize = container:addChild(GUI.button(3, 1, 1, 1, nil, 0xFFB640, nil, 0x996D00, symbol)) container.maximize = container:addChild(GUI.button(5, 1, 1, 1, nil, 0x00B640, nil, 0x006D40, symbol)) return container end -------------------------------------------------------------------------------- local function drawProgressBar(object) local activeWidth = math.floor(math.min(object.value, 100) / 100 * object.width) if object.thin then screen.drawText(object.x, object.y, object.colors.passive, string.rep("━", object.width)) screen.drawText(object.x, object.y, object.colors.active, string.rep("━", activeWidth)) else screen.drawRectangle(object.x, object.y, object.width, object.height, object.colors.passive, 0x0, " ") screen.drawRectangle(object.x, object.y, activeWidth, object.height, object.colors.active, 0x0, " ") end if object.showValue then local stringValue = (object.valuePrefix or "") .. object.value .. (object.valuePostfix or "") screen.drawText(math.floor(object.x + object.width / 2 - unicode.wlen(stringValue) / 2), object.y + 1, object.colors.value, stringValue) end return object end function GUI.progressBar(x, y, width, activeColor, passiveColor, valueColor, value, thin, showValue, valuePrefix, valuePostfix) local object = GUI.object(x, y, width, 1) object.value = value object.colors = {active = activeColor, passive = passiveColor, value = valueColor} object.thin = thin object.draw = drawProgressBar object.showValue = showValue object.valuePrefix = valuePrefix object.valuePostfix = valuePostfix return object end -------------------------------------------------------------------------------- function GUI.drawShadow(x, y, width, height, transparency, thin) if thin then screen.drawRectangle(x + width, y + 1, 1, height - 1, 0x0, 0x0, " ", transparency) screen.drawText(x + 1, y + height, 0x0, string.rep("▀", width), transparency) screen.drawText(x + width, y, 0x0, "▄", transparency) else screen.drawRectangle(x + width, y + 1, 2, height, 0x0, 0x0, " ", transparency) screen.drawRectangle(x + 2, y + height, width - 2, 1, 0x0, 0x0, " ", transparency) end end function GUI.drawRoundedCorners(x, y, width, height, color, transparency) screen.drawText(x, y, color, "◖", transparency) screen.drawText(x + width - 1, y, color, "◗", transparency) end -------------------------------------------------------------------------------- function GUI.alert(...) local args = {...} for i = 1, #args do if type(args[i]) == "table" then args[i] = text.serialize(args[i], true) else args[i] = tostring(args[i]) end end if #args == 0 then args[1] = "nil" end local sign = image.fromString([[06030000FF 0000FF 00F7FF▟00F7FF▙0000FF 0000FF 0000FF 00F7FF▟F7FF00 F7FF00 00F7FF▙0000FF 00F7FF▟F7FF00CF7FF00yF7FF00kF7FF00a00F7FF▙]]) local offset = 2 local lines = #args > 1 and "\"" .. table.concat(args, "\", \"") .. "\"" or args[1] local bufferWidth, bufferHeight = screen.getResolution() local width = math.floor(bufferWidth * 0.5) local textWidth = width - image.getWidth(sign) - 2 lines = text.wrap(lines, textWidth) local height = image.getHeight(sign) if #lines + 2 > height then height = #lines + 2 end local workspace = GUI.workspace(1, math.floor(bufferHeight / 2 - height / 2), bufferWidth, height + offset * 2) local oldPixels = screen.copy(workspace.x, workspace.y, workspace.width, workspace.height) local x, y = math.floor(bufferWidth / 2 - width / 2), offset + 1 workspace:addChild(GUI.panel(1, 1, workspace.width, workspace.height, 0x1D1D1D)) workspace:addChild(GUI.image(x, y, sign)) workspace:addChild(GUI.textBox(x + image.getWidth(sign) + 2, y, textWidth, #lines, 0x1D1D1D, 0xE1E1E1, lines, 1, 0, 0)).eventHandler = nil local buttonWidth = 10 local button = workspace:addChild(GUI.roundedButton(x + image.getWidth(sign) + textWidth - buttonWidth + 2, workspace.height - offset, buttonWidth, 1, 0x3366CC, 0xE1E1E1, 0xE1E1E1, 0x3366CC, "OK")) button.onTouch = function() workspace:stop() screen.paste(workspace.x, workspace.y, oldPixels) screen.update() end workspace.eventHandler = function(workspace, object, e1, e2, e3, e4, ...) if e1 == "key_down" and e4 == 28 then button.animated = false button:press(workspace, object, e1, e2, e3, e4, ...) end end workspace:draw(true) workspace:start() end -------------------------------------------------------------------------------- local function codeViewDraw(codeView) local y, toLine, colorScheme, patterns = codeView.y, codeView.fromLine + codeView.height - 1, codeView.syntaxColorScheme, codeView.syntaxPatterns -- Line numbers bar and code area codeView.lineNumbersWidth = unicode.wlen(tostring(toLine)) + 2 codeView.codeAreaPosition = codeView.x + codeView.lineNumbersWidth codeView.codeAreaWidth = codeView.width - codeView.lineNumbersWidth -- Line numbers screen.drawRectangle(codeView.x, y, codeView.lineNumbersWidth, codeView.height, colorScheme.lineNumbersBackground, colorScheme.lineNumbersText, " ") -- Background screen.drawRectangle(codeView.codeAreaPosition, y, codeView.codeAreaWidth, codeView.height, colorScheme.background, colorScheme.text, " ") -- Line numbers texts local text for line = codeView.fromLine, toLine do if codeView.lines[line] then text = line .. "" if codeView.highlights[line] then screen.drawRectangle(codeView.x, y, codeView.lineNumbersWidth, 1, codeView.highlights[line], colorScheme.text, " ", 0.3) screen.drawRectangle(codeView.codeAreaPosition, y, codeView.codeAreaWidth, 1, codeView.highlights[line], colorScheme.text, " ") end screen.drawText(codeView.codeAreaPosition - unicode.wlen(text) - 1, y, colorScheme.lineNumbersText, text) y = y + 1 else break end end if #codeView.selections > 0 then local function drawUpperSelection(y, selectionIndex) screen.drawRectangle( math.max(codeView.codeAreaPosition, codeView.codeAreaPosition + codeView.selections[selectionIndex].from.symbol - codeView.fromSymbol + 1), y + codeView.selections[selectionIndex].from.line - codeView.fromLine, codeView.codeAreaWidth - codeView.selections[selectionIndex].from.symbol + codeView.fromSymbol - 1, 1, codeView.selections[selectionIndex].color or colorScheme.selection, colorScheme.text, " " ) end local function drawLowerSelection(y, selectionIndex) screen.drawRectangle( codeView.codeAreaPosition, y + codeView.selections[selectionIndex].from.line - codeView.fromLine, codeView.selections[selectionIndex].to.symbol - codeView.fromSymbol + 2, 1, codeView.selections[selectionIndex].color or colorScheme.selection, colorScheme.text, " " ) end for selectionIndex = 1, #codeView.selections do y = codeView.y local dy = codeView.selections[selectionIndex].to.line - codeView.selections[selectionIndex].from.line if dy == 0 then screen.drawRectangle( codeView.codeAreaPosition + codeView.selections[selectionIndex].from.symbol - codeView.fromSymbol + 1, y + codeView.selections[selectionIndex].from.line - codeView.fromLine, codeView.selections[selectionIndex].to.symbol - codeView.selections[selectionIndex].from.symbol + 1, 1, codeView.selections[selectionIndex].color or colorScheme.selection, colorScheme.text, " " ) elseif dy == 1 then drawUpperSelection(y, selectionIndex) y = y + 1 drawLowerSelection(y, selectionIndex) else drawUpperSelection(y, selectionIndex) y = y + 1 for i = 1, dy - 1 do screen.drawRectangle( codeView.codeAreaPosition, y + codeView.selections[selectionIndex].from.line - codeView.fromLine, codeView.codeAreaWidth, 1, codeView.selections[selectionIndex].color or colorScheme.selection, colorScheme.text, " " ) y = y + 1 end drawLowerSelection(y, selectionIndex) end end end -- Code strings y = codeView.y for i = codeView.fromLine, toLine do if codeView.lines[i] then if codeView.syntaxHighlight then GUI.highlightString( codeView.codeAreaPosition + 1, y, codeView.fromSymbol, codeView.indentationWidth, patterns, colorScheme, codeView.lines[i] ) else screen.drawText( codeView.codeAreaPosition + 1, y, colorScheme.text, unicode.sub( codeView.lines[i], codeView.fromSymbol, codeView.fromSymbol + codeView.codeAreaWidth - 3 ) ) end y = y + 1 else break end end -- Scrollbars if #codeView.lines > codeView.height then codeView.verticalScrollBar.colors.background, codeView.verticalScrollBar.colors.foreground = colorScheme.scrollBarBackground, colorScheme.scrollBarForeground codeView.verticalScrollBar.minimumValue, codeView.verticalScrollBar.maximumValue, codeView.verticalScrollBar.value, codeView.verticalScrollBar.shownValueCount = 1, #codeView.lines, codeView.fromLine, codeView.height codeView.verticalScrollBar.localX = codeView.width codeView.verticalScrollBar.localY = 1 codeView.verticalScrollBar.height = codeView.height - 1 codeView.verticalScrollBar.hidden = false else codeView.verticalScrollBar.hidden = true end if codeView.maximumLineLength > codeView.codeAreaWidth - 2 then codeView.horizontalScrollBar.colors.background, codeView.horizontalScrollBar.colors.foreground = colorScheme.scrollBarBackground, colorScheme.scrollBarForeground codeView.horizontalScrollBar.minimumValue, codeView.horizontalScrollBar.maximumValue, codeView.horizontalScrollBar.value, codeView.horizontalScrollBar.shownValueCount = 1, codeView.maximumLineLength, codeView.fromSymbol, codeView.codeAreaWidth - 2 codeView.horizontalScrollBar.localX = codeView.lineNumbersWidth + 1 codeView.horizontalScrollBar.localY = codeView.height codeView.horizontalScrollBar.width = codeView.codeAreaWidth - 1 codeView.horizontalScrollBar.hidden = false else codeView.horizontalScrollBar.hidden = true end codeView:overrideDraw() end function GUI.codeView(x, y, width, height, fromSymbol, fromLine, maximumLineLength, selections, highlights, syntaxPatterns, syntaxColorScheme, syntaxHighlight, lines) local codeView = GUI.container(x, y, width, height) codeView.blockScreenEvents = true codeView.lines = lines codeView.fromSymbol = fromSymbol codeView.fromLine = fromLine codeView.maximumLineLength = maximumLineLength codeView.selections = selections or {} codeView.highlights = highlights or {} codeView.syntaxHighlight = syntaxHighlight codeView.syntaxPatterns = syntaxPatterns codeView.syntaxColorScheme = syntaxColorScheme codeView.indentationWidth = 2 codeView.verticalScrollBar = codeView:addChild(GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1, true)) codeView.horizontalScrollBar = codeView:addChild(GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1, true)) codeView.overrideDraw = codeView.draw codeView.draw = codeViewDraw return codeView end -------------------------------------------------------------------------------- local function colorSelectorDraw(colorSelector) local overlayColor = colorSelector.color < 0x7FFFFF and 0xFFFFFF or 0x0 screen.drawRectangle( colorSelector.x, colorSelector.y, colorSelector.width, colorSelector.height, colorSelector.pressed and color.blend(colorSelector.color, overlayColor, 0.8) or colorSelector.color, overlayColor, " " ) if colorSelector.height > 1 and colorSelector.drawLine then screen.drawText(colorSelector.x, colorSelector.y + colorSelector.height - 1, overlayColor, string.rep("▄", colorSelector.width), 0.8) end screen.drawText(math.ceil(colorSelector.x + colorSelector.height / 2), colorSelector.y + math.floor(colorSelector.height / 2), overlayColor, text.limit(colorSelector.text, colorSelector.width - 2)) return colorSelector end local function colorSelectorEventHandler(workspace, object, e1, ...) if e1 == "touch" then local eventData = {...} object.pressed = true local palette = workspace:addChild(GUI.palette(1, 1, object.color)) palette.localX, palette.localY = math.floor(workspace.width / 2 - palette.width / 2), math.floor(workspace.height / 2 - palette.height / 2) palette.cancelButton.onTouch = function() object.pressed = false palette:remove() workspace:draw() if object.onColorSelected then object.onColorSelected(workspace, object, e1, table.unpack(eventData)) end end palette.submitButton.onTouch = function() object.color = palette.color.integer palette.cancelButton.onTouch() end workspace:draw() end end function GUI.colorSelector(x, y, width, height, color, text) local colorSelector = GUI.object(x, y, width, height) colorSelector.drawLine = true colorSelector.eventHandler = colorSelectorEventHandler colorSelector.color = color colorSelector.text = text colorSelector.draw = colorSelectorDraw return colorSelector end -------------------------------------------------------------------------------- local function getAxisValue(num, postfix, roundValues) if roundValues then return math.floor(num) .. postfix else local integer, fractional = math.modf(num) local firstPart, secondPart = "", "" if math.abs(integer) >= 1000 then return number.shorten(integer, 2) .. postfix else if math.abs(fractional) > 0 then return string.format("%.2f", num) .. postfix else return num .. postfix end end end end local function drawChart(object) -- Sorting by x value local valuesCopy = {} for i = 1, #object.values do valuesCopy[i] = object.values[i] end table.sort(valuesCopy, function(a, b) return a[1] < b[1] end) if #valuesCopy == 0 then valuesCopy = {{0, 0}} end -- Max, min, deltas local xMin, xMax, yMin, yMax = valuesCopy[1][1], valuesCopy[#valuesCopy][1], valuesCopy[1][2], valuesCopy[1][2] for i = 1, #valuesCopy do yMin, yMax = math.min(yMin, valuesCopy[i][2]), math.max(yMax, valuesCopy[i][2]) end local dx, dy = xMax - xMin, yMax - yMin -- y axis values and helpers local value, chartHeight, yAxisValueMaxWidth, yAxisValues = yMin, object.height - 1 - (object.showXAxisValues and 1 or 0), 0, {} for y = object.y + object.height - 3, object.y + 1, -chartHeight * object.yAxisValueInterval do local stringValue = getAxisValue(value, object.yAxisPostfix, object.roundValues) yAxisValueMaxWidth = math.max(yAxisValueMaxWidth, unicode.wlen(stringValue)) table.insert(yAxisValues, {y = math.ceil(y), value = stringValue}) value = value + dy * object.yAxisValueInterval end local stringValue = getAxisValue(yMax, object.yAxisPostfix, object.roundValues) table.insert(yAxisValues, {y = object.y, value = stringValue}) yAxisValueMaxWidth = math.max(yAxisValueMaxWidth, unicode.wlen(stringValue)) local chartWidth = object.width - (object.showYAxisValues and yAxisValueMaxWidth + 2 or 0) local chartX = object.x + object.width - chartWidth for i = 1, #yAxisValues do if object.showYAxisValues then screen.drawText(chartX - unicode.wlen(yAxisValues[i].value) - 2, yAxisValues[i].y, object.colors.axisValue, yAxisValues[i].value) end screen.drawText(chartX, yAxisValues[i].y, object.colors.helpers, string.rep("─", chartWidth)) end -- x axis values if object.showXAxisValues then value = xMin for x = chartX, chartX + chartWidth - 2, chartWidth * object.xAxisValueInterval do local stringValue = getAxisValue(value, object.xAxisPostfix, object.roundValues) screen.drawText(math.floor(x - unicode.wlen(stringValue) / 2), object.y + object.height - 1, object.colors.axisValue, stringValue) value = value + dx * object.xAxisValueInterval end local value = getAxisValue(xMax, object.xAxisPostfix, object.roundValues) screen.drawText(object.x + object.width - unicode.wlen(value), object.y + object.height - 1, object.colors.axisValue, value) end -- Axis lines for y = object.y, object.y + chartHeight - 1 do screen.drawText(chartX - 1, y, object.colors.axis, "┨") end screen.drawText(chartX - 1, object.y + chartHeight, object.colors.axis, "┗" .. string.rep("┯━", math.floor(chartWidth / 2))) local function fillVerticalPart(x1, y1, x2, y2) local dx, dy = x2 - x1, y2 - y1 local absdx, absdy = math.abs(dx), math.abs(dy) if absdx >= absdy then local step, y = dy / absdx, y1 for x = x1, x2, (x1 < x2 and 1 or -1) do local yFloor = math.floor(y) screen.drawSemiPixelRectangle(math.floor(x), yFloor, 1, math.floor(object.y + chartHeight) * 2 - yFloor - 1, object.colors.chart) y = y + step end else local step, x = dx / absdy, x1 for y = y1, y2, (y1 < y2 and 1 or -1) do local yFloor = math.floor(y) screen.drawSemiPixelRectangle(math.floor(x), yFloor, 1, math.floor(object.y + chartHeight) * 2 - yFloor - 1, object.colors.chart) x = x + step end end end -- chart for i = 1, #valuesCopy - 1 do local x = math.floor(chartX + (valuesCopy[i][1] - xMin) / dx * (chartWidth - 1)) local y = math.floor(object.y + chartHeight - 1 - (valuesCopy[i][2] - yMin) / dy * (chartHeight - 1)) * 2 local xNext = math.floor(chartX + (valuesCopy[i + 1][1] - xMin) / dx * (chartWidth - 1)) local yNext = math.floor(object.y + chartHeight - 1 - (valuesCopy[i + 1][2] - yMin) / dy * (chartHeight - 1)) * 2 if object.fillChartArea then fillVerticalPart(x, y, xNext, yNext) else screen.drawSemiPixelLine(x, y, xNext, yNext, object.colors.chart) end end return object end function GUI.chart(x, y, width, height, axisColor, axisValueColor, axisHelpersColor, chartColor, xAxisValueInterval, yAxisValueInterval, xAxisPostfix, yAxisPostfix, fillChartArea, values) local object = GUI.object(x, y, width, height) object.colors = {axis = axisColor, chart = chartColor, axisValue = axisValueColor, helpers = axisHelpersColor} object.draw = drawChart object.values = values or {} object.xAxisPostfix = xAxisPostfix object.yAxisPostfix = yAxisPostfix object.xAxisValueInterval = xAxisValueInterval object.yAxisValueInterval = yAxisValueInterval object.fillChartArea = fillChartArea object.showYAxisValues = true object.showXAxisValues = true return object end -------------------------------------------------------------------------------- local function switchAndLabelDraw(switchAndLabel) switchAndLabel.label.width = switchAndLabel.width switchAndLabel.switch.localX = switchAndLabel.width - switchAndLabel.switch.width + 1 switchAndLabel.label.x, switchAndLabel.label.y = switchAndLabel.x + switchAndLabel.label.localX - 1, switchAndLabel.y + switchAndLabel.label.localY - 1 switchAndLabel.switch.x, switchAndLabel.switch.y = switchAndLabel.x + switchAndLabel.switch.localX - 1, switchAndLabel.y + switchAndLabel.switch.localY - 1 switchAndLabel.label:draw() switchAndLabel.switch:draw() return switchAndLabel end function GUI.switchAndLabel(x, y, width, switchWidth, activeColor, passiveColor, pipeColor, textColor, text, switchState) local switchAndLabel = GUI.container(x, y, width, 1) switchAndLabel.label = switchAndLabel:addChild(GUI.label(1, 1, width, 1, textColor, text)) switchAndLabel.switch = switchAndLabel:addChild(GUI.switch(1, 1, switchWidth, activeColor, passiveColor, pipeColor, switchState)) switchAndLabel.draw = switchAndLabelDraw return switchAndLabel end -------------------------------------------------------------------------------- local function sliderDraw(object) -- На всякий случай делаем значение не меньше минимального и не больше максимального object.value = math.min(math.max(object.value, object.minimumValue), object.maximumValue) if object.showMaximumAndMinimumValues then 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)) screen.drawText(object.x - unicode.wlen(stringMinimumValue) - 1, object.y, object.colors.value, stringMinimumValue) screen.drawText(object.x + object.width + 1, object.y, object.colors.value, stringMaximumValue) end if object.currentValuePrefix or object.currentValuePostfix then local stringCurrentValue = (object.currentValuePrefix or "") .. (object.roundValues and math.floor(object.value) or number.roundToDecimalPlaces(object.value, 2)) .. (object.currentValuePostfix or "") screen.drawText(math.floor(object.x + object.width / 2 - unicode.wlen(stringCurrentValue) / 2), object.y + 1, object.colors.value, stringCurrentValue) end local activeWidth = number.round((object.value - object.minimumValue) / (object.maximumValue - object.minimumValue) * object.width) screen.drawText(object.x, object.y, object.colors.passive, string.rep("━", object.width)) screen.drawText(object.x, object.y, object.colors.active, string.rep("━", activeWidth)) screen.drawText(activeWidth >= object.width and object.x + activeWidth - 1 or object.x + activeWidth, object.y, object.colors.pipe, "⬤") return object end local function sliderEventHandler(workspace, object, e1, e2, e3, e4, e5, ...) if e1 == "touch" or e1 == "drag" then local clickPosition = e3 - object.x if clickPosition <= 0 then object.value = object.minimumValue elseif clickPosition >= object.width - 1 then object.value = object.maximumValue else object.value = object.minimumValue + (clickPosition / object.width * (object.maximumValue - object.minimumValue)) end workspace:draw() if object.onValueChanged then object.onValueChanged(workspace, object, e1, e2, e3, e4, e5, ...) end -- Sucks on scrollable views -- elseif e1 == "scroll" then -- object.value = object.value + (object.maximumValue - object.minimumValue) * object.scrollSensivity * e5 -- if object.value > object.maximumValue then -- object.value = object.maximumValue -- elseif object.value < object.minimumValue then -- object.value = object.minimumValue -- end -- workspace:draw() -- if object.onValueChanged then -- object.onValueChanged(workspace, object, e1, e2, e3, e4, e5, ...) -- end end end function GUI.slider(x, y, width, activeColor, passiveColor, pipeColor, valueColor, minimumValue, maximumValue, value, showMaximumAndMinimumValues, currentValuePrefix, currentValuePostfix) local object = GUI.object(x, y, width, 1) object.eventHandler = sliderEventHandler object.colors = {active = activeColor, passive = passiveColor, pipe = pipeColor, value = valueColor} object.draw = sliderDraw object.minimumValue = minimumValue object.maximumValue = maximumValue object.value = value object.scrollSensivity = 0.05 object.showMaximumAndMinimumValues = showMaximumAndMinimumValues object.currentValuePrefix = currentValuePrefix object.currentValuePostfix = currentValuePostfix object.roundValues = false return object end -------------------------------------------------------------------------------- local function switchDraw(switch) -- ◖◗ -- ⠆⠰ -- Sides if switch.pipePosition > 1 then screen.drawText(switch.x, switch.y, switch.colors.active, "◖") end if switch.pipePosition < switch.width - 1 then screen.drawText(switch.x + switch.width - 1, switch.y, switch.colors.passive, "◗") end -- Background if switch.width > 2 then -- Active local width = switch.pipePosition - 1 if width > 0 then screen.drawRectangle(switch.x + 1, switch.y, width, 1, switch.colors.active, 0x0, " ") end -- Passive width = switch.width - switch.pipePosition - 1 if width > 0 then screen.drawRectangle(switch.x + switch.pipePosition, switch.y, width, 1, switch.colors.passive, 0x0, " ") end end -- Pipe screen.drawText(switch.x + switch.pipePosition - 1, switch.y, switch.colors.pipe, "◖") screen.set(switch.x + switch.pipePosition, switch.y, switch.colors.pipe, switch.colors.pipe, " ") screen.drawText(switch.x + switch.pipePosition + 1, switch.y, switch.colors.pipe, "◗") return switch end local function switchSetState(switch, state) switch.state = state switch.pipePosition = switch.state and switch.width - 2 or 1 return switch end local function switchEventHandler(workspace, switch, e1, ...) if e1 == "touch" then local eventData = {...} switch.state = not switch.state switch:addAnimation( function(animation) if switch.state then switch.pipePosition = number.round(1 + animation.position * (switch.width - 3)) else switch.pipePosition = number.round(1 + (1 - animation.position) * (switch.width - 3)) end end, function(animation) animation:remove() if switch.onStateChanged then switch.onStateChanged(switch, e1, table.unpack(eventData)) end end ):start(switch.animationDuration) end end function GUI.switch(x, y, width, activeColor, passiveColor, pipeColor, state) local switch = GUI.object(x, y, width, 1) switch.pipePosition = 1 switch.eventHandler = switchEventHandler switch.colors = { active = activeColor, passive = passiveColor, pipe = pipeColor, } switch.draw = switchDraw switch.state = state or false switch.animated = true switch.animationDuration = GUI.SWITCH_ANIMATION_DURATION switch.setState = switchSetState switch:setState(state) return switch end -------------------------------------------------------------------------------- local function layoutCheckCell(layout, column, row) if column < 1 or column > #layout.columnSizes or row < 1 or row > #layout.rowSizes then error("Specified grid position (" .. tostring(column) .. "x" .. tostring(row) .. ") is out of layout grid range") end end local function layoutGetAbsoluteTotalSize(array) local absoluteTotalSize = 0 for i = 1, #array do if array[i].sizePolicy == GUI.SIZE_POLICY_ABSOLUTE then absoluteTotalSize = absoluteTotalSize + array[i].size end end return absoluteTotalSize end local function layoutGetCalculatedSize(array, index, dependency) if array[index].sizePolicy == GUI.SIZE_POLICY_RELATIVE then array[index].calculatedSize = array[index].size * dependency else array[index].calculatedSize = array[index].size end end local function layoutUpdate(layout) local columnPercentageTotalSize, rowPercentageTotalSize = layout.width - layoutGetAbsoluteTotalSize(layout.columnSizes), layout.height - layoutGetAbsoluteTotalSize(layout.rowSizes) for row = 1, #layout.rowSizes do layoutGetCalculatedSize(layout.rowSizes, row, rowPercentageTotalSize) for column = 1, #layout.columnSizes do layoutGetCalculatedSize(layout.columnSizes, column, columnPercentageTotalSize) layout.cells[row][column].childrenWidth, layout.cells[row][column].childrenHeight = 0, 0 end end -- Подготавливаем объекты к расположению и подсчитываем тотальные размеры local child, layoutRow, layoutColumn, cell for i = 1, #layout.children do child = layout.children[i] if not child.hidden then layoutRow, layoutColumn = child.layoutRow, child.layoutColumn -- Проверка на позицию в сетке if layoutRow >= 1 and layoutRow <= #layout.rowSizes and layoutColumn >= 1 and layoutColumn <= #layout.columnSizes then cell = layout.cells[layoutRow][layoutColumn] -- Авто-фиттинг объектов if cell.horizontalFitting then child.width = number.round(layout.columnSizes[layoutColumn].calculatedSize - cell.horizontalFittingRemove) end if cell.verticalFitting then child.height = number.round(layout.rowSizes[layoutRow].calculatedSize - cell.verticalFittingRemove) end -- Направление и расчет размеров if cell.direction == GUI.DIRECTION_HORIZONTAL then cell.childrenWidth = cell.childrenWidth + child.width + cell.spacing cell.childrenHeight = math.max(cell.childrenHeight, child.height) else cell.childrenWidth = math.max(cell.childrenWidth, child.width) cell.childrenHeight = cell.childrenHeight + child.height + cell.spacing end else error("Layout child with index " .. i .. " has been assigned to cell (" .. layoutColumn .. "x" .. layoutRow .. ") out of layout grid range") end end end -- Высчитываем стартовую позицию объектов ячейки local x, y = 1, 1 for row = 1, #layout.rowSizes do for column = 1, #layout.columnSizes do cell = layout.cells[row][column] cell.x, cell.y = GUI.getAlignmentCoordinates( x, y, layout.columnSizes[column].calculatedSize, layout.rowSizes[row].calculatedSize, cell.horizontalAlignment, cell.verticalAlignment, cell.childrenWidth - (cell.direction == GUI.DIRECTION_HORIZONTAL and cell.spacing or 0), cell.childrenHeight - (cell.direction == GUI.DIRECTION_VERTICAL and cell.spacing or 0) ) -- Учитываем отступы от краев ячейки if cell.horizontalMargin ~= 0 or cell.verticalMargin ~= 0 then cell.x, cell.y = GUI.getMarginCoordinates( cell.x, cell.y, cell.horizontalAlignment, cell.verticalAlignment, cell.horizontalMargin, cell.verticalMargin ) end x = x + layout.columnSizes[column].calculatedSize end x, y = 1, y + layout.rowSizes[row].calculatedSize end -- Размещаем все объекты for i = 1, #layout.children do child = layout.children[i] if not child.hidden then cell = layout.cells[child.layoutRow][child.layoutColumn] child.localX, cell.localY = GUI.getAlignmentCoordinates( cell.x, cell.y, cell.childrenWidth, cell.childrenHeight, cell.horizontalAlignment, cell.verticalAlignment, child.width, child.height ) if cell.direction == GUI.DIRECTION_HORIZONTAL then child.localX, child.localY = math.floor(cell.x), math.floor(cell.localY) cell.x = cell.x + child.width + cell.spacing else child.localX, child.localY = math.floor(child.localX), math.floor(cell.y) cell.y = cell.y + child.height + cell.spacing end end end end local function layoutSetPosition(layout, column, row, object) layoutCheckCell(layout, column, row) object.layoutRow = row object.layoutColumn = column return object end local function layoutSetDirection(layout, column, row, direction) layoutCheckCell(layout, column, row) layout.cells[row][column].direction = direction return layout end local function layoutSetSpacing(layout, column, row, spacing) layoutCheckCell(layout, column, row) layout.cells[row][column].spacing = spacing return layout end local function layoutSetAlignment(layout, column, row, horizontalAlignment, verticalAlignment) layoutCheckCell(layout, column, row) layout.cells[row][column].horizontalAlignment, layout.cells[row][column].verticalAlignment = horizontalAlignment, verticalAlignment return layout end local function layoutGetMargin(layout, column, row) layoutCheckCell(layout, column, row) return layout.cells[row][column].horizontalMargin, layout.cells[row][column].verticalMargin end local function layoutSetMargin(layout, column, row, horizontalMargin, verticalMargin) layoutCheckCell(layout, column, row) layout.cells[row][column].horizontalMargin = horizontalMargin layout.cells[row][column].verticalMargin = verticalMargin return layout end local function layoutNewCell() return { horizontalAlignment = GUI.ALIGNMENT_HORIZONTAL_CENTER, verticalAlignment = GUI.ALIGNMENT_VERTICAL_CENTER, horizontalMargin = 0, verticalMargin = 0, direction = GUI.DIRECTION_VERTICAL, spacing = 1 } end local function layoutCalculatePercentageSize(changingExistent, array, index) if array[index].sizePolicy == GUI.SIZE_POLICY_RELATIVE then local allPercents, beforeFromIndexPercents = 0, 0 for i = 1, #array do if array[i].sizePolicy == GUI.SIZE_POLICY_RELATIVE then allPercents = allPercents + array[i].size if i <= index then beforeFromIndexPercents = beforeFromIndexPercents + array[i].size end end end local modifyer if changingExistent then if beforeFromIndexPercents > 1 then error("Layout summary percentage > 100% at index " .. index) end modifyer = (1 - beforeFromIndexPercents) / (allPercents - beforeFromIndexPercents) else modifyer = (1 - array[index].size) / (allPercents - array[index].size) end for i = changingExistent and index + 1 or 1, #array do if array[i].sizePolicy == GUI.SIZE_POLICY_RELATIVE and i ~= index then array[i].size = modifyer * array[i].size end end end end local function layoutSetColumnWidth(layout, column, sizePolicy, size) layout.columnSizes[column].sizePolicy, layout.columnSizes[column].size = sizePolicy, size layoutCalculatePercentageSize(true, layout.columnSizes, column) return layout end local function layoutSetRowHeight(layout, row, sizePolicy, size) layout.rowSizes[row].sizePolicy, layout.rowSizes[row].size = sizePolicy, size layoutCalculatePercentageSize(true, layout.rowSizes, row) return layout end local function layoutAddColumn(layout, sizePolicy, size) for i = 1, #layout.rowSizes do table.insert(layout.cells[i], layoutNewCell()) end table.insert(layout.columnSizes, { sizePolicy = sizePolicy, size = size }) layoutCalculatePercentageSize(false, layout.columnSizes, #layout.columnSizes) return layout end local function layoutAddRow(layout, sizePolicy, size) local row = {} for i = 1, #layout.columnSizes do table.insert(row, layoutNewCell()) end table.insert(layout.cells, row) table.insert(layout.rowSizes, { sizePolicy = sizePolicy, size = size }) layoutCalculatePercentageSize(false, layout.rowSizes, #layout.rowSizes) return layout end local function layoutRemoveRow(layout, row) table.remove(layout.cells, row) layout.rowSizes[row].size = 0 layoutCalculatePercentageSize(false, layout.rowSizes, row) table.remove(layout.rowSizes, row) return layout end local function layoutRemoveColumn(layout, column) for i = 1, #layout.rowSizes do table.remove(layout.cells[i], column) end layout.columnSizes[column].size = 0 layoutCalculatePercentageSize(false, layout.columnSizes, column) table.remove(layout.columnSizes, column) return layout end local function layoutSetGridSize(layout, columnCount, rowCount) layout.cells = {} layout.rowSizes = {} layout.columnSizes = {} local rowSize, columnSize = 1 / rowCount, 1 / columnCount for i = 1, rowCount do layoutAddRow(layout, GUI.SIZE_POLICY_RELATIVE, 1 / i) end for i = 1, columnCount do layoutAddColumn(layout, GUI.SIZE_POLICY_RELATIVE, 1 / i) end return layout end local function layoutDraw(layout) layout:update() containerDraw(layout) if layout.showGrid then local x, y = layout.x, layout.y for j = 1, #layout.columnSizes do for i = 1, #layout.rowSizes do screen.drawFrame( number.round(x), number.round(y), number.round(layout.columnSizes[j].calculatedSize), number.round(layout.rowSizes[i].calculatedSize), 0xFF0000 ) y = y + layout.rowSizes[i].calculatedSize end x, y = x + layout.columnSizes[j].calculatedSize, layout.y end end end local function layoutFitToChildrenSize(layout, column, row) layout.width, layout.height = 0, 0 for i = 1, #layout.children do if not layout.children[i].hidden then if layout.cells[row][column].direction == GUI.DIRECTION_HORIZONTAL then layout.width = layout.width + layout.children[i].width + layout.cells[row][column].spacing layout.height = math.max(layout.height, layout.children[i].height) else layout.width = math.max(layout.width, layout.children[i].width) layout.height = layout.height + layout.children[i].height + layout.cells[row][column].spacing end end end if layout.cells[row][column].direction == GUI.DIRECTION_HORIZONTAL then layout.width = layout.width - layout.cells[row][column].spacing else layout.height = layout.height - layout.cells[row][column].spacing end return layout end local function layoutSetFitting(layout, column, row, horizontal, vertical, horizontalRemove, verticalRemove) layoutCheckCell(layout, column, row) layout.cells[row][column].horizontalFitting = horizontal layout.cells[row][column].verticalFitting = vertical layout.cells[row][column].horizontalFittingRemove = horizontalRemove or 0 layout.cells[row][column].verticalFittingRemove = verticalRemove or 0 return layout end local function layoutAddChild(layout, object, ...) object.layoutRow = layout.defaultRow object.layoutColumn = layout.defaultColumn containerAddChild(layout, object, ...) return object end function GUI.layout(x, y, width, height, columnCount, rowCount) local layout = GUI.container(x, y, width, height) layout.defaultRow = 1 layout.defaultColumn = 1 layout.addRow = layoutAddRow layout.addColumn = layoutAddColumn layout.removeRow = layoutRemoveRow layout.removeColumn = layoutRemoveColumn layout.setRowHeight = layoutSetRowHeight layout.setColumnWidth = layoutSetColumnWidth layout.setPosition = layoutSetPosition layout.setDirection = layoutSetDirection layout.setGridSize = layoutSetGridSize layout.setSpacing = layoutSetSpacing layout.setAlignment = layoutSetAlignment layout.setMargin = layoutSetMargin layout.getMargin = layoutGetMargin layout.fitToChildrenSize = layoutFitToChildrenSize layout.setFitting = layoutSetFitting layout.update = layoutUpdate layout.addChild = layoutAddChild layout.draw = layoutDraw layout:setGridSize(columnCount, rowCount) return layout end -------------------------------------------------------------------------------- local function filesystemDialogDraw(filesystemDialog) if filesystemDialog.extensionComboBox.hidden then filesystemDialog.input.width = filesystemDialog.cancelButton.localX - 4 else filesystemDialog.input.width = filesystemDialog.extensionComboBox.localX - 3 end if filesystemDialog.IOMode == GUI.IO_MODE_SAVE then filesystemDialog.submitButton.disabled = not filesystemDialog.input.text or filesystemDialog.input.text == "" else filesystemDialog.input.text = filesystemDialog.filesystemTree.selectedItem or "" filesystemDialog.submitButton.disabled = not filesystemDialog.filesystemTree.selectedItem end containerDraw(filesystemDialog) GUI.drawShadow(filesystemDialog.x, filesystemDialog.y, filesystemDialog.width, filesystemDialog.height, GUI.CONTEXT_MENU_SHADOW_TRANSPARENCY, true) return filesystemDialog end local function filesystemDialogSetMode(filesystemDialog, IOMode, filesystemMode) filesystemDialog.IOMode = IOMode filesystemDialog.filesystemMode = filesystemMode if filesystemDialog.IOMode == GUI.IO_MODE_SAVE then filesystemDialog.filesystemTree.showMode = GUI.IO_MODE_DIRECTORY filesystemDialog.filesystemTree.selectionMode = GUI.IO_MODE_DIRECTORY filesystemDialog.input.disabled = false filesystemDialog.extensionComboBox.hidden = filesystemDialog.filesystemMode ~= GUI.IO_MODE_FILE or not filesystemDialog.filesystemTree.extensionFilters else if filesystemDialog.filesystemMode == GUI.IO_MODE_FILE then filesystemDialog.filesystemTree.showMode = GUI.IO_MODE_BOTH filesystemDialog.filesystemTree.selectionMode = GUI.IO_MODE_FILE else filesystemDialog.filesystemTree.showMode = GUI.IO_MODE_DIRECTORY filesystemDialog.filesystemTree.selectionMode = GUI.IO_MODE_DIRECTORY end filesystemDialog.input.disabled = true filesystemDialog.extensionComboBox.hidden = true end end local function filesystemDialogAddExtensionFilter(filesystemDialog, extension) filesystemDialog.extensionComboBox:addItem(extension) filesystemDialog.extensionComboBox.width = math.max(filesystemDialog.extensionComboBox.width, unicode.wlen(extension) + 3) filesystemDialog.extensionComboBox.localX = filesystemDialog.cancelButton.localX - filesystemDialog.extensionComboBox.width - 2 filesystemDialog.filesystemTree:addExtensionFilter(extension) filesystemDialog:setMode(filesystemDialog.IOMode, filesystemDialog.filesystemMode) end local function filesystemDialogExpandPath(filesystemDialog, ...) filesystemDialog.filesystemTree:expandPath(...) end function GUI.filesystemDialog(x, y, width, height, submitButtonText, cancelButtonText, placeholderText, path) local filesystemDialog = GUI.container(x, y, width, height) filesystemDialog:addChild(GUI.panel(1, height - 2, width, 3, 0xD2D2D2)) filesystemDialog.cancelButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 1, 0, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xE1E1E1, cancelButtonText)) filesystemDialog.submitButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 1, 0, 0x3C3C3C, 0xE1E1E1, 0xE1E1E1, 0x3C3C3C, submitButtonText)) filesystemDialog.submitButton.localX = filesystemDialog.width - filesystemDialog.submitButton.width - 1 filesystemDialog.cancelButton.localX = filesystemDialog.submitButton.localX - filesystemDialog.cancelButton.width - 2 filesystemDialog.extensionComboBox = filesystemDialog:addChild(GUI.comboBox(1, height - 1, 1, 1, 0xE1E1E1, 0x696969, 0xC3C3C3, 0x878787)) filesystemDialog.extensionComboBox.hidden = true filesystemDialog.input = filesystemDialog:addChild(GUI.input(2, height - 1, 1, 1, 0xE1E1E1, 0x696969, 0x969696, 0xE1E1E1, 0x3C3C3C, "", placeholderText)) filesystemDialog.filesystemTree = filesystemDialog:addChild(GUI.filesystemTree(1, 1, width, height - 3, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xA5A5A5, 0x3C3C3C, 0xE1E1E1, 0xB4B4B4, 0xA5A5A5, 0xC3C3C3, 0x4B4B4B)) filesystemDialog.filesystemTree.workPath = path filesystemDialog.animationDuration = GUI.FILESYSTEM_DIALOG_ANIMATION_DURATION filesystemDialog.draw = filesystemDialogDraw filesystemDialog.setMode = filesystemDialogSetMode filesystemDialog.addExtensionFilter = filesystemDialogAddExtensionFilter filesystemDialog.expandPath = filesystemDialogExpandPath filesystemDialog:setMode(GUI.IO_MODE_OPEN, GUI.IO_MODE_FILE) return filesystemDialog end local function filesystemDialogShow(filesystemDialog) filesystemDialog.filesystemTree:updateFileList() filesystemDialog:addAnimation( function(animation) filesystemDialog.localY = math.floor(1 + (1.0 - animation.position) * (-filesystemDialog.height)) end, function(animation) animation:remove() end ):start(filesystemDialog.animationDuration) return filesystemDialog end -------------------------------------------------------------------------------- function GUI.addFilesystemDialog(parentContainer, addPanel, ...) local container = GUI.addBackgroundContainer(parentContainer, addPanel, false, nil) local filesystemDialog = container:addChild(GUI.filesystemDialog(1, 1, ...)) filesystemDialog.localX = math.floor(container.width / 2 - filesystemDialog.width / 2) filesystemDialog.localY = -filesystemDialog.height local function onAnyTouch() container:remove() filesystemDialog.workspace:draw() end filesystemDialog.cancelButton.onTouch = function() onAnyTouch() if filesystemDialog.onCancel then filesystemDialog.onCancel() end end filesystemDialog.submitButton.onTouch = function() onAnyTouch() local path = filesystemDialog.filesystemTree.selectedItem or filesystemDialog.filesystemTree.workPath or "/" if filesystemDialog.IOMode == GUI.IO_MODE_SAVE then path = path .. filesystemDialog.input.text if filesystemDialog.filesystemMode == GUI.IO_MODE_FILE then local selectedItem = filesystemDialog.extensionComboBox:getItem(filesystemDialog.extensionComboBox.selectedItem) path = path .. (selectedItem and selectedItem.text or "") else path = path .. "/" end end if filesystemDialog.onSubmit then filesystemDialog.onSubmit(path) end end filesystemDialog.show = filesystemDialogShow return filesystemDialog end -------------------------------------------------------------------------------- local function filesystemChooserDraw(object) local tipWidth = object.height * 2 - 1 local y = math.floor(object.y + object.height / 2) screen.drawRectangle(object.x, object.y, object.width - tipWidth, object.height, object.colors.background, object.colors.text, " ") 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, " ") screen.drawText(object.x + object.width - math.floor(tipWidth / 2) - 1, y, object.pressed and object.colors.tipBackground or object.colors.tipText, "…") screen.drawText(math.ceil(object.x + object.height / 2), y, object.colors.text, text.limit(object.path or object.placeholderText, object.width - tipWidth - 2, "left")) return object end local function filesystemChooserAddExtensionFilter(object, extension) object.extensionFilters[unicode.lower(extension)] = true end local function filesystemChooserSetMode(object, IOMode, filesystemMode) object.IOMode = IOMode object.filesystemMode = filesystemMode end local function filesystemChooserEventHandler(workspace, object, e1) if e1 == "touch" then object.pressed = true workspace:draw() local filesystemDialog = GUI.addFilesystemDialog(workspace, false, 50, math.floor(workspace.height * 0.8), object.submitButtonText, object.cancelButtonText, object.placeholderText, object.filesystemDialogPath) for key in pairs(object.extensionFilters) do filesystemDialog:addExtensionFilter(key) end filesystemDialog:setMode(object.IOMode, object.filesystemMode) if object.path and #object.path > 0 then -- local path = object.path:gsub("/+", "/") filesystemDialog.filesystemTree.selectedItem = object.IOMode == GUI.IO_MODE_OPEN and object.path or filesystem.path(object.path) filesystemDialog.input.text = filesystem.name(object.path) filesystemDialog:expandPath(object.IOMode == GUI.IO_MODE_OPEN and filesystem.path(object.path) or filesystem.path(filesystem.path(object.path))) end filesystemDialog.onCancel = function() object.pressed = false workspace:draw() end filesystemDialog.onSubmit = function(path) object.path = path filesystemDialog.onCancel() if object.onSubmit then object.onSubmit(object.path) end end filesystemDialog:show() end end function GUI.filesystemChooser(x, y, width, height, backgroundColor, textColor, tipBackgroundColor, tipTextColor, path, submitButtonText, cancelButtonText, placeholderText, filesystemDialogPath) local object = GUI.object(x, y, width, height) object.colors = { tipBackground = tipBackgroundColor, tipText = tipTextColor, text = textColor, background = backgroundColor } object.submitButtonText = submitButtonText object.cancelButtonText = cancelButtonText object.placeholderText = placeholderText object.pressed = false object.path = path object.filesystemDialogPath = filesystemDialogPath object.filesystemMode = GUI.IO_MODE_FILE object.IOMode = GUI.IO_MODE_OPEN object.extensionFilters = {} object.draw = filesystemChooserDraw object.eventHandler = filesystemChooserEventHandler object.addExtensionFilter = filesystemChooserAddExtensionFilter object.setMode = filesystemChooserSetMode return object end -------------------------------------------------------------------------------- local function resizerDraw(object) local horizontalMode, x, y, symbol = object.width >= object.height if horizontalMode then screen.drawText(object.x, math.floor(object.y + object.height / 2), object.colors.helper, string.rep("━", object.width)) if object.lastTouchX then screen.drawText(object.lastTouchX, object.lastTouchY, object.colors.arrow, "↑") end else local x = math.floor(object.x + object.width / 2) local bufferWidth, bufferHeight, index = screen.getResolution() for i = object.y, object.y + object.height - 1 do if x >= 1 and x <= bufferWidth and i >= 1 and i <= bufferHeight then index = screen.getIndex(x, i) screen.rawSet(index, screen.rawGet(index), object.colors.helper, "┃") end end if object.lastTouchX then screen.drawText(object.lastTouchX - 1, object.lastTouchY, object.colors.arrow, "←→") end end end local function resizerEventHandler(workspace, object, e1, e2, e3, e4) if e1 == "touch" then object.lastTouchX, object.lastTouchY = math.ceil(e3), math.ceil(e4) workspace:draw() elseif e1 == "drag" and object.lastTouchX then e3, e4 = math.ceil(e3), math.ceil(e4) if object.onResize then object.onResize(e3 - object.lastTouchX, e4 - object.lastTouchY) end object.lastTouchX, object.lastTouchY = e3, e4 workspace:draw() elseif e1 == "drop" then if object.onResizeFinished then object.onResizeFinished() end object.lastTouchX, object.lastTouchY = nil, nil workspace:draw() end end function GUI.resizer(x, y, width, height, helperColor, arrowColor) local object = GUI.object(x, y, width, height) object.colors = { helper = helperColor, arrow = arrowColor } object.draw = resizerDraw object.eventHandler = resizerEventHandler return object end -------------------------------------------------------------------------------- local function scrollBarDraw(scrollBar) local isVertical = scrollBar.height > scrollBar.width local valuesDelta = scrollBar.maximumValue - scrollBar.minimumValue local part = scrollBar.value / valuesDelta if isVertical then local barSize = math.ceil(scrollBar.shownValueCount / valuesDelta * scrollBar.height) local halfBarSize = math.floor(barSize / 2) scrollBar.ghostPosition.y = scrollBar.y + halfBarSize scrollBar.ghostPosition.height = scrollBar.height - barSize if scrollBar.thin then local y1 = math.floor(scrollBar.ghostPosition.y + part * scrollBar.ghostPosition.height - halfBarSize) local y2 = y1 + barSize - 1 local background for y = scrollBar.y, scrollBar.y + scrollBar.height - 1 do background = screen.get(scrollBar.x, y) screen.set(scrollBar.x, y, background, y >= y1 and y <= y2 and scrollBar.colors.foreground or scrollBar.colors.background, "┃") end else screen.drawRectangle(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ") screen.drawRectangle( scrollBar.x, math.floor(scrollBar.ghostPosition.y + part * scrollBar.ghostPosition.height - halfBarSize), scrollBar.width, barSize, scrollBar.colors.foreground, 0x0, " " ) end else local barSize = math.ceil(scrollBar.shownValueCount / valuesDelta * scrollBar.width) local halfBarSize = math.floor(barSize / 2) scrollBar.ghostPosition.x = scrollBar.x + halfBarSize scrollBar.ghostPosition.width = scrollBar.width - barSize if scrollBar.thin then local x1 = math.floor(scrollBar.ghostPosition.x + part * scrollBar.ghostPosition.width - halfBarSize) local x2 = x1 + barSize - 1 local background for x = scrollBar.x, scrollBar.x + scrollBar.width - 1 do background = screen.get(x, scrollBar.y) screen.set(x, scrollBar.y, background, x >= x1 and x <= x2 and scrollBar.colors.foreground or scrollBar.colors.background, "⠤") end else screen.drawRectangle(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ") screen.drawRectangle( math.floor(scrollBar.ghostPosition.x + part * scrollBar.ghostPosition.width - halfBarSize), scrollBar.y, barSize, scrollBar.height, scrollBar.colors.foreground, 0x0, " " ) end end return scrollBar end local function scrollBarEventHandler(workspace, object, e1, e2, e3, e4, e5, ...) local newValue = object.value if e1 == "touch" or e1 == "drag" then e3, e4 = math.ceil(e3), math.ceil(e4) if object.height > object.width then if e4 == object.y + object.height - 1 then newValue = object.maximumValue else newValue = object.minimumValue + (e4 - object.y) / object.height * (object.maximumValue - object.minimumValue) end else if e3 == object.x + object.width - 1 then newValue = object.maximumValue else newValue = object.minimumValue + (e3 - object.x) / object.width * (object.maximumValue - object.minimumValue) end end elseif e1 == "scroll" then if e5 == 1 then if object.value >= object.minimumValue + object.onScrollValueIncrement then newValue = object.value - object.onScrollValueIncrement else newValue = object.minimumValue end else if object.value <= object.maximumValue - object.onScrollValueIncrement then newValue = object.value + object.onScrollValueIncrement else newValue = object.maximumValue end end end if e1 == "touch" or e1 == "drag" or e1 == "scroll" then object.value = newValue if object.onTouch then object.onTouch(workspace, object, e1, e2, e3, e4, e5, ...) end workspace:draw() end end function GUI.scrollBar(x, y, width, height, backgroundColor, foregroundColor, minimumValue, maximumValue, value, shownValueCount, onScrollValueIncrement, thin) local scrollBar = GUI.object(x, y, width, height) scrollBar.eventHandler = scrollBarEventHandler scrollBar.maximumValue = maximumValue scrollBar.minimumValue = minimumValue scrollBar.value = value scrollBar.onScrollValueIncrement = onScrollValueIncrement scrollBar.shownValueCount = shownValueCount scrollBar.thin = thin scrollBar.colors = { background = backgroundColor, foreground = foregroundColor, } scrollBar.ghostPosition = {} scrollBar.draw = scrollBarDraw return scrollBar end -------------------------------------------------------------------------------- local function treeDraw(tree) local y, yEnd, showScrollBar = tree.y, tree.y + tree.height - 1, #tree.items > tree.height local textLimit = tree.width - (showScrollBar and 1 or 0) if tree.colors.default.background then screen.drawRectangle(tree.x, tree.y, tree.width, tree.height, tree.colors.default.background, tree.colors.default.expandable, " ") end for i = tree.fromItem, #tree.items do local textColor, arrowColor, text = tree.colors.default.notExpandable, tree.colors.default.arrow, tree.items[i].expandable and "■ " or "□ " if tree.selectedItem == tree.items[i].definition then textColor, arrowColor = tree.colors.selected.any, tree.colors.selected.arrow screen.drawRectangle(tree.x, y, tree.width, 1, tree.colors.selected.background, textColor, " ") else if tree.items[i].expandable then textColor = tree.colors.default.expandable elseif tree.items[i].disabled then textColor = tree.colors.disabled end end if tree.items[i].expandable then screen.drawText(tree.x + tree.items[i].offset, y, arrowColor, tree.expandedItems[tree.items[i].definition] and "▽" or "▷") end 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)) y = y + 1 if y > yEnd then break end end if showScrollBar then local scrollBar = tree.scrollBar scrollBar.x = tree.x + tree.width - 1 scrollBar.y = tree.y scrollBar.width = 1 scrollBar.height = tree.height scrollBar.colors.background = tree.colors.scrollBar.background scrollBar.colors.foreground = tree.colors.scrollBar.foreground scrollBar.minimumValue = 1 scrollBar.maximumValue = #tree.items scrollBar.value = tree.fromItem scrollBar.shownValueCount = tree.height scrollBar.onScrollValueIncrement = 1 scrollBar.thin = true scrollBar:draw() end return tree end local function treeEventHandler(workspace, tree, e1, e2, e3, e4, e5, ...) if e1 == "touch" then e3, e4 = math.ceil(e3), math.ceil(e4) local i = e4 - tree.y + tree.fromItem if tree.items[i] then if tree.items[i].expandable and ( tree.selectionMode == GUI.IO_MODE_FILE or e3 >= tree.x + tree.items[i].offset - 1 and e3 <= tree.x + tree.items[i].offset + 1 ) then if tree.expandedItems[tree.items[i].definition] then tree.expandedItems[tree.items[i].definition] = nil else tree.expandedItems[tree.items[i].definition] = true end if tree.onItemExpanded then tree.onItemExpanded(tree.selectedItem, e1, e2, e3, e4, e5, ...) end else if ( tree.selectionMode == GUI.IO_MODE_BOTH or tree.selectionMode == GUI.IO_MODE_DIRECTORY and tree.items[i].expandable or tree.selectionMode == GUI.IO_MODE_FILE ) and not tree.items[i].disabled then tree.selectedItem = tree.items[i].definition if tree.onItemSelected then tree.onItemSelected(tree.selectedItem, e1, e2, e3, e4, e5, ...) end end end workspace:draw() end elseif e1 == "scroll" then if e5 == 1 then if tree.fromItem > 1 then tree.fromItem = tree.fromItem - 1 workspace:draw() end else if tree.fromItem < #tree.items then tree.fromItem = tree.fromItem + 1 workspace:draw() end end end end local function treeAddItem(tree, name, definition, offset, expandable, disabled) local item = { name = name, expandable = expandable, offset = offset or 0, definition = definition, disabled = disabled } table.insert(tree.items, item) return item end function GUI.tree(x, y, width, height, backgroundColor, expandableColor, notExpandableColor, arrowColor, backgroundSelectedColor, anySelectionColor, arrowSelectionColor, disabledColor, scrollBarBackground, scrollBarForeground, showMode, selectionMode) local tree = GUI.object(x, y, width, height) tree.eventHandler = treeEventHandler tree.colors = { default = { background = backgroundColor, expandable = expandableColor, notExpandable = notExpandableColor, arrow = arrowColor, }, selected = { background = backgroundSelectedColor, any = anySelectionColor, arrow = arrowSelectionColor, }, scrollBar = { background = scrollBarBackground, foreground = scrollBarForeground }, disabled = disabledColor } tree.items = {} tree.fromItem = 1 tree.selectedItem = nil tree.expandedItems = {} tree.scrollBar = GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1) tree.showMode = showMode tree.selectionMode = selectionMode tree.eventHandler = treeEventHandler tree.addItem = treeAddItem tree.draw = treeDraw return tree end -------------------------------------------------------------------------------- local function filesystemTreeUpdateFileListRecursively(tree, path, offset) local list = filesystem.list(path) local i, expandables = 1, {} while i <= #list do if filesystem.isDirectory(path .. list[i]) then table.insert(expandables, list[i]) table.remove(list, i) else i = i + 1 end end table.sort(expandables, function(a, b) return unicode.lower(a) < unicode.lower(b) end) table.sort(list, function(a, b) return unicode.lower(a) < unicode.lower(b) end) if tree.showMode == GUI.IO_MODE_BOTH or tree.showMode == GUI.IO_MODE_DIRECTORY then for i = 1, #expandables do tree:addItem(filesystem.name(expandables[i]):sub(1, -2), path .. expandables[i], offset, true) if tree.expandedItems[path .. expandables[i]] then filesystemTreeUpdateFileListRecursively(tree, path .. expandables[i], offset + 2) end end end if tree.showMode == GUI.IO_MODE_BOTH or tree.showMode == GUI.IO_MODE_FILE then for i = 1, #list do tree:addItem(list[i], path .. list[i], offset, false, tree.extensionFilters and not tree.extensionFilters[filesystem.extension(path .. list[i], true)] or false) end end end local function filesystemTreeUpdateFileList(tree) tree.items = {} filesystemTreeUpdateFileListRecursively(tree, tree.workPath, 1) end local function filesystemTreeAddExtensionFilter(tree, extensionFilter) tree.extensionFilters = tree.extensionFilters or {} tree.extensionFilters[unicode.lower(extensionFilter)] = true end local function filesystemTreeExpandPath(tree, path) local blyadina = tree.workPath for pizda in path:gmatch("[^/]+") do blyadina = blyadina .. pizda .. "/" tree.expandedItems[blyadina] = true end end function GUI.filesystemTree(...) local tree = GUI.tree(...) tree.workPath = "/" tree.updateFileList = filesystemTreeUpdateFileList tree.addExtensionFilter = filesystemTreeAddExtensionFilter tree.expandPath = filesystemTreeExpandPath tree.onItemExpanded = function() tree:updateFileList() end return tree end -------------------------------------------------------------------------------- local function textBoxUpdate(object) local doubleVerticalOffset = object.offset.vertical * 2 object.textWidth = object.width - object.offset.horizontal * 2 - (object.scrollBarEnabled and 1 or 0) object.linesCopy = {} if object.autoWrap then for i = 1, #object.lines do local isTable = type(object.lines[i]) == "table" for subLine in (isTable and object.lines[i].text or object.lines[i]):gmatch("[^\n]+") do local wrappedLine = text.wrap(subLine, object.textWidth) for j = 1, #wrappedLine do table.insert(object.linesCopy, isTable and {text = wrappedLine[j], color = object.lines[i].color} or wrappedLine[j]) end end end else for i = 1, #object.lines do table.insert(object.linesCopy, object.lines[i]) end end if object.autoHeight then object.height = #object.linesCopy + doubleVerticalOffset end object.textHeight = object.height - doubleVerticalOffset end local function textBoxDraw(object) object:update() if object.colors.background then screen.drawRectangle(object.x, object.y, object.width, object.height, object.colors.background, object.colors.text, " ", object.colors.transparency) end local x, y = nil, object.y + object.offset.vertical local lineType, line, textColor for i = object.currentLine, object.currentLine + object.textHeight - 1 do if object.linesCopy[i] then lineType = type(object.linesCopy[i]) if lineType == "string" then line, textColor = text.limit(object.linesCopy[i], object.textWidth), object.colors.text elseif lineType == "table" then line, textColor = text.limit(object.linesCopy[i].text, object.textWidth), object.linesCopy[i].color else error("Unknown TextBox line type: " .. tostring(lineType)) end x = GUI.getAlignmentCoordinates( object.x + object.offset.horizontal, 1, object.textWidth, 1, object.horizontalAlignment, object.verticalAlignment, unicode.wlen(line), 1 ) screen.drawText(math.floor(x), y, textColor, line) y = y + 1 else break end end if object.scrollBarEnabled and object.textHeight < #object.lines then object.scrollBar.x = object.x + object.width - 1 object.scrollBar.y = object.y object.scrollBar.height = object.height object.scrollBar.maximumValue = #object.lines - object.textHeight + 1 object.scrollBar.value = object.currentLine object.scrollBar.shownValueCount = object.textHeight object.scrollBar:draw() end return object end local function scrollDownTextBox(object, count) count = math.min(count or 1, #object.lines - object.height - object.currentLine + object.offset.vertical * 2 + 1) if #object.lines >= object.height and object.currentLine < #object.lines - count then object.currentLine = object.currentLine + count end return object end local function scrollUpTextBox(object, count) count = count or 1 if object.currentLine > count and object.currentLine >= 1 then object.currentLine = object.currentLine - count end return object end local function scrollToStartTextBox(object) object.currentLine = 1 return object end local function scrollToEndTextBox(object) if #object.lines > object.textHeight then object.currentLine = #object.lines - object.textHeight + 1 end return object end local function textBoxScrollEventHandler(workspace, object, e1, e2, e3, e4, e5) if e1 == "scroll" then if e5 == 1 then object:scrollUp() else object:scrollDown() end workspace:draw() end end function GUI.textBox(x, y, width, height, backgroundColor, textColor, lines, currentLine, horizontalOffset, verticalOffset, autoWrap, autoHeight) local object = GUI.object(x, y, width, height) object.colors = { text = textColor, background = backgroundColor } object.lines = lines object.currentLine = currentLine or 1 object.scrollUp = scrollUpTextBox object.scrollDown = scrollDownTextBox object.scrollToStart = scrollToStartTextBox object.scrollToEnd = scrollToEndTextBox object.offset = {horizontal = horizontalOffset or 0, vertical = verticalOffset or 0} object.autoWrap = autoWrap object.autoHeight = autoHeight object.scrollBar = GUI.scrollBar(1, 1, 1, 1, 0xC3C3C3, 0x4B4B4B, 1, 1, 1, 1, 1, true) object.scrollBarEnabled = false object.eventHandler = textBoxScrollEventHandler object.draw = textBoxDraw object.update = textBoxUpdate object.setAlignment = GUI.setAlignment object:setAlignment(GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) object:update() return object end -------------------------------------------------------------------------------- local function inputSetCursorPosition(input, newPosition) if newPosition < 1 then newPosition = 1 elseif newPosition > unicode.len(input.text) + 1 then newPosition = unicode.len(input.text) + 1 end if newPosition > input.textCutFrom + input.width - 1 - input.textOffset * 2 then input.textCutFrom = input.textCutFrom + newPosition - (input.textCutFrom + input.width - 1 - input.textOffset * 2) elseif newPosition < input.textCutFrom then input.textCutFrom = newPosition end input.cursorPosition = newPosition return input end local function inputTextDrawMethod(x, y, color, text) screen.drawText(x, y, color, text) end local function inputDraw(input) local background, foreground, transparency, text if input.workspace.focusedObject == input then background, transparency = input.colors.focused.background, input.colors.focused.transparency if input.text == "" then input.textCutFrom = 1 foreground, text = input.colors.placeholderText, input.text else foreground = input.colors.focused.text if input.textMask then text = string.rep(input.textMask, unicode.len(input.text)) else text = input.text end end else background, transparency = input.colors.default.background, input.colors.default.transparency if input.text == "" then input.textCutFrom = 1 foreground, text = input.colors.placeholderText, input.placeholderText else foreground = input.colors.default.text if input.textMask then text = string.rep(input.textMask, unicode.len(input.text)) else text = input.text end end end if background then screen.drawRectangle(input.x, input.y, input.width, input.height, background, foreground, " ", transparency) end local y = input.y + math.floor(input.height / 2) input.textDrawMethod( input.x + input.textOffset, y, foreground, unicode.sub( text or "", input.textCutFrom, input.textCutFrom + input.width - 1 - input.textOffset * 2 ) ) if input.cursorBlinkState then local index = screen.getIndex(input.x + input.cursorPosition - input.textCutFrom + input.textOffset, y) local background = screen.rawGet(index) screen.rawSet(index, background, input.colors.cursor, input.cursorSymbol) end end local function inputCursorBlink(workspace, input, state) input.cursorBlinkState = state input.cursorBlinkUptime = computer.uptime() workspace:draw() end local function inputStopInput(input) input.workspace.capturedObject = nil input.workspace.focusedObject = nil if input.validator then if not input.validator(input.text) then input.text = input.startText input.startText = nil input:setCursorPosition(unicode.len(input.text) + 1) end end inputCursorBlink(input.workspace, input, false) if input.onInputFinished then input.onInputFinished(workspace, input) end end local function inputStartInput(input) input.startText = input.text input.workspace.capturedObject = input input.workspace.focusedObject = input if input.historyEnabled then input.historyIndex = input.historyIndex + 1 end if input.eraseTextOnFocus then input.text = "" end input:setCursorPosition(input.cursorPosition) inputCursorBlink(input.workspace, input, true) end local function inputEventHandler(workspace, input, e1, e2, e3, e4, e5, e6, ...) local focused = workspace.focusedObject == input if e1 == "touch" or e1 == "drag" then if input:isPointInside(math.ceil(e3), math.ceil(e4)) then input:setCursorPosition(input.textCutFrom + math.ceil(e3) - input.x - input.textOffset) if focused then inputCursorBlink(workspace, input, true) else input:startInput() end else if focused then inputStopInput(input) end end elseif e1 == "key_down" and focused then -- Return if e4 == 28 then if input.historyEnabled then -- Очистка истории for i = 1, (#input.history - input.historyLimit) do table.remove(input.history, 1) end -- Добавление введенных данных в историю if input.history[#input.history] ~= input.text and unicode.len(input.text) > 0 then table.insert(input.history, input.text) end input.historyIndex = #input.history end inputStopInput(input) if input.onKeyDown then input.onKeyDown(workspace, input, e1, e2, e3, e4, e5, e6, ...) end return -- Arrows up/down/left/right elseif e4 == 200 then if input.historyEnabled and #input.history > 0 then -- Добавление уже введенного текста в историю при стрелке вверх if input.historyIndex == #input.history + 1 and unicode.len(input.text) > 0 then input.history[input.historyIndex] = input.text end input.historyIndex = input.historyIndex - 1 if input.historyIndex > #input.history then input.historyIndex = #input.history elseif input.historyIndex < 1 then input.historyIndex = 1 end input.text = input.history[input.historyIndex] input:setCursorPosition(unicode.len(input.text) + 1) end elseif e4 == 208 then if input.historyEnabled and #input.history > 0 then input.historyIndex = input.historyIndex + 1 if input.historyIndex > #input.history then input.historyIndex = #input.history elseif input.historyIndex < 1 then input.historyIndex = 1 end input.text = input.history[input.historyIndex] input:setCursorPosition(unicode.len(input.text) + 1) end elseif e4 == 203 then input:setCursorPosition(input.cursorPosition - 1) elseif e4 == 205 then input:setCursorPosition(input.cursorPosition + 1) -- Backspace elseif e4 == 14 then input.text = unicode.sub(unicode.sub(input.text, 1, input.cursorPosition - 1), 1, -2) .. unicode.sub(input.text, input.cursorPosition, -1) input:setCursorPosition(input.cursorPosition - 1) -- Delete elseif e4 == 211 then input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. unicode.sub(input.text, input.cursorPosition + 1, -1) -- Home elseif e4 == 199 then input:setCursorPosition(1) -- End elseif e4 == 207 then input:setCursorPosition(unicode.len(input.text) + 1) else local char = unicode.char(e3) if not keyboard.isControl(e3) then input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. char .. unicode.sub(input.text, input.cursorPosition, -1) input:setCursorPosition(input.cursorPosition + 1) end end if input.onKeyDown then input.onKeyDown(workspace, input, e1, e2, e3, e4, e5, e6, ...) end inputCursorBlink(workspace, input, true) elseif e1 == "clipboard" and focused then input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. e3 .. unicode.sub(input.text, input.cursorPosition, -1) input:setCursorPosition(input.cursorPosition + unicode.len(e3)) inputCursorBlink(workspace, input, true) elseif not e1 and focused and computer.uptime() - input.cursorBlinkUptime > input.cursorBlinkDelay then inputCursorBlink(workspace, input, not input.cursorBlinkState) end end function GUI.input(x, y, width, height, backgroundColor, textColor, placeholderTextColor, backgroundFocusedColor, textFocusedColor, text, placeholderText, eraseTextOnFocus, textMask) local input = GUI.object(x, y, width, height) input.colors = { default = { background = backgroundColor, text = textColor }, focused = { background = backgroundFocusedColor, text = textFocusedColor }, placeholderText = placeholderTextColor, cursor = 0x00A8FF } input.text = text or "" input.placeholderText = placeholderText input.eraseTextOnFocus = eraseTextOnFocus input.textMask = textMask input.textOffset = 1 input.textCutFrom = 1 input.cursorPosition = 1 input.cursorSymbol = "┃" input.cursorBlinkDelay = 0.4 input.cursorBlinkState = false input.textMask = textMask input.setCursorPosition = inputSetCursorPosition input.history = {} input.historyLimit = 20 input.historyIndex = 0 input.historyEnabled = false input.textDrawMethod = inputTextDrawMethod input.draw = inputDraw input.eventHandler = inputEventHandler input.startInput = inputStartInput return input end -------------------------------------------------------------------------------- local function autoCompleteDraw(object) local y, yEnd = object.y, object.y + object.height - 1 screen.drawRectangle(object.x, object.y, object.width, object.height, object.colors.default.background, object.colors.default.text, " ") for i = object.fromItem, object.itemCount do local textColor, textMatchColor = object.colors.default.text, object.colors.default.textMatch if i == object.selectedItem then screen.drawRectangle(object.x, y, object.width, 1, object.colors.selected.background, object.colors.selected.text, " ") textColor, textMatchColor = object.colors.selected.text, object.colors.selected.textMatch end screen.drawText(object.x + 1, y, textMatchColor, unicode.sub(object.matchText, 1, object.width - 2)) screen.drawText(object.x + object.matchTextLength + 1, y, textColor, unicode.sub(object.items[i], object.matchTextLength + 1, object.matchTextLength + object.width - object.matchTextLength - 2)) y = y + 1 if y > yEnd then break end end if object.itemCount > object.height then object.scrollBar.x = object.x + object.width - 1 object.scrollBar.y = object.y object.scrollBar.height = object.height object.scrollBar.maximumValue = object.itemCount - object.height + 2 object.scrollBar.value = object.fromItem object.scrollBar.shownValueCount = object.height object.scrollBar:draw() end end local function autoCompleteScroll(workspace, object, direction) if object.itemCount >= object.height then object.fromItem = object.fromItem + direction if object.fromItem < 1 then object.fromItem = 1 elseif object.fromItem > object.itemCount - object.height + 1 then object.fromItem = object.itemCount - object.height + 1 end end end local function autoCompleteEventHandler(workspace, object, e1, e2, e3, e4, e5, ...) if e1 == "touch" then object.selectedItem = math.ceil(e4) - object.y + object.fromItem workspace:draw() if object.onItemSelected then event.sleep(0.2) object.onItemSelected(workspace, object, e1, e2, e3, e4, e5, ...) end elseif e1 == "scroll" then autoCompleteScroll(workspace, object, -e5) workspace:draw() elseif e1 == "key_down" then if e4 == 28 then if object.onItemSelected then object.onItemSelected(workspace, object, e1, e2, e3, e4, e5, ...) end elseif e4 == 200 then object.selectedItem = object.selectedItem - 1 if object.selectedItem < 1 then object.selectedItem = 1 end if object.selectedItem == object.fromItem - 1 then autoCompleteScroll(workspace, object, -1) end workspace:draw() elseif e4 == 208 then object.selectedItem = object.selectedItem + 1 if object.selectedItem > object.itemCount then object.selectedItem = object.itemCount end if object.selectedItem == object.fromItem + object.height then autoCompleteScroll(workspace, object, 1) end workspace:draw() end end end local function autoCompleteClear(object) object.items = {} object.itemCount = 0 object.fromItem = 1 object.selectedItem = 1 object.height = 0 end local function autoCompleteMatch(object, variants, text, asKey) object:clear() if asKey then if text then for key in pairs(variants) do if key ~= text and key:match("^" .. text) then table.insert(object.items,key) end end else for key in pairs(variants) do table.insert(object.items, key) end end else if text then for i = 1, #variants do if variants[i] ~= text and variants[i]:match("^" .. text) then table.insert(object.items, variants[i]) end end else for i = 1, #variants do table.insert(object.items, variants[i]) end end end object.matchText = text or "" object.matchTextLength = unicode.len(object.matchText) table.sort(object.items, function(a, b) return unicode.lower(a) < unicode.lower(b) end) object.itemCount = #object.items object.height = math.min(object.itemCount, object.maximumHeight) return object end function GUI.autoComplete(x, y, width, maximumHeight, backgroundColor, textColor, textMatchColor, backgroundSelectedColor, textSelectedColor, textMatchSelectedColor, scrollBarBackground, scrollBarForeground) local object = GUI.object(x, y, width, maximumHeight) object.colors = { default = { background = backgroundColor, text = textColor, textMatch = textMatchColor }, selected = { background = backgroundSelectedColor, text = textSelectedColor, textMatch = textMatchSelectedColor } } object.maximumHeight = maximumHeight object.fromItem = 1 object.selectedItem = 1 object.items = {} object.matchText = " " object.matchTextLength = 1 object.itemCount = 0 object.scrollBar = GUI.scrollBar(1, 1, 1, 1, scrollBarBackground, scrollBarForeground, 1, 1, 1, 1, 1, true) object.match = autoCompleteMatch object.draw = autoCompleteDraw object.eventHandler = autoCompleteEventHandler object.clear = autoCompleteClear object:clear() return object end -------------------------------------------------------------------------------- local function brailleCanvasDraw(brailleCanvas) local index, background, foreground, symbol for y = 1, brailleCanvas.height do for x = 1, brailleCanvas.width do index = screen.getIndex(brailleCanvas.x + x - 1, brailleCanvas.y + y - 1) background, foreground, symbol = screen.rawGet(index) screen.rawSet(index, background, brailleCanvas.pixels[y][x][9], brailleCanvas.pixels[y][x][10]) end end return brailleCanvas end local function brailleCanvasSet(brailleCanvas, x, y, state, color) local xReal, yReal = math.ceil(x / 2), math.ceil(y / 4) brailleCanvas.pixels[yReal][xReal][(y - (yReal - 1) * 4 - 1) * 2 + x - (xReal - 1) * 2] = state and 1 or 0 brailleCanvas.pixels[yReal][xReal][9] = color or brailleCanvas.pixels[yReal][xReal][9] brailleCanvas.pixels[yReal][xReal][10] = unicode.char( 10240 + 128 * brailleCanvas.pixels[yReal][xReal][8] + 64 * brailleCanvas.pixels[yReal][xReal][7] + 32 * brailleCanvas.pixels[yReal][xReal][6] + 16 * brailleCanvas.pixels[yReal][xReal][4] + 8 * brailleCanvas.pixels[yReal][xReal][2] + 4 * brailleCanvas.pixels[yReal][xReal][5] + 2 * brailleCanvas.pixels[yReal][xReal][3] + brailleCanvas.pixels[yReal][xReal][1] ) return brailleCanvas end local function brailleCanvasGet(brailleCanvas, x, y) local xReal, yReal = math.ceil(x / 2), math.ceil(y / 4) 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] end local function brailleCanvasFill(brailleCanvas, x, y, width, height, state, color) for j = y, y + height - 1 do for i = x, x + width - 1 do brailleCanvas:set(i, j, state, color) end end end local function brailleCanvasClear(brailleCanvas) for j = 1, brailleCanvas.height * 4 do brailleCanvas.pixels[j] = {} for i = 1, brailleCanvas.width * 2 do brailleCanvas.pixels[j][i] = { 0, 0, 0, 0, 0, 0, 0, 0, 0x0, " " } end end end function GUI.brailleCanvas(x, y, width, height) local brailleCanvas = GUI.object(x, y, width, height) brailleCanvas.pixels = {} brailleCanvas.get = brailleCanvasGet brailleCanvas.set = brailleCanvasSet brailleCanvas.fill = brailleCanvasFill brailleCanvas.clear = brailleCanvasClear brailleCanvas.draw = brailleCanvasDraw brailleCanvas:clear() return brailleCanvas end -------------------------------------------------------------------------------- local function paletteShow(palette) local workspace = GUI.workspace() workspace:addChild(palette) palette.submitButton.onTouch = function() workspace:stop() end palette.cancelButton.onTouch = palette.submitButton.onTouch workspace:draw() workspace:start() return palette.color.integer end function GUI.palette(x, y, startColor) local palette = GUI.window(x, y, 71, 25) palette.color = {hsb = {}, rgb = {}} palette:addChild(GUI.panel(1, 1, palette.width, palette.height, 0xF0F0F0)) local bigImage = palette:addChild(GUI.image(1, 1, image.create(50, 25))) local bigCrest = palette:addChild(GUI.object(1, 1, 5, 3)) local function paletteDrawBigCrestPixel(x, y, symbol) local background, foreground = screen.get(x, y) local r, g, b = color.integerToRGB(background) screen.set(x, y, background, (r + g + b) / 3 >= 127 and 0x0 or 0xFFFFFF, symbol) end bigCrest.draw = function(object) paletteDrawBigCrestPixel(object.x, object.y + 1, "─") paletteDrawBigCrestPixel(object.x + 1, object.y + 1, "─") paletteDrawBigCrestPixel(object.x + 3, object.y + 1, "─") paletteDrawBigCrestPixel(object.x + 4, object.y + 1, "─") paletteDrawBigCrestPixel(object.x + 2, object.y, "│") paletteDrawBigCrestPixel(object.x + 2, object.y + 2, "│") end bigCrest.blockScreenEvents = false local miniImage = palette:addChild(GUI.image(53, 1, image.create(3, 25))) local miniCrest = palette:addChild(GUI.object(52, 1, 5, 1)) miniCrest.draw = function(object) screen.drawText(object.x, object.y, 0x0, ">") screen.drawText(object.x + 4, object.y, 0x0, "<") end local colorPanel = palette:addChild(GUI.panel(58, 2, 12, 3, 0x0)) palette.submitButton = palette:addChild(GUI.roundedButton(58, 6, 12, 1, 0x4B4B4B, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "OK")) palette.cancelButton = palette:addChild(GUI.roundedButton(58, 8, 12, 1, 0xFFFFFF, 0x696969, 0x2D2D2D, 0xFFFFFF, "Cancel")) local function paletteRefreshBigImage() local saturationStep, brightnessStep, saturation, brightness = 1 / bigImage.width, 1 / bigImage.height, 0, 1 for j = 1, bigImage.height do for i = 1, bigImage.width do image.set(bigImage.image, i, j, color.optimize(color.HSBToInteger(palette.color.hsb.hue, saturation, brightness)), 0x0, 0x0, " ") saturation = saturation + saturationStep end saturation, brightness = 0, brightness - brightnessStep end end local function paletteRefreshMiniImage() local hueStep, hue = 360 / miniImage.height, 0 for j = 1, miniImage.height do for i = 1, miniImage.width do image.set(miniImage.image, i, j, color.optimize(color.HSBToInteger(hue, 1, 1)), 0x0, 0, " ") end hue = hue + hueStep end end local function paletteUpdateCrestsCoordinates() bigCrest.localX = math.floor((bigImage.width - 1) * palette.color.hsb.saturation) - 1 bigCrest.localY = math.floor((bigImage.height - 1) - (bigImage.height - 1) * palette.color.hsb.brightness) miniCrest.localY = math.ceil(palette.color.hsb.hue / 360 * miniImage.height + 0.5) end local inputs local function paletteUpdateInputs() inputs[1].text = tostring(palette.color.rgb.red) inputs[2].text = tostring(palette.color.rgb.green) inputs[3].text = tostring(palette.color.rgb.blue) inputs[4].text = tostring(math.floor(palette.color.hsb.hue)) inputs[5].text = tostring(math.floor(palette.color.hsb.saturation * 100)) inputs[6].text = tostring(math.floor(palette.color.hsb.brightness * 100)) inputs[7].text = string.format("%06X", palette.color.integer) colorPanel.colors.background = palette.color.integer end local function paletteSwitchColorFromHex(hex) palette.color.integer = hex palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = color.integerToRGB(hex) 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) paletteUpdateInputs() end local function paletteSwitchColorFromHsb(hue, saturation, brightness) palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = hue, saturation, brightness palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = color.HSBToRGB(hue, saturation, brightness) palette.color.integer = color.RGBToInteger(palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue) paletteUpdateInputs() end local function paletteSwitchColorFromRgb(red, green, blue) palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = red, green, blue palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = color.RGBToHSB(red, green, blue) palette.color.integer = color.RGBToInteger(red, green, blue) paletteUpdateInputs() end local function onAnyInputFinished() paletteRefreshBigImage() paletteUpdateCrestsCoordinates() palette.workspace:draw() end local function onHexInputFinished() paletteSwitchColorFromHex(tonumber("0x" .. inputs[7].text)) onAnyInputFinished() end local function onRgbInputFinished() paletteSwitchColorFromRgb(tonumber(inputs[1].text), tonumber(inputs[2].text), tonumber(inputs[3].text)) onAnyInputFinished() end local function onHsbInputFinished() paletteSwitchColorFromHsb(tonumber(inputs[4].text), tonumber(inputs[5].text) / 100, tonumber(inputs[6].text) / 100) onAnyInputFinished() end local function rgbValidaror(text) local number = tonumber(text) if number and number >= 0 and number <= 255 then return true end end local function hValidator(text) local number = tonumber(text) if number and number >= 0 and number <= 359 then return true end end local function sbValidator(text) local number = tonumber(text) if number and number >= 0 and number <= 100 then return true end end local function hexValidator(text) 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 return true end end inputs = { { shortcut = "R:", validator = rgbValidaror, onInputFinished = onRgbInputFinished }, { shortcut = "G:", validator = rgbValidaror, onInputFinished = onRgbInputFinished }, { shortcut = "B:", validator = rgbValidaror, onInputFinished = onRgbInputFinished }, { shortcut = "H:", validator = hValidator, onInputFinished = onHsbInputFinished }, { shortcut = "S:", validator = sbValidator, onInputFinished = onHsbInputFinished }, { shortcut = "L:", validator = sbValidator, onInputFinished = onHsbInputFinished }, { shortcut = "0x", validator = hexValidator, onInputFinished = onHexInputFinished } } local y = 10 for i = 1, #inputs do palette:addChild(GUI.label(58, y, 2, 1, 0x0, inputs[i].shortcut)) local validator, onInputFinished = inputs[i].validator, inputs[i].onInputFinished inputs[i] = palette:addChild(GUI.input(61, y, 9, 1, 0xFFFFFF, 0x696969, 0x696969, 0xFFFFFF, 0x0, "", "", true)) inputs[i].validator = validator inputs[i].onInputFinished = onInputFinished y = y + 2 end local paletteConfigPath = paths.user.applicationData .. "GUI/Palette.cfg" local favourites if filesystem.exists(paletteConfigPath) then favourites = filesystem.readTable(paletteConfigPath) else favourites = {} for i = 1, 6 do favourites[i] = color.HSBToInteger(math.random(0, 360), 1, 1) end filesystem.writeTable(paletteConfigPath, favourites) end local favouritesContainer = palette:addChild(GUI.container(58, 24, 12, 1)) for i = 1, #favourites do favouritesContainer:addChild(GUI.button(i * 2 - 1, 1, 2, 1, favourites[i], 0x0, 0x0, 0x0, " ")).onTouch = function(workspace) paletteSwitchColorFromHex(favourites[i]) paletteRefreshBigImage() paletteUpdateCrestsCoordinates() workspace:draw() end end palette:addChild(GUI.button(58, 25, 12, 1, 0xFFFFFF, 0x4B4B4B, 0x2D2D2D, 0xFFFFFF, "+")).onTouch = function(workspace) local favouriteExists = false for i = 1, #favourites do if favourites[i] == palette.color.integer then favouriteExists = true break end end if not favouriteExists then table.insert(favourites, 1, palette.color.integer) table.remove(favourites, #favourites) for i = 1, #favourites do favouritesContainer.children[i].colors.default.background = favourites[i] favouritesContainer.children[i].colors.pressed.background = 0x0 end filesystem.writeTable(paletteConfigPath, favourites) workspace:draw() end end bigImage.eventHandler = function(workspace, object, e1, e2, e3, e4) if e1 == "touch" or e1 == "drag" then e3, e4 = math.ceil(e3), math.ceil(e4) bigCrest.localX, bigCrest.localY = e3 - palette.x - 1, e4 - palette.y paletteSwitchColorFromHex(select(3, component.invoke(screen.getGPUAddress(), "get", e3, e4))) workspace:draw() end end miniImage.eventHandler = function(workspace, object, e1, e2, e3, e4) if e1 == "touch" or e1 == "drag" then e4 = math.ceil(e4) miniCrest.localY = e4 - palette.y + 1 paletteSwitchColorFromHsb((e4 - miniImage.y) * 360 / miniImage.height, palette.color.hsb.saturation, palette.color.hsb.brightness) paletteRefreshBigImage() workspace:draw() end end palette.show = paletteShow paletteSwitchColorFromHex(startColor) paletteUpdateCrestsCoordinates() paletteRefreshBigImage() paletteRefreshMiniImage() return palette end -------------------------------------------------------------------------------- local function textUpdate(object) object.width = unicode.wlen(object.text) return object end local function textDraw(object) object:update() screen.drawText(object.x, object.y, object.color, object.text, object.transparency) return object end function GUI.text(x, y, color, text, transparency) local object = GUI.object(x, y, 1, 1) object.text = text object.color = color object.transparency = transparency object.update = textUpdate object.draw = textDraw object:update() return object end -------------------------------------------------------------------------------- function GUI.addBackgroundContainer(parentContainer, addPanel, addLayout, title) local container = parentContainer:addChild(GUI.container(1, 1, parentContainer.width, parentContainer.height)) if addPanel then container.panel = container:addChild(GUI.panel(1, 1, container.width, container.height, GUI.BACKGROUND_CONTAINER_PANEL_COLOR, GUI.BACKGROUND_CONTAINER_PANEL_TRANSPARENCY)) container.panel.eventHandler = function(parentContainer, object, e1) if e1 == "touch" then container:remove() parentContainer:draw() end end end if addLayout then container.layout = container:addChild(GUI.layout(1, 1, container.width, container.height, 1, 1)) if title then 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) end end return container end -------------------------------------------------------------------------------- local function listUpdate(list) local step, child = false for i = 1, #list.children do child = list.children[i] -- Жмяканье пизды child.pressed = i == list.selectedItem -- Цвет залупы if step then child.colors.default = list.colors.alternative else child.colors.default = list.colors.default end child.colors.pressed, step = list.colors.selected, not step -- Размеры хуйни if list.cells[1][1].direction == GUI.DIRECTION_HORIZONTAL then if list.offsetMode then child.width, child.height = list.itemSize * 2 + unicode.wlen(child.text), list.height else child.width, child.height = list.itemSize, list.height end else if list.offsetMode then child.width, child.height = list.width, list.itemSize * 2 + 1 else child.width, child.height = list.width, list.itemSize end end end layoutUpdate(list) end local function listItemEventHandler(workspace, item, e1, ...) if e1 == "touch" or e1 == "drag" then item.parent.selectedItem = item:indexOf() item.parent:update() workspace:draw() if item.onTouch then item.onTouch(workspace, item, e1, ...) end end end local function listAddItem(list, text) local item = list:addChild(pressable(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, text)) item.switchMode = true item.eventHandler = listItemEventHandler return item end local function listSetAlignment(list, ...) layoutSetAlignment(list, 1, 1, ...) return list end local function listSetSpacing(list, ...) layoutSetSpacing(list, 1, 1, ...) return list end local function listSetDirection(list, ...) layoutSetDirection(list, 1, 1, ...) return list end local function listSetFitting(list, ...) layoutSetFitting(list, 1, 1, ...) return list end local function listSetMargin(list, ...) layoutSetMargin(list, 1, 1, ...) return list end local function listGetMargin(list, ...) return layoutGetMargin(list, 1, 1, ...) end local function listGetItem(list, what) if type(what) == "number" then return list.children[what] else for i = 1, #list.children do if list.children[i].text == what then return list.children[i], i end end end end local function listCount(list) return #list.children end local function listDraw(list) if list.colors.default.background then screen.drawRectangle(list.x, list.y, list.width, list.height, list.colors.default.background, list.colors.default.text, " ") end layoutDraw(list) end function GUI.list(x, y, width, height, itemSize, spacing, backgroundColor, textColor, backgroundAlternatingColor, textAlternatingColor, backgroundSelectedColor, textSelectedColor, offsetMode) local list = GUI.layout(x, y, width, height, 1, 1) list.colors = { default = { background = backgroundColor, text = textColor }, alternative = { background = backgroundAlternatingColor, text = textAlternatingColor }, selected = { background = backgroundSelectedColor, text = textSelectedColor }, } list.blockScreenEvents = true list.selectedItem = 1 list.offsetMode = offsetMode list.itemSize = itemSize list.addItem = listAddItem list.getItem = listGetItem list.count = listCount list.setAlignment = listSetAlignment list.setSpacing = listSetSpacing list.setDirection = listSetDirection list.setFitting = listSetFitting list.setMargin = listSetMargin list.getMargin = listGetMargin list.update = listUpdate list.draw = listDraw list:setAlignment(GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) list:setSpacing(spacing) list:setDirection(GUI.DIRECTION_VERTICAL) return list end --------------------------------------------------------------------------------------------------- local function keyAndValueUpdate(object) object.keyLength, object.valueLength = unicode.wlen(object.key), unicode.wlen(object.value) object.width = object.keyLength + object.valueLength end local function keyAndValueDraw(object) keyAndValueUpdate(object) screen.drawText(object.x, object.y, object.colors.key, object.key) screen.drawText(object.x + object.keyLength, object.y, object.colors.value, object.value) end function GUI.keyAndValue(x, y, keyColor, valueColor, key, value) local object = GUI.object(x, y, 1, 1) object.colors = { key = keyColor, value = valueColor } object.key = key object.value = value object.update = keyAndValueUpdate object.draw = keyAndValueDraw object:update() return object end --------------------------------------------------------------------------------------------------- function GUI.highlightString(x, y, fromChar, indentationWidth, patterns, colorScheme, s) local stringLength, x1, y1, x2, y2 = unicode.len(s), screen.getDrawLimit() fromChar = fromChar or 1 if x < x1 then fromChar = fromChar + x1 - x x = x1 end -- local toChar, endX = stringLength, x + stringLength - 1 -- if endX > x2 then -- toChar = toChar - endX + x2 -- end local toChar = fromChar + x2 - x if toChar > stringLength then toChar = stringLength end local counter, symbols, colors, bufferIndex, newFrameBackgrounds, newFrameForegrounds, newFrameSymbols, searchFrom, starting, ending = indentationWidth, {}, {}, screen.getIndex(x, y), screen.getNewFrameTables() -- Пидорасим на символы for i = fromChar, toChar do symbols[i] = unicode.sub(s, i, i) end -- Вгоняем в цветовую карту синтаксическую подсветку for j = 1, #patterns, 4 do searchFrom = 1 while true do starting, ending = text.unicodeFind(s, patterns[j], searchFrom) if starting then for i = starting + patterns[j + 2], ending - patterns[j + 3] do colors[i] = colorScheme[patterns[j + 1]] end else break end searchFrom = ending + 1 - patterns[j + 3] end end -- Ебошим индентейшны for i = fromChar, toChar do if symbols[i] == " " then colors[i] = colorScheme.indentation if counter == indentationWidth then symbols[i], counter = "│", 0 end counter = counter + 1 else break end end -- Рисуем текст for i = fromChar, toChar do newFrameForegrounds[bufferIndex], newFrameSymbols[bufferIndex] = colors[i] or colorScheme.text, symbols[i] or " " bufferIndex = bufferIndex + 1 end end -------------------------------------------------------------------------------- local function dropDownMenuItemDraw(item) local yText = item.y + math.floor(item.height / 2) if item.isTextItem then local iconColor = item.color or item.parent.parent.colors.default.icon local textColor = item.color or item.parent.parent.colors.default.text if item.pressed then iconColor = item.parent.parent.colors.selected.icon textColor = item.parent.parent.colors.selected.text screen.drawRectangle(item.x, yText, item.width, 1, item.parent.parent.colors.selected.background, textColor, " ") if item.height > 1 then screen.drawText(item.x, yText - 1, item.parent.parent.colors.selected.background, string.rep("⣤", item.width)) screen.drawText(item.x, yText + item.height - 1, item.parent.parent.colors.selected.background, string.rep("⠛", item.width)) end elseif item.disabled then iconColor = item.parent.parent.colors.disabled.icon textColor = item.parent.parent.colors.disabled.text end -- Icon & text if item.icon then screen.drawText(item.x + item.height, yText, iconColor, item.icon) screen.drawText(item.x + item.height * 2 + 2, yText, textColor, item.text) else screen.drawText(item.x + item.height, yText, textColor, item.text) end -- shortcut if item.shortcut then screen.drawText(item.x + item.width - unicode.wlen(item.shortcut) - item.height, yText, textColor, item.shortcut) end else screen.drawText(item.x, yText, item.parent.parent.colors.separator, string.rep(item.parent.parent.itemHeight % 2 == 0 and "▁" or "─", item.width)) end return item end local function dropDownMenuReleaseItems(menu) for i = 1, #menu.itemsContainer.children do menu.itemsContainer.children[i].pressed = false end return menu end local function dropDownMenuItemEventHandler(workspace, object, e1, ...) if e1 == "touch" then if object.isTextItem and not object.pressed then dropDownMenuReleaseItems(object.parent.parent) if #object.parent.parent.subMenus then for i, subMenu in ipairs(object.parent.parent.subMenus) do subMenu:remove() table.remove(object.parent.parent.subMenus, i) end end object.pressed = true workspace:draw() if object.subMenu then object.parent.parent.parent:addChild(object.subMenu:releaseItems()) object.subMenu.localX = object.parent.parent.localX + object.parent.parent.width object.subMenu.localY = object.parent.parent.localY + object.localY - 1 if screen.getWidth() - object.parent.parent.localX - object.parent.parent.width + 1 < object.subMenu.width then object.subMenu.localX = object.parent.parent.localX - object.subMenu.width object.parent.parent:moveToFront() end table.insert(object.parent.parent.subMenus, object.subMenu) workspace:draw() else event.sleep(0.2) object.parent.parent.parent:remove() local objectIndex = object:indexOf() for i = 2, #object.parent.parent.parent.children do if object.parent.parent.parent.children[i].onMenuClosed then object.parent.parent.parent.children[i].onMenuClosed(objectIndex) end end if object.onTouch then object.onTouch(workspace, object, e1, ...) end workspace:draw() end end end end local function dropDownMenuGetHeight(menu) local height = menu.itemHeight % 2 == 0 and 1 or 0 for i = 1, #menu.itemsContainer.children do height = height + menu.itemsContainer.children[i].height end return height end local function dropDownMenuReposition(menu) menu.itemsContainer.width, menu.itemsContainer.height = menu.width, menu.height menu.prevButton.width, menu.nextButton.width = menu.width, menu.width menu.nextButton.localY = menu.height local y = menu.itemsContainer.children[1].localY for i = 1, #menu.itemsContainer.children do menu.itemsContainer.children[i].localY = y menu.itemsContainer.children[i].width = menu.itemsContainer.width y = y + menu.itemsContainer.children[i].height end menu.prevButton.hidden = menu.itemsContainer.children[1].localY >= 1 menu.nextButton.hidden = menu.itemsContainer.children[#menu.itemsContainer.children].localY + menu.itemsContainer.children[#menu.itemsContainer.children].height - 1 <= menu.height end local function dropDownMenuUpdate(menu) if #menu.itemsContainer.children > 0 then menu.height = math.min(dropDownMenuGetHeight(menu), menu.maximumHeight, screen.getHeight() - menu.y) dropDownMenuReposition(menu) end end local function dropDownMenuRemoveItem(menu, index) table.remove(menu.itemsContainer.children, index) menu:update() return menu end local function dropDownMenuAddItem(menu, a1, a2, a3, a4, a5) local item = menu.itemsContainer:addChild(GUI.object(1, 1, 1, menu.itemHeight)) if type(a1) == "string" and type(a2) == "string" then item.icon = a1 item.text = a2 item.disabled = a3 item.shortcut = a4 item.color = a5 else item.text = a1 item.disabled = a2 item.shortcut = a3 item.color = a4 end item.draw = dropDownMenuItemDraw item.eventHandler = dropDownMenuItemEventHandler item.isTextItem = true menu:update() return item end local function dropDownMenuAddSeparator(menu) local item = menu.itemsContainer:addChild(GUI.object(1, 1, 1, 1)) item.draw = dropDownMenuItemDraw item.eventHandler = dropDownMenuItemEventHandler menu:update() return item end local function dropDownMenuScrollDown(workspace, menu) local limit, first = 1, menu.itemsContainer.children[1] first.localY = first.localY + menu.scrollSpeed if first.localY > limit then first.localY = limit end dropDownMenuReposition(menu) workspace:draw() end local function dropDownMenuScrollUp(workspace, menu) local limit, first = -(dropDownMenuGetHeight(menu) - menu.height - 1), menu.itemsContainer.children[1] first.localY = first.localY - menu.scrollSpeed if first.localY < limit then first.localY = limit end dropDownMenuReposition(menu) workspace:draw() end local function dropDownMenuEventHandler(workspace, menu, e1, e2, e3, e4, e5) if e1 == "scroll" then if e5 == 1 then dropDownMenuScrollDown(workspace, menu) else dropDownMenuScrollUp(workspace, menu) end end end local function dropDownMenuPrevButtonOnTouch(workspace, button) dropDownMenuScrollDown(workspace, button.parent) end local function dropDownMenuNextButtonOnTouch(workspace, button) dropDownMenuScrollUp(workspace, button.parent) end local function dropDownMenuDraw(menu) screen.drawRectangle(menu.x, menu.y, menu.width, menu.height, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency.background) GUI.drawShadow(menu.x, menu.y, menu.width, menu.height, menu.colors.transparency.shadow, true) containerDraw(menu) end local function dropDownMenuBackgroundObjectEventHandler(workspace, object, e1) if e1 == "touch" then for i = 2, #object.parent.children do if object.parent.children[i].onMenuClosed then object.parent.children[i].onMenuClosed() end end object.parent:remove() workspace:draw() end end local function dropDownMenuAdd(parentContainer, menu) local container = parentContainer:addChild(GUI.container(1, 1, parentContainer.width, parentContainer.height)) container:addChild(GUI.object(1, 1, container.width, container.height)).eventHandler = dropDownMenuBackgroundObjectEventHandler return container:addChild(menu:releaseItems()) end function GUI.dropDownMenu( x, y, width, maximumHeight, itemHeight, backgroundColor, iconColor, textColor, backgroundPressedColor, iconPressedColor, textPressedColor, disabledIconColor, disabledTextColor, separatorColor, backgroundTransparency, shadowTransparency ) local menu = GUI.container(x, y, width, 1) menu.colors = { default = { background = backgroundColor, text = textColor, icon = iconColor }, selected = { background = backgroundPressedColor, text = textPressedColor, icon = iconPressedColor }, disabled = { text = disabledTextColor, icon = disabledIconColor }, separator = separatorColor, transparency = { background = backgroundTransparency, shadow = shadowTransparency } } menu.scrollSpeed = 1 menu.itemsContainer = menu:addChild(GUI.container(1, 1, menu.width, menu.height)) -- RIP ▲▼ -- ⬆⬇ menu.prevButton = menu:addChild(GUI.button(1, 1, menu.width, 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, "⬆")) menu.nextButton = menu:addChild(GUI.button(1, 1, menu.width, 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, "⬇")) menu.prevButton.colors.transparency, menu.nextButton.colors.transparency = backgroundTransparency, backgroundTransparency menu.prevButton.onTouch = dropDownMenuPrevButtonOnTouch menu.nextButton.onTouch = dropDownMenuNextButtonOnTouch menu.releaseItems = dropDownMenuReleaseItems menu.itemHeight = itemHeight menu.addSeparator = dropDownMenuAddSeparator menu.addItem = dropDownMenuAddItem menu.removeItem = dropDownMenuRemoveItem menu.draw = dropDownMenuDraw menu.maximumHeight = maximumHeight menu.eventHandler = dropDownMenuEventHandler menu.update = dropDownMenuUpdate menu.subMenus = {} return menu end -------------------------------------------------------------------------------- local function contextMenuUpdate(menu) if #menu.itemsContainer.children > 0 then local widestItem, widestShortcut, haveIcon, item = 0, 0, false for i = 1, #menu.itemsContainer.children do item = menu.itemsContainer.children[i] if item.isTextItem then if item.icon then haveIcon = true end widestItem = math.max(widestItem, unicode.wlen(item.text)) if item.shortcut then widestShortcut = math.max(widestShortcut, unicode.wlen(item.shortcut)) end end end menu.width = menu.itemHeight * 2 + (haveIcon and menu.itemHeight + 2 or 0) + widestItem + (widestShortcut > 0 and widestShortcut + menu.itemHeight + 1 or 0) menu.height = math.min(dropDownMenuGetHeight(menu), menu.maximumHeight) dropDownMenuReposition(menu) local bufferWidth, bufferHeight = screen.getResolution() if menu.x + menu.width + 1 >= bufferWidth then menu.localX = bufferWidth - menu.width - 1 end if menu.y + menu.height >= bufferHeight then menu.localY = bufferHeight - menu.height end end end local contextMenuCreate, contextMenuaddSubMenuItem contextMenuaddSubMenuItem = function(menu, a1, a2, a3) local item if type(a1) == "string" and type(a2) == "string" then item = menu:addItem(a1, a2, a3, "►") else item = menu:addItem(a1, a2, "►") end item.subMenu = contextMenuCreate(1, 1) item.subMenu.colors = menu.colors return item.subMenu end contextMenuCreate = function(x, y, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, backgroundTransparency, shadowTransparency) local menu = GUI.dropDownMenu( x, y, 1, math.ceil(screen.getHeight() * 0.7), 2, GUI.CONTEXT_MENU_DEFAULT_BACKGROUND_COLOR, GUI.CONTEXT_MENU_DEFAULT_ICON_COLOR, GUI.CONTEXT_MENU_DEFAULT_TEXT_COLOR, GUI.CONTEXT_MENU_PRESSED_BACKGROUND_COLOR, GUI.CONTEXT_MENU_PRESSED_ICON_COLOR, GUI.CONTEXT_MENU_PRESSED_TEXT_COLOR, GUI.CONTEXT_MENU_DISABLED_ICON_COLOR, GUI.CONTEXT_MENU_DISABLED_TEXT_COLOR, GUI.CONTEXT_MENU_SEPARATOR_COLOR, GUI.CONTEXT_MENU_BACKGROUND_TRANSPARENCY, GUI.CONTEXT_MENU_SHADOW_TRANSPARENCY ) menu.update = contextMenuUpdate menu.addSubMenuItem = contextMenuaddSubMenuItem return menu end function GUI.addContextMenu(parentContainer, arg1, ...) if type(arg1) == "table" then return dropDownMenuAdd(parentContainer, arg1, ...) else return dropDownMenuAdd(parentContainer, contextMenuCreate(arg1, ...)) end end -------------------------------------------------------------------------------- local function comboBoxDraw(object) local arrowSize = object.height * 2 - 1 local width = object.width - arrowSize -- Background screen.drawRectangle(object.x, object.y, width, object.height, object.colors.default.background, object.colors.default.text, " ") -- Item if object.dropDownMenu.itemsContainer.children[object.selectedItem] then screen.drawText( math.ceil(object.x + object.height / 2), math.floor(object.y + object.height / 2), object.colors.default.text, text.limit(object.dropDownMenu.itemsContainer.children[object.selectedItem].text, object.width - object.height - 2, "right") ) end -- Arrow width = object.x + width screen.drawRectangle(width, object.y, arrowSize, object.height, object.colors.arrow.background, object.colors.arrow.text, " ") screen.drawText(math.floor(width + arrowSize / 2), math.floor(object.y + object.height / 2), object.colors.arrow.text, object.pressed and "⬆" or "⬇") return object end local function comboBoxGetItem(object, what) if type(what) == "number" then return object.dropDownMenu.itemsContainer.children[what] else local children = object.dropDownMenu.itemsContainer.children for i = 1, #children do if children[i].text == what then return children[i], i end end end end local function comboBoxRemoveItem(object, index) object.dropDownMenu:removeItem(index) if object.selectedItem > #object.dropDownMenu.itemsContainer.children then object.selectedItem = #object.dropDownMenu.itemsContainer.children end end local function comboBoxCount(object) return #object.dropDownMenu.itemsContainer.children end local function comboBoxClear(object) object.dropDownMenu.itemsContainer:removeChildren() object.selectedItem = 1 return object end local function comboBoxEventHandler(workspace, object, e1, ...) if e1 == "touch" and #object.dropDownMenu.itemsContainer.children > 0 then object.pressed = true object.dropDownMenu.x, object.dropDownMenu.y, object.dropDownMenu.width = object.x, object.y + object.height, object.width object.dropDownMenu:update() dropDownMenuAdd(workspace, object.dropDownMenu) workspace:draw() end end local function comboBoxAddItem(object, ...) return object.dropDownMenu:addItem(...) end local function comboBoxAddSeparator(object) return object.dropDownMenu:addSeparator() end function GUI.comboBox(x, y, width, height, backgroundColor, textColor, arrowBackgroundColor, arrowTextColor) local comboBox = GUI.object(x, y, width, height) comboBox.colors = { default = { background = backgroundColor, text = textColor }, selected = { background = GUI.CONTEXT_MENU_PRESSED_BACKGROUND_COLOR, text = GUI.CONTEXT_MENU_PRESSED_TEXT_COLOR }, arrow = { background = arrowBackgroundColor, text = arrowTextColor } } comboBox.dropDownMenu = GUI.dropDownMenu( 1, 1, 1, math.ceil(screen.getHeight() * 0.5), height == 1 and 1 or 2, comboBox.colors.default.background, comboBox.colors.default.text, comboBox.colors.default.text, comboBox.colors.selected.background, comboBox.colors.selected.text, comboBox.colors.selected.text, GUI.CONTEXT_MENU_DISABLED_ICON_COLOR, GUI.CONTEXT_MENU_DISABLED_TEXT_COLOR, GUI.CONTEXT_MENU_SEPARATOR_COLOR, GUI.CONTEXT_MENU_BACKGROUND_TRANSPARENCY, GUI.CONTEXT_MENU_SHADOW_TRANSPARENCY ) comboBox.dropDownMenu.onMenuClosed = function(index) comboBox.pressed = false comboBox.selectedItem = index or comboBox.selectedItem comboBox.workspace:draw() if index and comboBox.onItemSelected then comboBox.onItemSelected(index) end end comboBox.selectedItem = 1 comboBox.addItem = comboBoxAddItem comboBox.removeItem = comboBoxRemoveItem comboBox.addSeparator = comboBoxAddSeparator comboBox.draw = comboBoxDraw comboBox.clear = comboBoxClear comboBox.getItem = comboBoxGetItem comboBox.count = comboBoxCount comboBox.eventHandler = comboBoxEventHandler return comboBox end --------------------------------------------------------------------------------------------------- local function windowDraw(window) containerDraw(window) if window.drawShadow then GUI.drawShadow(window.x, window.y, window.width, window.height, GUI.WINDOW_SHADOW_TRANSPARENCY, true) end return window end local function windowCheckForBlockingScreenEvent(object, x, y) local child, result for i = #object.children, 1, -1 do child = object.children[i] if not child.hidden and not child.disabled and child:isPointInside(x, y) then if child.blockScreenEvents and child.eventHandler then return true elseif child.children then if windowCheckForBlockingScreenEvent(child, x, y) then return true end end end end end local function windowEventHandler(workspace, window, e1, e2, e3, e4, ...) if not window.movingEnabled then return end if e1 == "touch" then e3, e4 = math.ceil(e3), math.ceil(e4) if not windowCheckForBlockingScreenEvent(window, e3, e4) then workspace.capturedObject = window window.lastTouchX, window.lastTouchY = e3, e4 end -- Focusing window if it's not focused if window ~= window.parent.children[#window.parent.children] then window:focus() workspace:draw() end elseif e1 == "drag" and window.lastTouchX then e3, e4 = math.ceil(e3), math.ceil(e4) window.localX, window.localY = window.localX + e3 - window.lastTouchX, window.localY + e4 - window.lastTouchY window.lastTouchX, window.lastTouchY = e3, e4 workspace:draw() elseif e1 == "drop" then window.lastTouchX, window.lastTouchY, workspace.capturedObject = nil, nil, nil end end local function windowResize(window, width, height, ignoreOnResizeFinished) window.width, window.height = width, height if window.onResize then window.onResize(width, height) end if window.onResizeFinished and not ignoreOnResizeFinished then window.onResizeFinished() end return window end function GUI.windowMaximize(window, animationDisabled) local fromX, fromY, fromWidth, fromHeight, toX, toY, toWidth, toHeight = window.localX, window.localY, window.width, window.height if window.maximized then toX, toY, toWidth, toHeight = window.oldGeometryX, window.oldGeometryY, window.oldGeometryWidth, window.oldGeometryHeight window.oldGeometryX, window.oldGeometryY, window.oldGeometryWidth, window.oldGeometryHeight = nil, nil, nil, nil window.maximized = nil else toWidth, toHeight = window.parent.width, window.parent.height if window.maxWidth then toWidth = math.min(toWidth, window.maxWidth) end if window.maxHeight then toHeight = math.min(toHeight, window.maxHeight) end toX, toY = math.floor(1 + window.parent.width / 2 - toWidth / 2), math.floor(1 + window.parent.height / 2 - toHeight / 2) window.oldGeometryX, window.oldGeometryY, window.oldGeometryWidth, window.oldGeometryHeight = window.localX, window.localY, window.width, window.height window.maximized = true end if animationDisabled then window.localX, window.localY = toX, toY window:resize(toWidth, toHeight) else window:addAnimation( function(animation) window.localX, window.localY = math.floor(fromX + (toX - fromX) * animation.position), math.floor(fromY + (toY - fromY) * animation.position) window:resize( math.floor(fromWidth + (toWidth - fromWidth) * animation.position), math.floor(fromHeight + (toHeight - fromHeight) * animation.position), animation.position < 1 ) end, function(animation) animation:remove() end ):start(0.5) end end local function windowFocus(window) window.workspace.focusedObject = window window.hidden = false window:moveToFront() if window.onFocus then window.onFocus() end end function GUI.windowMinimize(window) window.hidden = not window.hidden end function GUI.window(x, y, width, height) local window = GUI.container(x, y, width, height) window.blockScreenEvents = true window.movingEnabled = true window.drawShadow = true window.resize = windowResize window.maximize = GUI.windowMaximize window.minimize = GUI.windowMinimize window.eventHandler = windowEventHandler window.draw = windowDraw window.focus = windowFocus return window end function GUI.filledWindow(x, y, width, height, backgroundColor, transparency) local window = GUI.window(x, y, width, height) window.backgroundPanel = window:addChild(GUI.panel(1, 1, width, height, backgroundColor, transparency)) window.actionButtons = window:addChild(GUI.actionButtons(2, 2, true)) return window end function GUI.titledWindow(x, y, width, height, title, addTitlePanel) local window = GUI.filledWindow(x, y, width, height, GUI.WINDOW_BACKGROUND_PANEL_COLOR) if addTitlePanel then window.titlePanel = window:addChild(GUI.panel(1, 1, width, 1, GUI.WINDOW_TITLE_BACKGROUND_COLOR)) window.backgroundPanel.localY, window.backgroundPanel.height = 2, window.height - 1 end 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) window.actionButtons.localY = 1 window.actionButtons:moveToFront() return window end function GUI.tabbedWindow(x, y, width, height, ...) local window = GUI.filledWindow(x, y, width, height, GUI.WINDOW_BACKGROUND_PANEL_COLOR) 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)) window.backgroundPanel.localY, window.backgroundPanel.height = 4, window.height - 3 window.actionButtons:moveToFront() window.actionButtons.localY = 2 return window end --------------------------------------------------------------------------------------------------- function GUI.tabBar(...) local tabBar = GUI.list(...) tabBar:setDirection(GUI.DIRECTION_HORIZONTAL) tabBar:setAlignment(GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP) return tabBar end -------------------------------------------------------------------------------- local function menuDraw(menu) screen.drawRectangle(menu.x, menu.y, menu.width, 1, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency) local x = 1 local child for i = 1, #menu.children do child = menu.children[i] child.localX = x x = x + child.width end containerDraw(menu) end local function menuAddItem(menu, text, textColor) local item = menu:addChild(pressable(1, 1, unicode.wlen(text) + 2, 1, nil, textColor or menu.colors.default.text, menu.colors.selected.background, menu.colors.selected.text, 0x0, 0x0, text)) item.eventHandler = pressableEventHandler return item end local function menuGetItem(menu, what) if type(what) == "number" then return menu.children[what] else for i = 1, #menu.children do if menu.children[i].text == what then return menu.children[i], i end end end end local function menuContextMenuItemOnTouch(workspace, item) item.contextMenu.x, item.contextMenu.y = item.x, item.y + 1 dropDownMenuAdd(workspace, item.contextMenu) workspace:draw() end local function menuAddContextMenuItem(menu, ...) local item = menu:addItem(...) item.switchMode = true item.onTouch = menuContextMenuItemOnTouch item.contextMenu = contextMenuCreate(1, 1) item.contextMenu.onMenuClosed = function() item.pressed = false item.workspace:draw() end return item.contextMenu end function GUI.menu(x, y, width, backgroundColor, textColor, backgroundPressedColor, textPressedColor, backgroundTransparency) local menu = GUI.container(x, y, width, 1) menu.colors = { default = { background = backgroundColor, text = textColor, }, selected = { background = backgroundPressedColor, text = textPressedColor, }, transparency = backgroundTransparency } menu.blockScreenEvents = true menu.addContextMenuItem = menuAddContextMenuItem menu.addItem = menuAddItem menu.getItem = menuGetItem menu.draw = menuDraw -- menu:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL) -- menu:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) -- menu:setSpacing(1, 1, 0) -- menu:setMargin(1, 1, 1, 0) return menu end --------------------------------------------------------------------------------------------------- local function progressIndicatorDraw(self) local color = self.active and (self.position == 1 and self.colors.secondary or self.colors.primary) or self.colors.passive screen.drawText(self.x + 1, self.y, color, "⢀") screen.drawText(self.x + 2, self.y, color, "⡀") color = self.active and (self.position == 2 and self.colors.secondary or self.colors.primary) or self.colors.passive screen.drawText(self.x + 3, self.y + 1, color, "⠆") screen.drawText(self.x + 2, self.y + 1, color, "⢈") color = self.active and (self.position == 3 and self.colors.secondary or self.colors.primary) or self.colors.passive screen.drawText(self.x + 1, self.y + 2, color, "⠈") screen.drawText(self.x + 2, self.y + 2, color, "⠁") color = self.active and (self.position == 4 and self.colors.secondary or self.colors.primary) or self.colors.passive screen.drawText(self.x, self.y + 1, color, "⠰") screen.drawText(self.x + 1, self.y + 1, color, "⡁") end local function progressIndicatorRoll(self) self.position = self.position + 1 if self.position > 4 then self.position = 1 end end local function progressIndicatorReset(self, state) self.active = state self.position = 1 end function GUI.progressIndicator(x, y, passiveColor, primaryColor, secondaryColor) local object = GUI.object(x, y, 4, 3) object.colors = { passive = passiveColor, primary = primaryColor, secondary = secondaryColor } object.active = false object.reset = progressIndicatorReset object.draw = progressIndicatorDraw object.roll = progressIndicatorRoll object:reset() return object end --------------------------------------------------------------------------------------------------- local function tableHeaderDraw(self) screen.drawRectangle(self.x, self.y, self.width, self.height, self.parent.colors.headerBackground, self.parent.colors.headerForeground, " ") screen.drawText(self.x + 1, self.y, self.parent.colors.headerForeground, self.text) end local function tableAddColumn(self, headerText, sizePolicy, size) layoutAddColumn(self, sizePolicy, size) local lastColumn = #self.columnSizes local header = self:setPosition(lastColumn, 1, self:addChild(GUI.object(1, 1, 1, self.itemHeight))) header.text = headerText header.draw = tableHeaderDraw for row = 1, 2 do self:setAlignment(lastColumn, row, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) self:setSpacing(lastColumn, row, 0) self:setFitting(lastColumn, row, true, false) end end local function tableAddRow(self, ...) local objects, columnCount = {...}, #self.columnSizes local index = #self.children - columnCount + 1 if #objects == columnCount then for i = #objects, 1, -1 do local object = self:setPosition(i, 2, self:addChild(objects[i], index)) object.height = self.itemHeight object.alternative = self.nextRowAlternative end self.nextRowAlternative = not self.nextRowAlternative else error("Failed to add row: count of columns ~= count of objects in row") end end local function tableUpdateSelection(self) local columnCount, row = #self.columnSizes, 1 for i = 1, #self.children - columnCount, columnCount do for j = i, i + columnCount - 1 do self.children[j].selected = self.selectedRows[row] end row = row + 1 end end local function tableClear(self) local columnCount, childrenCount = #self.columnSizes, #self.children if childrenCount > columnCount then self:removeChildren(1, childrenCount - columnCount) end self.selectedRows, self.nextRowAlternative = {}, nil end function GUI.tableCellEventHandler(workspace, self, e1, e2, e3, e4, e5, ...) if e1 == "touch" or e1 == "drag" or e1 == "double_touch" then local row = math.ceil(self:indexOf() / #self.parent.columnSizes) -- Deselecting all rows if (e5 == 0 or not self.parent.selectedRows[row]) and not (keyboard.isControlDown() or keyboard.isCommandDown()) then self.parent.selectedRows = {} end -- Selecting this row self.parent.selectedRows[row] = true tableUpdateSelection(self.parent) if self.parent.onCellTouch then self.parent.onCellTouch(workspace, self, e1, e2, e3, e4, e5, ...) end workspace:draw() end end function GUI.tableCellDraw(self) local background, foreground if self.selected then background, foreground = self.colors.selectionBackground or self.parent.colors.itemSelectionBackground, self.colors.selectionForeground or self.parent.colors.itemSelectionForeground elseif self.alternative then background, foreground = self.colors.alternativeBackground or self.parent.colors.itemAlternativeBackground, self.colors.alternativeForeground or self.parent.colors.itemAlternativeForeground else background, foreground = self.colors.defaultBackground or self.parent.colors.itemDefaultBackground, self.colors.defaultForeground or self.parent.colors.itemDefaultForeground end if background then screen.drawRectangle(self.x, self.y, self.width, self.height, background, foreground or 0x0, " ") end return foreground or 0x0 end function GUI.tableCell() local cell = GUI.object(1, 1, 1, 1) cell.colors = {} cell.draw = GUI.tableCellDraw cell.eventHandler = GUI.tableCellEventHandler return cell end local function tableTextCellDraw(self) screen.drawText(self.x + 1, self.y, GUI.tableCellDraw(self) or 0x0, self.text) end function GUI.tableTextCell(text) local cell = GUI.tableCell() cell.text = text cell.draw = tableTextCellDraw return cell end local function tableDraw(self) -- Items background screen.drawRectangle(self.x, self.y + self.itemHeight, self.width, self.height - self.itemHeight, self.colors.background, 0x0, " ") -- Content layoutDraw(self) end local function tableGetVerticalScroll(self) local horizontalMargin, verticalMargin = self:getMargin(1, 2) return verticalMargin end local function tableSetVerticalScroll(self, value) local horizontalMargin = self:getMargin(1, 2) local columnCount = #self.columnSizes local maxValue = -self.itemHeight * (#self.children - columnCount) / columnCount + 1 for i = 1, columnCount do self:setMargin(i, 2, horizontalMargin, math.max( maxValue, math.min( 0, value ) ) ) end end function GUI.tableEventHandler(workspace, self, e1, e2, e3, e4, e5, ...) if e1 == "touch" then local itemTouched = false for i = 1, #self.children do if self.children[i]:isPointInside(math.ceil(e3), math.ceil(e4)) then itemTouched = true break end end if not itemTouched then if self.onBackgroundTouch then self.onBackgroundTouch(workspace, self, e1, e2, e3, e4, e5, ...) end end elseif e1 == "scroll" then self:setVerticalScroll(self:getVerticalScroll() + e5) workspace:draw() end end function GUI.table( x, y, width, height, itemHeight, background, headerBackground, headerForeground, itemDefaultBackground, itemDefaultForeground, itemAlternativeBackground, itemAlternativeForeground, itemSelectionBackground, itemSelectionForeground ) local table = GUI.layout(x, y, width, height, 0, 2) table.colors = { background = background, headerBackground = headerBackground, headerForeground = headerForeground, itemDefaultBackground = itemDefaultBackground, itemDefaultForeground = itemDefaultForeground, itemAlternativeBackground = itemAlternativeBackground, itemAlternativeForeground = itemAlternativeForeground, itemSelectionBackground = itemSelectionBackground, itemSelectionForeground = itemSelectionForeground } table.blockScreenEvents = true table.itemHeight = itemHeight table.selectedRows = {} table.addColumn = tableAddColumn table.addRow = tableAddRow table.setVerticalScroll = tableSetVerticalScroll table.getVerticalScroll = tableGetVerticalScroll table.clear = tableClear table.draw = tableDraw table.eventHandler = GUI.tableEventHandler table:setRowHeight(1, GUI.SIZE_POLICY_ABSOLUTE, itemHeight) table:setRowHeight(2, GUI.SIZE_POLICY_RELATIVE, 1.0) return table end --------------------------------------------------------------------------------------------------- return GUI