----------------------------------------- Libraries ----------------------------------------- local libraries = { advancedLua = "advancedLua", buffer = "doubleBuffering", unicode = "unicode", event = "event", } for library in pairs(libraries) do if not _G[library] then _G[library] = require(libraries[library]) end end; libraries = nil ----------------------------------------- Core constants ----------------------------------------- local GUI = {} GUI.alignment = { horizontal = enum( "left", "center", "right" ), vertical = enum( "top", "center", "bottom" ) } GUI.directions = enum( "horizontal", "vertical" ) GUI.colors = { disabled = { background = 0x888888, text = 0xAAAAAA }, contextMenu = { background = 0xFFFFFF, separator = 0xAAAAAA, default = { text = 0x2D2D2D }, disabled = { text = 0xAAAAAA }, pressed = { background = 0x3366CC, text = 0xFFFFFF }, transparency = { background = 20, shadow = 50 } } } GUI.contextMenuElementTypes = enum( "default", "separator" ) GUI.objectTypes = enum( "empty", "panel", "label", "button", "framedButton", "image", "windowActionButtons", "windowActionButton", "tabBar", "tabBarTab", "menu", "menuElement", "window", "inputTextBox", "textBox", "horizontalSlider", "switch", "progressBar" ) ----------------------------------------- Primitive objects ----------------------------------------- -- Universal method to check if object was clicked by following coordinates local function isObjectClicked(object, x, y) if x >= object.x and y >= object.y and x <= object.x + object.width - 1 and y <= object.y + object.height - 1 and not object.disabled and not object.invisible ~= false then return true end return false end -- Limit object's text field to its' size local function objectTextLimit(object) local text, textLength = object.text, unicode.len(object.text) if textLength > object.width then text = unicode.sub(text, 1, object.width); textLength = object.width end return text, textLength end -- Base object to use in everything function GUI.object(x, y, width, height) return { x = x, y = y, width = width, height = height, isClicked = isObjectClicked } end ----------------------------------------- Object alignment ----------------------------------------- -- Set children alignment in parent object function GUI.setAlignment(object, horizontalAlignment, verticalAlignment) object.alignment = { horizontal = horizontalAlignment, vertical = verticalAlignment } return object end -- Get subObject position inside of parent object function GUI.getAlignmentCoordinates(object, subObject) local x, y if object.alignment.horizontal == GUI.alignment.horizontal.left then x = object.x elseif object.alignment.horizontal == GUI.alignment.horizontal.center then x = math.floor(object.x + object.width / 2 - subObject.width / 2) elseif object.alignment.horizontal == GUI.alignment.horizontal.right then x = object.x + object.width - subObject.width else error("Unknown horizontal alignment: " .. tostring(object.alignment.horizontal)) end if object.alignment.vertical == GUI.alignment.vertical.top then y = object.y elseif object.alignment.vertical == GUI.alignment.vertical.center then y = math.floor(object.y + object.height / 2 - subObject.height / 2) elseif object.alignment.vertical == GUI.alignment.vertical.bottom then y = object.y + object.height - subObject.height else error("Unknown vertical alignment: " .. tostring(object.alignment.vertical)) end return x, y end ----------------------------------------- Containers ----------------------------------------- -- Object calling by it's name using 'container["objectName"]' against of 'container.children[1, 2, 3, ...]' function GUI.setContainerMetasearch(container) setmetatable(container, { __index = function(container, objectName) for objectIndex = 1, #container.children do if container.children[objectIndex].name == objectName then return container.children[objectIndex] end end end }) end -- Go recursively through every container's object (including other containers) and return object that was clicked firstly by it's GUI-layer position function GUI.getClickedObject(container, xEvent, yEvent) local clickedObject, clickedIndex for objectIndex = #container.children, 1, -1 do container.children[objectIndex].x, container.children[objectIndex].y = container.children[objectIndex].localPosition.x + container.x - 1, container.children[objectIndex].localPosition.y + container.y - 1 if container.children[objectIndex].children and #container.children[objectIndex].children > 0 then clickedObject, clickedIndex = GUI.getClickedObject(container.children[objectIndex], xEvent, yEvent) if clickedObject then break end elseif not container.children[objectIndex].disableClicking and container.children[objectIndex]:isClicked(xEvent, yEvent) then clickedObject, clickedIndex = container.children[objectIndex], objectIndex break end end return clickedObject, clickedIndex end -- Add any object as children to parent container with specified objectName local function addObjectToContainer(container, objectType, objectName, object) object.name = objectName object.type = objectType object.parent = container object.localPosition = {x = object.x, y = object.y} table.insert(container.children, object) return object end -- Add empty GUI.object to container local function addEmptyObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.empty, objectName, GUI.object(...)) end -- Add button object to container local function addButtonObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.button, objectName, GUI.button(...)) end -- Add adaptive button object to container local function addAdaptiveButtonObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.button, objectName, GUI.adaptiveButton(...)) end -- Add framedButton object to container local function addFramedButtonObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.button, objectName, GUI.framedButton(...)) end -- Add adaptive framedButton object to container local function addAdaptiveFramedButtonObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.button, objectName, GUI.adaptiveFramedButton(...)) end -- Add label object to container local function addLabelObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.label, objectName, GUI.label(...)) end -- Add panel object to container local function addPanelObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.panel, objectName, GUI.panel(...)) end -- Add windowActionButtons object to container local function addWindowActionButtonsObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.windowActionButtons, objectName, GUI.windowActionButtons(...)) end -- Add another container to container local function addContainerToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.container, objectName, GUI.container(...)) end -- Add image object to container local function addImageObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.image, objectName, GUI.image(...)) end -- Add image object to container local function addTabBarObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.tabBar, objectName, GUI.tabBar(...)) end -- Add InputTextBox object to container local function addInputTextBoxObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.inputTextBox, objectName, GUI.inputTextBox(...)) end -- Add TextBox object to container local function addTextBoxObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.textBox, objectName, GUI.textBox(...)) end -- Add Horizontal Slider object to container local function addHorizontalSliderObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.horizontalSlider, objectName, GUI.horizontalSlider(...)) end -- Add Progressbar object to container local function addProgressBarObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.progressBar, objectName, GUI.progressBar(...)) end -- Add Switch object to container local function addSwitchObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.switch, objectName, GUI.switch(...)) end -- Recursively draw container's content including all children container's content local function drawContainerContent(container) for objectIndex = 1, #container.children do container.children[objectIndex].x, container.children[objectIndex].y = container.children[objectIndex].localPosition.x + container.x - 1, container.children[objectIndex].localPosition.y + container.y - 1 if container.children[objectIndex].children then -- drawContainerContent(container.children[objectIndex]) container.children[objectIndex]:draw() else if container.children[objectIndex].draw then container.children[objectIndex]:draw() else error("Container object with index " .. objectIndex .. " and name \"" .. tostring(container.children[objectIndex].name) .. "\" doesn't have :draw() method") end end end return container end -- Delete every container's children object local function deleteContainersContent(container) for objectIndex = 1, #container.children do container.children[objectIndex] = nil end end -- Universal container to store any other objects like buttons, labels, etc function GUI.container(x, y, width, height) local container = GUI.object(x, y, width, height) container.children = {} GUI.setContainerMetasearch(container) container.draw = drawContainerContent container.getClickedObject = GUI.getClickedObject container.deleteObjects = deleteContainersContent container.addObject = addEmptyObjectToContainer container.addContainer = addContainerToContainer container.addPanel = addPanelObjectToContainer container.addLabel = addLabelObjectToContainer container.addButton = addButtonObjectToContainer container.addAdaptiveButton = addAdaptiveButtonObjectToContainer container.addFramedButton = addFramedButtonObjectToContainer container.addAdaptiveFramedButton = addAdaptiveFramedButtonObjectToContainer container.addWindowActionButtons = addWindowActionButtonsObjectToContainer container.addImage = addImageObjectToContainer container.addTabBar = addTabBarObjectToContainer container.addTextBox = addTextBoxObjectToContainer container.addInputTextBox = addInputTextBoxObjectToContainer container.addHorizontalSlider = addHorizontalSliderObjectToContainer container.addSwitch = addSwitchObjectToContainer container.addProgressBar = addProgressBarObjectToContainer return container end ----------------------------------------- Buttons ----------------------------------------- local function drawButton(object) local text, textLength = objectTextLimit(object) local xText, yText = GUI.getAlignmentCoordinates(object, {width = textLength, height = 1}) local buttonColor = object.disabled and object.colors.disabled.background or (object.pressed and object.colors.pressed.background or object.colors.default.background) local textColor = object.disabled and object.colors.disabled.text or (object.pressed and object.colors.pressed.text or object.colors.default.text) if buttonColor then if object.buttonType == GUI.objectTypes.button then buffer.square(object.x, object.y, object.width, object.height, buttonColor, textColor, " ") else buffer.frame(object.x, object.y, object.width, object.height, buttonColor) end end buffer.text(xText, yText, textColor, text) return object end local function pressButton(object) object.pressed = true drawButton(object) end local function releaseButton(object) object.pressed = nil drawButton(object) end local function pressAndReleaseButton(object, pressTime) pressButton(object) buffer.draw() os.sleep(pressTime or 0.2) releaseButton(object) buffer.draw() end -- Создание таблицы кнопки со всеми необходимыми параметрами local function createButtonObject(buttonType, x, y, width, height, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState) local object = GUI.object(x, y, width, height) object.colors = { default = { background = buttonColor, text = textColor }, pressed = { background = buttonPressedColor, text = textPressedColor }, disabled = { background = GUI.colors.disabled.background, text = GUI.colors.disabled.text, } } object.buttonType = buttonType object.disabled = disabledState object.text = text object.press = pressButton object.release = releaseButton object.pressAndRelease = pressAndReleaseButton object.draw = drawButton object.setAlignment = GUI.setAlignment object:setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.center) return object end -- Кнопка фиксированных размеров function GUI.button(x, y, width, height, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState) return createButtonObject(GUI.objectTypes.button, x, y, width, height, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState) end -- Кнопка, подстраивающаяся под размер текста function GUI.adaptiveButton(x, y, xOffset, yOffset, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState) return createButtonObject(GUI.objectTypes.button, x, y, unicode.len(text) + xOffset * 2, yOffset * 2 + 1, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState) end -- Кнопка в рамке function GUI.framedButton(x, y, width, height, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState) return createButtonObject(GUI.objectTypes.framedButton, x, y, width, height, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState) end function GUI.adaptiveFramedButton(x, y, xOffset, yOffset, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState) return createButtonObject(GUI.objectTypes.framedButton, x, y, unicode.len(text) + xOffset * 2, yOffset * 2 + 1, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState) end ----------------------------------------- TabBar ----------------------------------------- local function drawTabBar(object) for tab = 1, #object.tabs.children do if tab == object.selectedTab then object.tabs.children[tab].pressed = true else object.tabs.children[tab].pressed = false end end object:reimplementedDraw() return object end function GUI.tabBar(x, y, width, height, spaceBetweenElements, backgroundColor, textColor, backgroundSelectedColor, textSelectedColor, ...) local elements, object = {...}, GUI.container(x, y, width, height) object.selectedTab = 1 object.tabsWidth = 0; for elementIndex = 1, #elements do object.tabsWidth = object.tabsWidth + unicode.len(elements[elementIndex]) + 2 + spaceBetweenElements end; object.tabsWidth = object.tabsWidth - spaceBetweenElements object.reimplementedDraw = object.draw object.draw = drawTabBar object:addPanel("backgroundPanel", 1, 1, object.width, object.height, backgroundColor).disableClicking = true object.tabs = object:addContainer("tabs", 1, 1, object.width, object.height) x = math.floor(width / 2 - object.tabsWidth / 2) for elementIndex = 1, #elements do local tab = object.tabs:addButton(elements[elementIndex], x, 1, unicode.len(elements[elementIndex]) + 2, height, backgroundColor, textColor, backgroundSelectedColor, textSelectedColor, elements[elementIndex]) tab.type = GUI.objectTypes.tabBarTab x = x + tab.width + spaceBetweenElements end return object end ----------------------------------------- Panel ----------------------------------------- local function drawPanel(object) buffer.square(object.x, object.y, object.width, object.height, object.colors.background, 0x000000, " ", 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 ----------------------------------------- Label ----------------------------------------- local function drawLabel(object) local text, textLength = objectTextLimit(object) local xText, yText = GUI.getAlignmentCoordinates(object, {width = textLength, height = 1}) buffer.text(xText, yText, object.colors.text, 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 ----------------------------------------- Image ----------------------------------------- local function drawImage(object) buffer.image(object.x, object.y, object.image) return object end function GUI.image(x, y, image) local object = GUI.object(x, y, image.width, image.height) object.image = image object.draw = drawImage return object end ----------------------------------------- Window action buttons ----------------------------------------- local function drawWindowActionButton(object) local background = buffer.get(object.x, object.y) object.colors.default.background, object.colors.pressed.background, object.colors.disabled.background = background, background, background object:reimplementedDraw() return object end function GUI.windowActionButtons(x, y, fatSymbol) local symbol = fatSymbol and "⬤" or "●" local container = GUI.container(x, y, 5, 1) local closeButton = container:addButton("close", 1, 1, 1, 1, 0x000000, 0xFF4940, 0x000000, 0x992400, symbol) local minimizeButton = container:addButton("minimize", 3, 1, 1, 1, 0x000000, 0xFFB640, 0x000000, 0x996D00, symbol) local maximizeButton = container:addButton("maximize", 5, 1, 1, 1, 0x000000, 0x00B640, 0x000000, 0x006D40, symbol) closeButton.reimplementedDraw, minimizeButton.reimplementedDraw, maximizeButton.reimplementedDraw = closeButton.draw, minimizeButton.draw, maximizeButton.draw closeButton.draw, minimizeButton.draw, maximizeButton.draw = drawWindowActionButton, drawWindowActionButton, drawWindowActionButton return container end ----------------------------------------- Context Menu ----------------------------------------- local function drawContextMenuElement(contextMenuObject, elementIndex, isPressed) if contextMenuObject.elements[elementIndex].type == GUI.contextMenuElementTypes.default then local textColor = contextMenuObject.elements[elementIndex].disabled and GUI.colors.contextMenu.disabled.text or (contextMenuObject.elements[elementIndex].color or GUI.colors.contextMenu.default.text) if isPressed then buffer.square(contextMenuObject.x, contextMenuObject.y + elementIndex - 1, contextMenuObject.width, 1, GUI.colors.contextMenu.pressed.background, GUI.colors.contextMenu.pressed.text, " ") textColor = GUI.colors.contextMenu.pressed.text end buffer.text(contextMenuObject.x + 2, contextMenuObject.y + elementIndex - 1, textColor, contextMenuObject.elements[elementIndex].text) if contextMenuObject.elements[elementIndex].shortcut then buffer.text(contextMenuObject.x + contextMenuObject.width - unicode.len(contextMenuObject.elements[elementIndex].shortcut) - 2, contextMenuObject.y + elementIndex - 1, textColor, contextMenuObject.elements[elementIndex].shortcut) end else buffer.text(contextMenuObject.x, contextMenuObject.y + elementIndex - 1, GUI.colors.contextMenu.separator, string.rep("─", contextMenuObject.width)) end end local function drawContextMenu(contextMenuObject) buffer.square(contextMenuObject.x, contextMenuObject.y, contextMenuObject.width, contextMenuObject.height, GUI.colors.contextMenu.background, GUI.colors.contextMenu.default.text, " ", GUI.colors.contextMenu.transparency.background) GUI.windowShadow(contextMenuObject.x, contextMenuObject.y, contextMenuObject.width, contextMenuObject.height, GUI.colors.contextMenu.transparency.shadow, true) for elementIndex = 1, #contextMenuObject.elements do drawContextMenuElement(contextMenuObject, elementIndex, false) end end local function showContextMenu(contextMenuObject) local oldDrawLimit = buffer.getDrawLimit(); buffer.resetDrawLimit() -- Расчет ширины окна меню local longestElement, longestShortcut = 0, 0 for elementIndex = 1, #contextMenuObject.elements do if contextMenuObject.elements[elementIndex].type == GUI.contextMenuElementTypes.default then longestElement = math.max(longestElement, unicode.len(contextMenuObject.elements[elementIndex].text)) if contextMenuObject.elements[elementIndex].shortcut then longestShortcut = math.max(longestShortcut, unicode.len(contextMenuObject.elements[elementIndex].shortcut)) end end end contextMenuObject.width, contextMenuObject.height = longestElement + 4 + (longestShortcut > 0 and longestShortcut + 3 or 0), #contextMenuObject.elements -- А это чтоб за края экрана не лезло if contextMenuObject.y + contextMenuObject.height >= buffer.screen.height then contextMenuObject.y = buffer.screen.height - contextMenuObject.height end if contextMenuObject.x + contextMenuObject.width + 1 >= buffer.screen.width then contextMenuObject.x = buffer.screen.width - contextMenuObject.width - 1 end local oldPixels = buffer.copy(contextMenuObject.x, contextMenuObject.y, contextMenuObject.width + 1, contextMenuObject.height + 1) local function quit() buffer.paste(contextMenuObject.x, contextMenuObject.y, oldPixels) buffer.draw() buffer.setDrawLimit(oldDrawLimit) end drawContextMenu(contextMenuObject) buffer.draw() while true do local e = {event.pull()} if e[1] == "touch" then local objectFound = false for elementIndex = 1, #contextMenuObject.elements do if e[3] >= contextMenuObject.x and e[3] <= contextMenuObject.x + contextMenuObject.width - 1 and e[4] == contextMenuObject.y + elementIndex - 1 then objectFound = true if not contextMenuObject.elements[elementIndex].disabled and contextMenuObject.elements[elementIndex].type == GUI.contextMenuElementTypes.default then drawContextMenuElement(contextMenuObject, elementIndex, true) buffer.draw() os.sleep(0.2) quit() return contextMenuObject.elements[elementIndex].text end break end end if not objectFound then quit(); return end end end end local function addContextMenuElement(contextMenuObject, text, disabled, shortcut, color) local element = {} element.type = GUI.contextMenuElementTypes.default element.text = text element.disabled = disabled element.shortcut = shortcut element.color = color or GUI.colors.contextMenu.default.text --OPTIMIZATION table.insert(contextMenuObject.elements, element) return element end local function addContextMenuSeparator(contextMenuObject) local element = {type = GUI.contextMenuElementTypes.separator} table.insert(contextMenuObject.elements, element) return element end function GUI.contextMenu(x, y, ...) local argumentElements = {...} local contextMenuObject = GUI.object(x, y, 1, 1) contextMenuObject.elements = {} contextMenuObject.addElement = addContextMenuElement contextMenuObject.addSeparator = addContextMenuSeparator contextMenuObject.show = showContextMenu contextMenuObject.selectedElement = nil for elementIndex = 1, #argumentElements do if argumentElements[elementIndex] == "-" then contextMenuObject:addSeparator() else contextMenuObject:addElement(argumentElements[elementIndex][1], argumentElements[elementIndex][2], argumentElements[elementIndex][3], argumentElements[elementIndex][4]) end end return contextMenuObject end ----------------------------------------- Menu ----------------------------------------- function GUI.menu(x, y, width, backgroundColor, textColor, backgroundPressedColor, textPressedColor, backgroundTransparency, ...) local elements = {...} local menuObject = GUI.container(x, y, width, 1) menuObject:addPanel("backgroundPanel", 1, 1, menuObject.width, 1, backgroundColor, backgroundTransparency).disableClicking = true local x = 2 for elementIndex = 1, #elements do local button = menuObject:addAdaptiveButton(elementIndex, x, 1, 1, 0, nil, elements[elementIndex][2] or textColor, elements[elementIndex][3] or backgroundPressedColor, elements[elementIndex][4] or textPressedColor, elements[elementIndex][1]) button.type = GUI.objectTypes.menuElement x = x + button.width end return menuObject end ----------------------------------------- ProgressBar Object ----------------------------------------- local function drawProgressBar(object) local activeWidth = math.floor(object.value * object.width / 100) if object.thin then buffer.text(object.x, object.y, object.colors.passive, string.rep("━", object.width)) buffer.text(object.x, object.y, object.colors.active, string.rep("━", activeWidth)) else buffer.square(object.x, object.y, object.width, object.height, object.colors.passive) buffer.square(object.x, object.y, activeWidth, object.height, object.colors.active) end if object.showValue then local stringValue = tostring((object.valuePrefix or "") .. object.value .. (object.valuePostfix or "")) buffer.text(math.floor(object.x + object.width / 2 - unicode.len(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 ----------------------------------------- Other GUI elements ----------------------------------------- function GUI.windowShadow(x, y, width, height, transparency, thin) transparency = transparency or 50 if thin then buffer.square(x + width, y + 1, 1, height - 1, 0x000000, 0x000000, " ", transparency) buffer.text(x + 1, y + height, 0x000000, string.rep("▀", width), transparency) buffer.text(x + width, y, 0x000000, "▄", transparency) else buffer.square(x + width, y + 1, 2, height, 0x000000, 0x000000, " ", transparency) buffer.square(x + 2, y + height, width - 2, 1, 0x000000, 0x000000, " ", transparency) end end ------------------------------------------------- Окна ------------------------------------------------------------------- -- Красивое окошко для отображения сообщения об ошибке. Аргумент errorWindowParameters может принимать следующие значения: -- local errorWindowParameters = { -- backgroundColor = 0x262626, -- textColor = 0xFFFFFF, -- truncate = 50, -- title = {color = 0xFF8888, text = "Ошибочка"} -- noAnimation = true, -- } function GUI.error(text, errorWindowParameters) --Всякие константы, бла-бла local backgroundColor = (errorWindowParameters and errorWindowParameters.backgroundColor) or 0x1b1b1b local errorPixMap = { {{0xffdb40 , 0xffffff,"#"}, {0xffdb40 , 0xffffff, "#"}, {backgroundColor, 0xffdb40, "▟"}, {backgroundColor, 0xffdb40, "▙"}, {0xffdb40 , 0xffffff, "#"}, {0xffdb40 , 0xffffff, "#"}}, {{0xffdb40 , 0xffffff,"#"}, {backgroundColor, 0xffdb40, "▟"}, {0xffdb40 , 0xffffff, " "}, {0xffdb40 , 0xffffff, " "}, {backgroundColor, 0xffdb40, "▙"}, {0xffdb40 , 0xffffff, "#"}}, {{backgroundColor, 0xffdb40,"▟"}, {0xffdb40 , 0xffffff, "c"}, {0xffdb40 , 0xffffff, "y"}, {0xffdb40 , 0xffffff, "k"}, {0xffdb40 , 0xffffff, "a"}, {backgroundColor, 0xffdb40, "▙"}}, } local textColor = (errorWindowParameters and errorWindowParameters.textColor) or 0xFFFFFF local buttonWidth = 12 local verticalOffset = 2 local minimumHeight = verticalOffset * 2 + #errorPixMap local height = 0 local widthOfText = math.floor(buffer.screen.width * 0.5) --Ебемся с текстом, делаем его пиздатым во всех смыслах if type(text) ~= "table" then text = tostring(text) text = (errorWindowParameters and errorWindowParameters.truncate) and unicode.sub(text, 1, errorWindowParameters.truncate) or text text = { text } end text = string.wrap(text, widthOfText) --Ебашим высоту правильнуюe height = verticalOffset * 2 + #text + 1 if errorWindowParameters and errorWindowParameters.title then height = height + 2 end if height < minimumHeight then height = minimumHeight end --Ебашим стартовые коорды отрисовки local x, y = math.ceil(buffer.screen.width / 2 - widthOfText / 2), math.ceil(buffer.screen.height / 2 - height / 2) local OKButton = {} local oldPixels = buffer.copy(1, y, buffer.screen.width, height) --Отрисовочка local function draw() local yPos = y --Подложка buffer.square(1, yPos, buffer.screen.width, height, backgroundColor, 0x000000); yPos = yPos + verticalOffset buffer.customImage(x - #errorPixMap[1] - 3, yPos, errorPixMap) --Титл, епта! if errorWindowParameters and errorWindowParameters.title then buffer.text(x, yPos, errorWindowParameters.title.color, errorWindowParameters.title.text); yPos = yPos + 2 end --Текстус for i = 1, #text do buffer.text(x, yPos, textColor, text[i]); yPos = yPos + 1 end; yPos = yPos + 1 --Кнопачка OKButton = GUI.button(x + widthOfText - buttonWidth, y + height - 2, buttonWidth, 1, 0x3392FF, 0xFFFFFF, 0xFFFFFF, 0x262626, "OK"):draw() --Атрисовачка buffer.draw() end --Графонистый выход local function quit() OKButton:pressAndRelease(0.2) buffer.paste(1, y, oldPixels) buffer.draw() end --Онимацыя if not (errorWindowParameters and errorWindowParameters.noAnimation) then for i = 1, height do buffer.setDrawLimit(1, math.floor(buffer.screen.height / 2) - i, buffer.screen.width, i * 2); draw(); os.sleep(0.05) end; buffer.resetDrawLimit() end draw() --Анализ говнища while true do local e = {event.pull()} if e[1] == "key_down" then if e[4] == 28 then quit(); return end elseif e[1] == "touch" then if OKButton:isClicked(e[3], e[4]) then quit(); return end end end end ----------------------------------------- Universal keyboard-input function ----------------------------------------- local function findValue(t, whatToSearch) if type(t) ~= "table" then return end for key, value in pairs(t) do if type(key) == "string" and string.match(key, "^" .. whatToSearch) then local valueType, postfix = type(value), "" if valueType == "function" or (valueType == "table" and getmetatable(value) and getmetatable(value).__call) then postfix = "()" elseif valueType == "table" then postfix = "." end return key .. postfix end end end local function findTable(whereToSearch, t, whatToSearch) local beforeFirstDot = string.match(whereToSearch, "^[^%.]+%.") -- Если вообще есть таблица, где надо искать if beforeFirstDot then beforeFirstDot = unicode.sub(beforeFirstDot, 1, -2) if t[beforeFirstDot] then return findTable(unicode.sub(whereToSearch, unicode.len(beforeFirstDot) + 2, -1), t[beforeFirstDot], whatToSearch) else -- Кароч, слушай суда: вот в эту зону хуйня может зайти толька -- тагда, кагда ты вручную ебенишь массив вида "abc.cda.blabla.test" -- без автозаполнения, т.е. он МОЖЕТ быть неверным, однако прога все -- равно проверяет на верность, и вот если НИ ХУЯ такого говнища типа -- ... .blabla не существует, то интерхпретатор захуяривается СУДЫ -- И ЧТОБ БОЛЬШЕ ВОПРОСОВ НЕ ЗАДАВАЛ!11! end -- Или если таблиц либо ваще нету, либо рекурсия суда вон вошла else return findValue(t[whereToSearch], whatToSearch) end end local function autocompleteVariables(sourceText) local varPath = string.match(sourceText, "[a-zA-Z0-9%.%_]+$") if varPath then local prefix = string.sub(sourceText, 1, -unicode.len(varPath) - 1) local whereToSearch = string.match(varPath, "[a-zA-Z0-9%.%_]+%.") if whereToSearch then whereToSearch = unicode.sub(whereToSearch, 1, -2) local findedTable = findTable(whereToSearch, _G, unicode.sub(varPath, unicode.len(whereToSearch) + 2, -1)) return findedTable and prefix .. whereToSearch .. "." .. findedTable or sourceText else local findedValue = findValue(_G, varPath) return findedValue and prefix .. findedValue or sourceText end else return sourceText end end -- local inputProperties = { -- --Метод, получающий на вход текущий текст и проверяющий вводимые данные. В случае успеха должен возвращать true -- validator = function(text) if string.match(text, "^%d+$") then return true end -- -- Автоматически очищает текстовое поле при начале ввода информации в него. -- -- Если при окончании ввода тексет не будет соответствовать методу validator, указанному выше, -- -- то будет возвращено исходное значение текста (не очищенное) -- eraseTextWhenInputBegins = true -- --Отключает символ многоточия при выходе за пределы текстового поля -- disableDots = true, -- --Попросту отрисовывает всю необходимую информацию без активации нажатия на клавиши -- justDrawNotEvent = true, -- --Задержка между миганимем курсора -- cursorBlinkDelay = 1.5, -- --Цвет курсора -- cursorColor = 0xFF7777, -- --Символ, используемый для отрисовки курсора -- cursorSymbol = "▌", -- --Символ-маскировщик, на который будет визуально заменен весь вводимый текст. Полезно для полей ввода пароля -- maskTextWithSymbol = "*", -- --Активация подсветки Lua-синтаксиса -- highlightLuaSyntax = true, -- -- Активация автозаполнения названий переменных по нажатию клавиши Tab -- autocompleteVariables = true, -- } function GUI.input(x, y, width, foreground, startText, inputProperties) inputProperties = inputProperties or {} if not (y >= buffer.drawLimit.y and y <= buffer.drawLimit.y2) then return startText end local text = startText if not inputProperties.justDrawNotEvent and inputProperties.eraseTextWhenInputBegins then text = "" end local textLength = unicode.len(text) local cursorBlinkState = false local cursorBlinkDelay = inputProperties.cursorBlinkDelay and inputProperties.cursorBlinkDelay or 0.5 local cursorColor = inputProperties.cursorColor and inputProperties.cursorColor or 0x00A8FF local cursorSymbol = inputProperties.cursorSymbol and inputProperties.cursorSymbol or "┃" local oldPixels = {}; for i = x, x + width - 1 do table.insert(oldPixels, { buffer.get(i, y) }) end local function drawOldPixels() for i = 1, #oldPixels do buffer.set(x + i - 1, y, oldPixels[i][1], oldPixels[i][2], oldPixels[i][3]) end end local function getTextLength() textLength = unicode.len(text) end local function textFormat() local formattedText = text if inputProperties.maskTextWithSymbol then formattedText = string.rep(inputProperties.maskTextWithSymbol or "*", textLength) end if textLength > width then if inputProperties.disableDots then formattedText = unicode.sub(formattedText, -width, -1) else formattedText = "…" .. unicode.sub(formattedText, -width + 1, -1) end end return formattedText end local function draw() drawOldPixels() if inputProperties.highlightLuaSyntax then require("syntax").highlightString(x, y, textFormat(), 1, width) else buffer.text(x, y, foreground, textFormat()) end if cursorBlinkState then local cursorPosition = textLength < width and x + textLength or x + width - 1 local bg = buffer.get(cursorPosition, y) buffer.set(cursorPosition, y, bg, cursorColor, cursorSymbol) end if not inputProperties.justDrawNotEvent then buffer.draw() end end local function backspace() if unicode.len(text) > 0 then text = unicode.sub(text, 1, -2); getTextLength(); draw() end end local function quit() cursorBlinkState = false if inputProperties.validator and not inputProperties.validator(text) then text = startText draw() return startText end draw() return text end draw() if inputProperties.justDrawNotEvent then return startText end while true do local e = { event.pull(cursorBlinkDelay) } if e[1] == "key_down" then if e[4] == 14 then backspace() elseif e[4] == 28 then return quit() elseif e[4] == 15 then if inputProperties.autocompleteVariables then text = autocompleteVariables(text); getTextLength(); draw() end else local symbol = unicode.char(e[3]) if not keyboard.isControl(e[3]) then text = text .. symbol getTextLength() draw() end end elseif e[1] == "clipboard" then text = text .. e[3] getTextLength() draw() elseif e[1] == "touch" then return quit() else cursorBlinkState = not cursorBlinkState draw() end end end ----------------------------------------- Input Text Box object ----------------------------------------- local function drawInputTextBox(object, isFocused) local background = isFocused and object.colors.focused.background or object.colors.default.background local foreground = isFocused and object.colors.focused.text or object.colors.default.text local y = math.floor(object.y + object.height / 2) local text = isFocused and (object.text or "") or (object.text or object.placeholderText or "") if background then buffer.square(object.x, object.y, object.width, object.height, background, foreground, " ") end local resultText = GUI.input(object.x + 1, y, object.width - 2, foreground, text, { justDrawNotEvent = not isFocused, highlightLuaSyntax = isFocused and object.highlightLuaSyntax or nil, autocompleteVariables = object.autocompleteVariables or nil, maskTextWithSymbol = object.textMask or nil, validator = object.validator or nil, eraseTextWhenInputBegins = object.eraseTextOnFocus or nil, }) object.text = isFocused and resultText or object.text end local function inputTextBoxBeginInput(object) drawInputTextBox(object, true) if object.text == "" then object.text = nil; drawInputTextBox(object, false); buffer.draw() end end function GUI.inputTextBox(x, y, width, height, inputTextBoxColor, textColor, inputTextBoxFocusedColor, textFocusedColor, text, placeholderText, maskTextWithSymbol, eraseTextOnFocus) local object = GUI.object(x, y, width, height) object.colors = { default = { background = inputTextBoxColor, text = textColor }, focused = { background = inputTextBoxFocusedColor, text = textFocusedColor } } object.text = text object.placeholderText = placeholderText object.isClicked = isObjectClicked object.draw = drawInputTextBox object.input = inputTextBoxBeginInput object.eraseTextOnFocus = eraseTextOnFocus object.textMask = maskTextWithSymbol return object end ----------------------------------------- Text Box object ----------------------------------------- local function drawTextBox(object) if object.colors.background then buffer.square(object.x, object.y, object.width, object.height, object.colors.background, object.colors.text, " ") end local xPos, yPos = GUI.getAlignmentCoordinates(object, {width = 1, height = object.height - object.offset.vertical * 2}) local lineLimit = object.width - object.offset.horizontal * 2 for line = object.currentLine, object.currentLine + object.height - 1 do if object.lines[line] then local lineType, text, textColor = type(object.lines[line]) if lineType == "table" then text, textColor = string.limit(object.lines[line].text, lineLimit), object.lines[line].color elseif lineType == "string" then text, textColor = string.limit(object.lines[line], lineLimit), object.colors.text else error("Unknown TextBox line type: " .. tostring(lineType)) end xPos = GUI.getAlignmentCoordinates( { x = object.x + object.offset.horizontal, y = object.y + object.offset.vertical, width = object.width - object.offset.horizontal * 2, height = object.height - object.offset.vertical * 2, alignment = object.alignment }, {width = unicode.len(text), height = object.height} ) buffer.text(xPos, yPos, textColor, text) yPos = yPos + 1 else break end end return object end local function scrollDownTextBox(object, count) count = count or 1 local maxCountAvailableToScroll = #object.lines - object.height - object.currentLine + 1 count = math.min(count, maxCountAvailableToScroll) 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) object.currentLine = #lines return object end function GUI.textBox(x, y, width, height, backgroundColor, textColor, lines, currentLine, horizontalOffset, verticalOffset) local object = GUI.object(x, y, width, height) object.colors = { text = textColor, background = backgroundColor } object.setAlignment = GUI.setAlignment object:setAlignment(GUI.alignment.horizontal.left, GUI.alignment.vertical.top) object.lines = lines object.currentLine = currentLine or 1 object.draw = drawTextBox object.scrollUp = scrollUpTextBox object.scrollDown = scrollDownTextBox object.scrollToStart = scrollToStartTextBox object.scrollToEnd = scrollToEndTextBox object.offset = {horizontal = horizontalOffset or 0, vertical = verticalOffset or 0} return object end ----------------------------------------- Horizontal Slider Object ----------------------------------------- local function drawHorizontalSlider(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 math.roundToDecimalPlaces(object.maximumValue, 2)), tostring(object.roundValues and math.floor(object.maximumValue) or math.roundToDecimalPlaces(object.minimumValue, 2)) buffer.text(object.x - unicode.len(stringMinimumValue) - 1, object.y, object.colors.value, stringMinimumValue) buffer.text(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 math.roundToDecimalPlaces(object.value, 2)) .. (object.currentValuePostfix or "") buffer.text(math.floor(object.x + object.width / 2 - unicode.len(stringCurrentValue) / 2), object.y + 1, object.colors.value, stringCurrentValue) end -- Рисуем сам слайдер local activeWidth = math.floor(object.width - ((object.maximumValue - object.value) * object.width / (object.maximumValue - object.minimumValue))) buffer.text(object.x, object.y, object.colors.passive, string.rep("━", object.width)) buffer.text(object.x, object.y, object.colors.active, string.rep("━", activeWidth)) buffer.square(object.x + activeWidth - 1, object.y, 2, 1, object.colors.pipe, 0x000000, " ") return object end function GUI.horizontalSlider(x, y, width, activeColor, passiveColor, pipeColor, valueColor, minimumValue, maximumValue, value, showMaximumAndMinimumValues, currentValuePrefix, currentValuePostfix) local object = GUI.object(x, y, width, 1) object.colors = {active = activeColor, passive = passiveColor, pipe = pipeColor, value = valueColor} object.draw = drawHorizontalSlider object.minimumValue = minimumValue object.maximumValue = maximumValue object.value = value object.showMaximumAndMinimumValues = showMaximumAndMinimumValues object.currentValuePrefix = currentValuePrefix object.currentValuePostfix = currentValuePostfix object.roundValues = false return object end ----------------------------------------- Switch object ----------------------------------------- local function drawSwitch(object) local pipeWidth = object.height * 2 local pipePosition, backgroundColor if object.state then pipePosition, backgroundColor = object.x + object.width - pipeWidth, object.colors.active else pipePosition, backgroundColor = object.x, object.colors.passive end buffer.square(object.x, object.y, object.width, object.height, backgroundColor, 0x000000, " ") buffer.square(pipePosition, object.y, pipeWidth, object.height, object.colors.pipe, 0x000000, " ") return object end function GUI.switch(x, y, width, activeColor, passiveColor, pipeColor, state) local object = GUI.object(x, y, width, 1) object.colors = {active = activeColor, passive = passiveColor, pipe = pipeColor, value = valueColor} object.draw = drawSwitch object.state = state or false return object end -------------------------------------------------------------------------------------------------------------------------------- -- buffer.clear(0x1b1b1b) -- buffer.draw(true) -- GUI.switch(2, 2, 8, 0x77FF77, 0x999999, 0xFFFFFF, true):draw() -- buffer.draw() -------------------------------------------------------------------------------------------------------------------------------- return GUI