MineOS/lib/GUI.lua
2018-05-14 12:00:09 +03:00

4246 lines
135 KiB
Lua
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Detailed documentation can be found here:
-- https://github.com/IgorTimofeev/OpenComputers/blob/master/Documentation/GUI.md
-----------------------------------------------------------------------
require("advancedLua")
local component = require("component")
local computer = require("computer")
local keyboard = require("keyboard")
local fs = require("filesystem")
local unicode = require("unicode")
local event = require("event")
local color = require("color")
local image = require("image")
local buffer = require("doubleBuffering")
-----------------------------------------------------------------------
local GUI = {}
GUI.alignment = {
horizontal = enum(
"left",
"center",
"right"
),
vertical = enum(
"top",
"center",
"bottom"
)
}
GUI.directions = enum(
"horizontal",
"vertical"
)
GUI.sizePolicies = enum(
"percentage",
"absolute"
)
GUI.dropDownMenuItemTypes = enum(
"default",
"separator"
)
GUI.filesystemModes = enum(
"file",
"directory",
"both",
"open",
"save"
)
GUI.colors = {
disabled = {
background = 0x878787,
text = 0xA5A5A5
},
contextMenu = {
separator = 0x878787,
default = {
background = 0xFFFFFF,
text = 0x2D2D2D
},
disabled = 0x878787,
pressed = {
background = 0x3366CC,
text = 0xFFFFFF
},
transparency = {
background = 0.24,
shadow = 0.4
}
},
fadeContainer = {
transparency = 0.3,
title = 0xE1E1E1
},
windows = {
shadowTransparency = 0.5
},
syntaxHighlighting = {
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,
}
}
GUI.luaSyntaxPatterns = {
{"[%.%,%>%<%=%~%+%-%*%/%^%#%%%&]", "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%s?", "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},
}
GUI.paletteConfigPath = "/lib/.palette.cfg"
-----------------------------------------------------------------------
local function objectIsPointInside(object, x, y)
return
x >= object.x and
x <= object.x + object.width - 1 and
y >= object.y and
y <= object.y + object.height - 1
end
local function objectDraw(object)
return object
end
function GUI.object(x, y, width, height)
return {
x = x,
y = y,
width = width,
height = height,
draw = objectDraw,
isPointInside = objectIsPointInside,
}
end
-----------------------------------------------------------------------
function GUI.setAlignment(object, horizontalAlignment, verticalAlignment)
object.alignment = {
horizontal = horizontalAlignment,
vertical = verticalAlignment
}
return object
end
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 = 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 = 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
function GUI.getMarginCoordinates(object)
local x, y = object.x, object.y
if object.alignment.horizontal == GUI.alignment.horizontal.left then
x = x + object.margin.horizontal
elseif object.alignment.horizontal == GUI.alignment.horizontal.right then
x = x - object.margin.horizontal
end
if object.alignment.vertical == GUI.alignment.vertical.top then
y = y + object.margin.vertical
elseif object.alignment.vertical == GUI.alignment.vertical.bottom then
y = y - object.margin.vertical
end
return x, y
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[index], object.parent.children[index + 1] = object.parent.children[index + 1], object.parent.children[index]
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 containerObjectGetFirstParent(object)
local currentParent = object.parent
while currentParent.parent do
currentParent = currentParent.parent
end
return currentParent
end
local function containerObjectSelfDelete(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 containerObjectAnimationDelete(animation)
animation.deleteLater = true
end
local function containerObjectAddAnimation(object, frameHandler, onFinish)
local animation = {
object = object,
position = 0,
start = containerObjectAnimationStart,
stop = containerObjectAnimationStop,
delete = containerObjectAnimationDelete,
frameHandler = frameHandler,
onFinish = onFinish,
}
local firstParent = object:getFirstParent()
firstParent.animations = firstParent.animations or {}
table.insert(firstParent.animations, animation)
return animation
end
function GUI.addChildToContainer(container, object, atIndex)
object.localX = object.x
object.localY = object.y
object.indexOf = containerObjectIndexOf
object.moveToFront = containerObjectMoveToFront
object.moveToBack = containerObjectMoveToBack
object.moveForward = containerObjectMoveForward
object.moveBackward = containerObjectMoveBackward
object.getFirstParent = containerObjectGetFirstParent
object.delete = containerObjectSelfDelete
object.parent = container
object.addAnimation = containerObjectAddAnimation
if atIndex then
table.insert(container.children, atIndex, object)
else
table.insert(container.children, object)
end
return object
end
local function deleteContainersContent(container, from, to)
from = from or 1
for objectIndex = from, to or #container.children do
table.remove(container.children, from)
end
end
local function getRectangleIntersection(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
function GUI.drawContainerContent(container)
local R1X1, R1Y1, R1X2, R1Y2, child = buffer.getDrawLimit()
local intersectionX1, intersectionY1, intersectionX2, intersectionY2 = getRectangleIntersection(
R1X1,
R1Y1,
R1X2,
R1Y2,
container.x,
container.y,
container.x + container.width - 1,
container.y + container.height - 1
)
if intersectionX1 then
buffer.setDrawLimit(intersectionX1, intersectionY1, intersectionX2, intersectionY2)
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
buffer.setDrawLimit(R1X1, R1Y1, R1X2, R1Y2)
end
return container
end
local function containerDrawOnScreen(container, ...)
container:draw()
buffer.draw(...)
end
local function containerHandler(isScreenEvent, mainContainer, currentContainer, intersectionX1, intersectionY1, intersectionX2, intersectionY2, e1, e2, e3, e4, ...)
local child, newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2
if not isScreenEvent or intersectionX1 and e3 >= intersectionX1 and e4 >= intersectionY1 and e3 <= intersectionX2 and e4 <= intersectionY2 then
if currentContainer.eventHandler then
if isScreenEvent then
if currentContainer:isPointInside(e3, e4) and not currentContainer.disabled then
currentContainer.eventHandler(mainContainer, currentContainer, e1, e2, e3, e4, ...)
end
else
currentContainer.eventHandler(mainContainer, currentContainer, e1, e2, e3, e4, ...)
end
end
for i = #currentContainer.children, 1, -1 do
child = currentContainer.children[i]
if not child.hidden then
if child.children then
newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2 = getRectangleIntersection(
intersectionX1,
intersectionY1,
intersectionX2,
intersectionY2,
child.x,
child.y,
child.x + child.width - 1,
child.y + child.height - 1
)
if newIntersectionX1 and containerHandler(isScreenEvent, mainContainer, child, newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2, e1, e2, e3, e4, ...) then
return true
end
else
if isScreenEvent then
if child:isPointInside(e3, e4) then
if child.eventHandler and not child.disabled then
child.eventHandler(mainContainer, child, e1, e2, e3, e4, ...)
end
return true
end
elseif child.eventHandler then
child.eventHandler(mainContainer, child, e1, e2, e3, e4, ...)
end
end
end
end
end
end
local function containerStartEventHandling(container, eventHandlingDelay)
container.eventHandlingDelay = eventHandlingDelay
local animationIndex, animation, animationOnFinishMethods, 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
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(container.animations and 0 or container.eventHandlingDelay)
containerHandler(
(
e1 == "touch" or
e1 == "drag" or
e1 == "drop" or
e1 == "scroll" or
e1 == "double_touch"
),
container,
container,
container.x,
container.y,
container.x + container.width - 1,
container.y + container.height - 1,
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
)
if container.animations then
animationIndex, animationOnFinishMethods = 1, {}
-- Продрачиваем анимации и вызываем обработчики кадров
while animationIndex <= #container.animations do
animation = container.animations[animationIndex]
if animation.deleteLater then
table.remove(container.animations, animationIndex)
if #container.animations == 0 then
container.animations = nil
break
end
else
if animation.started then
animation.position = (computer.uptime() - animation.startUptime) / animation.duration
if animation.position < 1 then
animation.frameHandler(container, animation)
else
animation.position = 1
animation.started = false
animation.frameHandler(container, animation)
if animation.onFinish then
table.insert(animationOnFinishMethods, animation)
end
end
end
animationIndex = animationIndex + 1
end
end
-- По завершению продрочки отрисовываем изменения на экране
container:drawOnScreen()
-- Вызываем поочередно все методы .onFinish
for i = 1, #animationOnFinishMethods do
animationOnFinishMethods[i].onFinish(container, animationOnFinishMethods[i])
end
end
until container.needClose
end
local function containerStopEventHandling(container)
container.needClose = true
end
function GUI.container(x, y, width, height)
local container = GUI.object(x, y, width, height)
container.children = {}
container.draw = GUI.drawContainerContent
container.drawOnScreen = containerDrawOnScreen
container.deleteChildren = deleteContainersContent
container.addChild = GUI.addChildToContainer
container.returnData = containerReturnData
container.startEventHandling = containerStartEventHandling
container.stopEventHandling = containerStopEventHandling
return container
end
function GUI.fullScreenContainer()
return GUI.container(1, 1, buffer.getResolution())
end
-----------------------------------------------------------------------
local function buttonPlayAnimation(button, onFinish)
button.animationStarted = true
button:addAnimation(
function(mainContainer, 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(mainContainer, animation)
button.animationStarted = false
button.pressed = not button.pressed
onFinish(mainContainer, animation)
end
):start(button.animationDuration)
end
local function buttonPress(button, mainContainer, object, ...)
if button.animated then
local eventData = {...}
buttonPlayAnimation(button, function(mainContainer, animation)
if button.onTouch then
button.onTouch(mainContainer, button, table.unpack(eventData))
end
animation:delete()
if not button.switchMode then
buttonPlayAnimation(button, function(mainContainer, animation)
animation:delete()
end)
end
end)
else
button.pressed = not button.pressed
mainContainer:drawOnScreen()
if not button.switchMode then
button.pressed = not button.pressed
os.sleep(0.2)
mainContainer:drawOnScreen()
end
if button.onTouch then
button.onTouch(mainContainer, button, ...)
end
end
end
local function buttonEventHandler(mainContainer, button, e1, ...)
if e1 == "touch" and (not button.animated or not button.animationStarted) then
button:press(mainContainer, 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)
buffer.text(math.floor(button.x + button.width / 2 - unicode.len(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
buffer.square(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
buffer.frame(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
buffer.text(button.x + 1, button.y, backgroundColor, string.rep("", button.width - 2))
buffer.text(button.x, button.y, backgroundColor, "")
buffer.text(x2, button.y, backgroundColor, "")
buffer.square(button.x, button.y + 1, button.width, button.height - 2, backgroundColor, textColor, " ")
buffer.text(button.x + 1, y2, backgroundColor, string.rep("", button.width - 2))
buffer.text(button.x, y2, backgroundColor, "")
buffer.text(x2, y2, backgroundColor, "")
else
buffer.square(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ")
GUI.roundedCorners(button.x, button.y, button.width, button.height, backgroundColor)
end
end
buttonDrawText(button, textColor)
end
local function buttonCreate(x, y, width, height, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text)
local button = GUI.object(x, y, width, height)
button.colors = {
default = {
background = backgroundColor,
text = textColor
},
pressed = {
background = backgroundPressedColor,
text = textPressedColor
},
disabled = {
background = GUI.colors.disabled.background,
text = GUI.colors.disabled.text
}
}
button.animationCurrentBackground = backgroundColor
button.animationCurrentText = textColor
button.text = text
button.animationDuration = 0.2
button.animated = true
button.pressed = false
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.len(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
-----------------------------------------------------------------------
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
-----------------------------------------------------------------------
local function drawLabel(object)
local xText, yText = GUI.getAlignmentCoordinates(object, {width = unicode.len(object.text), height = 1})
buffer.text(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)
buffer.image(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 = fatSymbol and "" or ""
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 menuDraw(menu)
buffer.square(menu.x, menu.y, menu.width, 1, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency)
menu:reimplementedDraw()
end
local function menuItemEventHandler(mainContainer, object, e1, ...)
if e1 == "touch" then
if object.onTouch then
object.pressed = true
mainContainer:drawOnScreen()
object.onTouch(e1, ...)
object.pressed = false
mainContainer:drawOnScreen()
end
end
end
local function menuAddItem(menu, text, textColor)
local x = 2; for i = 1, #menu.children do x = x + unicode.len(menu.children[i].text) + 2; end
local item = menu:addChild(GUI.adaptiveButton(x, 1, 1, 0, nil, textColor or menu.colors.default.text, menu.colors.pressed.background, menu.colors.pressed.text, text))
item.animated = false
item.eventHandler = menuItemEventHandler
return item
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,
},
pressed = {
background = backgroundPressedColor,
text = textPressedColor,
},
transparency = backgroundTransparency
}
menu.addItem = menuAddItem
menu.reimplementedDraw = menu.draw
menu.draw = menuDraw
return menu
end
-----------------------------------------------------------------------
local function drawProgressBar(object)
local activeWidth = math.floor(math.min(object.value, 100) / 100 * object.width)
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, 0x0, " ")
buffer.square(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 "")
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
-----------------------------------------------------------------------
function GUI.windowShadow(x, y, width, height, transparency, thin)
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
function GUI.roundedCorners(x, y, width, height, color, transparency)
buffer.text(x - 1, y, color, "", transparency)
buffer.text(x + width, y, color, "", transparency)
end
-----------------------------------------------------------------------
function GUI.error(...)
local args = {...}
for i = 1, #args do
if type(args[i]) == "table" then
args[i] = table.toString(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 = buffer.getResolution()
local width = math.floor(bufferWidth * 0.5)
local textWidth = width - image.getWidth(sign) - 2
lines = string.wrap(lines, textWidth)
local height = image.getHeight(sign)
if #lines + 2 > height then
height = #lines + 2
end
local mainContainer = GUI.container(1, math.floor(bufferHeight / 2 - height / 2), bufferWidth, height + offset * 2)
local oldPixels = buffer.copy(mainContainer.x, mainContainer.y, mainContainer.width, mainContainer.height)
local x, y = math.floor(bufferWidth / 2 - width / 2), offset + 1
mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x1D1D1D))
mainContainer:addChild(GUI.image(x, y, sign))
mainContainer:addChild(GUI.textBox(x + image.getWidth(sign) + 2, y, textWidth, #lines, 0x1D1D1D, 0xE1E1E1, lines, 1, 0, 0)).eventHandler = nil
local buttonWidth = 10
local button = mainContainer:addChild(GUI.roundedButton(x + image.getWidth(sign) + textWidth - buttonWidth + 2, mainContainer.height - offset, buttonWidth, 1, 0x3366CC, 0xE1E1E1, 0xE1E1E1, 0x3366CC, "OK"))
button.onTouch = function()
mainContainer:stopEventHandling()
buffer.paste(mainContainer.x, mainContainer.y, oldPixels)
buffer.draw()
end
mainContainer.eventHandler = function(mainContainer, object, e1, e2, e3, e4, ...)
if e1 == "key_down" and e4 == 28 then
button.animated = false
button:press(mainContainer, object, e1, e2, e3, e4, ...)
end
end
mainContainer:drawOnScreen(true)
mainContainer:startEventHandling()
end
-----------------------------------------------------------------------
local function codeViewDraw(codeView)
local toLine, colorScheme = codeView.fromLine + codeView.height - 1, GUI.colors.syntaxHighlighting
-- Line numbers bar and code area
codeView.lineNumbersWidth = unicode.len(tostring(toLine)) + 2
codeView.codeAreaPosition = codeView.x + codeView.lineNumbersWidth
codeView.codeAreaWidth = codeView.width - codeView.lineNumbersWidth
-- Line numbers
buffer.square(codeView.x, codeView.y, codeView.lineNumbersWidth, codeView.height, colorScheme.lineNumbersBackground, colorScheme.lineNumbersText, " ")
-- Background
buffer.square(codeView.codeAreaPosition, codeView.y, codeView.codeAreaWidth, codeView.height, colorScheme.background, colorScheme.text, " ")
-- Line numbers texts
local y = codeView.y
for line = codeView.fromLine, toLine do
if codeView.lines[line] then
local text = tostring(line)
if codeView.highlights[line] then
buffer.square(codeView.x, y, codeView.lineNumbersWidth, 1, codeView.highlights[line], colorScheme.text, " ", 0.3)
buffer.square(codeView.codeAreaPosition, y, codeView.codeAreaWidth, 1, codeView.highlights[line], colorScheme.text, " ")
end
buffer.text(codeView.codeAreaPosition - unicode.len(text) - 1, y, colorScheme.lineNumbersText, text)
y = y + 1
else
break
end
end
local function drawUpperSelection(y, selectionIndex)
buffer.square(
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)
buffer.square(
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
local oldDrawLimitX1, oldDrawLimitY1, oldDrawLimitX2, oldDrawLimitY2 = buffer.getDrawLimit()
buffer.setDrawLimit(codeView.codeAreaPosition, codeView.y, codeView.codeAreaPosition + codeView.codeAreaWidth - 1, codeView.y + codeView.height - 1)
if #codeView.selections > 0 then
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
buffer.square(
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
buffer.square(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
buffer.setDrawLimit(codeView.codeAreaPosition + 1, y, codeView.codeAreaPosition + codeView.codeAreaWidth - 2, y + codeView.height - 1)
for i = codeView.fromLine, toLine do
if codeView.lines[i] then
if codeView.highlightLuaSyntax then
GUI.highlightString(codeView.codeAreaPosition + 1,
y,
codeView.fromSymbol,
codeView.codeAreaWidth - 2,
codeView.indentationWidth,
colorScheme,
GUI.luaSyntaxPatterns,
codeView.lines[i]
)
else
buffer.text(codeView.codeAreaPosition - codeView.fromSymbol + 2, y, colorScheme.text, codeView.lines[i])
end
y = y + 1
else
break
end
end
buffer.setDrawLimit(oldDrawLimitX1, oldDrawLimitY1, oldDrawLimitX2, oldDrawLimitY2)
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:draw()
codeView.horizontalScrollBar.hidden = false
else
codeView.horizontalScrollBar.hidden = true
end
codeView:reimplementedDraw()
end
function GUI.codeView(x, y, width, height, lines, fromSymbol, fromLine, maximumLineLength, selections, highlights, highlightLuaSyntax, indentationWidth)
local codeView = GUI.container(x, y, width, height)
codeView.lines = lines
codeView.fromSymbol = fromSymbol
codeView.fromLine = fromLine
codeView.maximumLineLength = maximumLineLength
codeView.selections = selections or {}
codeView.highlights = highlights or {}
codeView.highlightLuaSyntax = highlightLuaSyntax
codeView.indentationWidth = indentationWidth
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.reimplementedDraw = codeView.draw
codeView.draw = codeViewDraw
return codeView
end
-----------------------------------------------------------------------
local function colorSelectorDraw(colorSelector)
local overlayColor = colorSelector.color < 0x7FFFFF and 0xFFFFFF or 0x000000
buffer.square(
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
buffer.text(colorSelector.x, colorSelector.y + colorSelector.height - 1, overlayColor, string.rep("", colorSelector.width), 0.8)
end
buffer.text(colorSelector.x + 1, colorSelector.y + math.floor(colorSelector.height / 2), overlayColor, string.limit(colorSelector.text, colorSelector.width - 2))
return colorSelector
end
local function colorSelectorEventHandler(mainContainer, object, e1, ...)
if e1 == "touch" then
local eventData = {...}
object.pressed = true
local palette = GUI.addPaletteWindowToContainer(mainContainer, object.color)
palette.onCancel = function()
object.pressed = false
palette:delete()
mainContainer:drawOnScreen()
if object.onTouch then
object.onTouch(mainContainer, object, e1, table.unpack(eventData))
end
end
palette.onSubmit = function()
object.color = palette.color.integer
palette.onCancel()
end
mainContainer:drawOnScreen()
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(number, postfix, roundValues)
if roundValues then
return math.floor(number) .. postfix
else
local integer, fractional = math.modf(number)
local firstPart, secondPart = "", ""
if math.abs(integer) >= 1000 then
return math.shorten(integer, 2) .. postfix
else
if math.abs(fractional) > 0 then
return string.format("%.2f", number) .. postfix
else
return number .. 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.len(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.len(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
buffer.text(chartX - unicode.len(yAxisValues[i].value) - 2, yAxisValues[i].y, object.colors.axisValue, yAxisValues[i].value)
end
buffer.text(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)
buffer.text(math.floor(x - unicode.len(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)
buffer.text(object.x + object.width - unicode.len(value), object.y + object.height - 1, object.colors.axisValue, value)
end
-- Axis lines
for y = object.y, object.y + chartHeight - 1 do
buffer.text(chartX - 1, y, object.colors.axis, "")
end
buffer.text(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)
buffer.semiPixelSquare(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)
buffer.semiPixelSquare(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
buffer.semiPixelLine(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 dropDownMenuItemDraw(item)
local yText = item.y + math.floor(item.height / 2)
if item.type == GUI.dropDownMenuItemTypes.default then
local textColor = item.color or item.parent.parent.colors.default.text
if item.pressed then
textColor = item.parent.parent.colors.pressed.text
buffer.square(item.x, item.y, item.width, item.height, item.parent.parent.colors.pressed.background, textColor, " ")
elseif item.disabled then
textColor = item.parent.parent.colors.disabled.text
end
buffer.text(item.x + 1, yText, textColor, item.text)
if item.shortcut then
buffer.text(item.x + item.width - unicode.len(item.shortcut) - 1, yText, textColor, item.shortcut)
end
else
buffer.text(item.x, yText, item.parent.parent.colors.separator, string.rep("", item.width))
end
return item
end
local function dropDownMenuItemEventHandler(mainContainer, object, e1)
if e1 == "touch" then
if object.type == GUI.dropDownMenuItemTypes.default then
object.pressed = true
mainContainer:drawOnScreen()
if object.subMenu then
object.subMenu.y = object.parent.y + object.localY - 1
object.subMenu.x = object.parent.x + object.parent.width
if buffer.getWidth() - object.parent.x - object.parent.width + 1 < object.subMenu.width then
object.subMenu.x = object.parent.x - object.subMenu.width
end
object.subMenu:show()
else
os.sleep(0.2)
end
object.pressed = false
mainContainer.selectedItem = object:indexOf()
end
mainContainer:stopEventHandling()
end
end
local function dropDownMenuCalculateSizes(menu)
if #menu.itemsContainer.children > 0 then
local y, totalHeight = menu.itemsContainer.children[1].localY or 1, 0
for i = 1, #menu.itemsContainer.children do
menu.itemsContainer.children[i].width = menu.width
menu.itemsContainer.children[i].localY = y
y = y + menu.itemsContainer.children[i].height
totalHeight = totalHeight + (menu.itemsContainer.children[i].type == GUI.dropDownMenuItemTypes.separator and 1 or menu.itemHeight)
end
menu.height = math.min(totalHeight, menu.maximumHeight, buffer.getHeight() - menu.y)
menu.itemsContainer.width, menu.itemsContainer.height = menu.width, menu.height
menu.nextButton.localY = menu.height
menu.prevButton.width, menu.nextButton.width = menu.width, menu.width
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
end
local function dropDownMenuRemoveItem(menu, index)
table.remove(menu.itemsContainer.children, index)
dropDownMenuCalculateSizes(menu)
return menu
end
local function dropDownMenuAddItem(menu, text, disabled, shortcut, color)
local item = menu.itemsContainer:addChild(GUI.object(1, 1, 1, menu.itemHeight))
item.type = GUI.dropDownMenuItemTypes.default
item.text = text
item.disabled = disabled
item.shortcut = shortcut
item.color = color
item.draw = dropDownMenuItemDraw
item.eventHandler = dropDownMenuItemEventHandler
dropDownMenuCalculateSizes(menu)
return item
end
local function dropDownMenuAddSeparator(menu)
local item = dropDownMenuAddItem(menu)
item.type = GUI.dropDownMenuItemTypes.separator
item.height = 1
return item
end
local function dropDownMenuScrollDown(menu)
if menu.itemsContainer.children[1].localY < 1 then
for i = 1, #menu.itemsContainer.children do
menu.itemsContainer.children[i].localY = menu.itemsContainer.children[i].localY + 1
end
end
menu:draw()
buffer.draw()
end
local function dropDownMenuScrollUp(menu)
if menu.itemsContainer.children[#menu.itemsContainer.children].localY + menu.itemsContainer.children[#menu.itemsContainer.children].height - 1 > menu.height then
for i = 1, #menu.itemsContainer.children do
menu.itemsContainer.children[i].localY = menu.itemsContainer.children[i].localY - 1
end
end
menu:draw()
buffer.draw()
end
local function dropDownMenuEventHandler(mainContainer, object, e1, e2, e3, e4, e5)
if e1 == "scroll" then
if e5 == 1 then
dropDownMenuScrollDown(object)
else
dropDownMenuScrollUp(object)
end
end
end
local function dropDownMenuDraw(menu)
dropDownMenuCalculateSizes(menu)
if menu.oldPixels then
buffer.paste(menu.x, menu.y, menu.oldPixels)
else
menu.oldPixels = buffer.copy(menu.x, menu.y, menu.width + 1, menu.height + 1)
end
buffer.square(menu.x, menu.y, menu.width, menu.height, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency.background)
GUI.drawContainerContent(menu)
GUI.windowShadow(menu.x, menu.y, menu.width, menu.height, menu.colors.transparency.shadow, true)
return menu
end
local function dropDownMenuShow(menu)
local mainContainer = GUI.fullScreenContainer()
-- Удаляем олдпиксельсы, чтоб старое дерьмое не рисовалось во всяких комбобоксах
menu.oldPixels = nil
mainContainer:addChild(GUI.object(1, 1, mainContainer.width, mainContainer.height)).eventHandler = function(mainContainer, object, e1)
if e1 == "touch" then
buffer.paste(menu.x, menu.y, menu.oldPixels)
buffer.draw()
mainContainer:stopEventHandling()
end
end
mainContainer:addChild(menu)
mainContainer:drawOnScreen()
mainContainer:startEventHandling()
if mainContainer.selectedItem then
local item = menu.itemsContainer.children[mainContainer.selectedItem]
if not item.subMenu then
buffer.paste(menu.x, menu.y, menu.oldPixels)
buffer.draw()
end
menu.oldPixels = nil
if item.onTouch then
item.onTouch()
end
return item, mainContainer.selectedItem
end
end
function GUI.dropDownMenu(x, y, width, maximumHeight, itemHeight, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, backgroundTransparency, shadowTransparency)
local menu = GUI.container(x, y, width, 1)
menu.colors = {
default = {
background = backgroundColor,
text = textColor
},
pressed = {
background = backgroundPressedColor,
text = textPressedColor
},
disabled = {
text = disabledColor
},
separator = separatorColor,
transparency = {
background = backgroundTransparency,
shadow = shadowTransparency
}
}
menu.itemsContainer = menu:addChild(GUI.container(1, 1, menu.width, menu.height))
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 = function()
dropDownMenuScrollDown(menu)
end
menu.nextButton.onTouch = function()
dropDownMenuScrollUp(menu)
end
menu.itemHeight = itemHeight
menu.addSeparator = dropDownMenuAddSeparator
menu.addItem = dropDownMenuAddItem
menu.removeItem = dropDownMenuRemoveItem
menu.draw = dropDownMenuDraw
menu.show = dropDownMenuShow
menu.maximumHeight = maximumHeight
menu.eventHandler = dropDownMenuEventHandler
return menu
end
-----------------------------------------------------------------------
local function contextMenuCalculate(menu)
local widestItem, widestShortcut = 0, 0
for i = 1, #menu.itemsContainer.children do
if menu.itemsContainer.children[i].type == GUI.dropDownMenuItemTypes.default then
widestItem = math.max(widestItem, unicode.len(menu.itemsContainer.children[i].text))
if menu.itemsContainer.children[i].shortcut then
widestShortcut = math.max(widestShortcut, unicode.len(menu.itemsContainer.children[i].shortcut))
end
end
end
menu.width = 2 + widestItem + (widestShortcut > 0 and 3 + widestShortcut or 0)
menu.height = #menu.itemsContainer.children
end
local function contextMenuShow(menu)
contextMenuCalculate(menu)
local bufferWidth, bufferHeight = buffer.getResolution()
if menu.y + menu.height >= bufferHeight then
menu.y = bufferHeight - menu.height
end
if menu.x + menu.width + 1 >= bufferWidth then
menu.x = bufferWidth - menu.width - 1
end
return dropDownMenuShow(menu)
end
local function contextMenuAddItem(menu, ...)
local item = dropDownMenuAddItem(menu, ...)
contextMenuCalculate(menu)
return item
end
local function contextMenuAddSeparator(menu, ...)
local item = dropDownMenuAddSeparator(menu, ...)
contextMenuCalculate(menu)
return item
end
local function contextMenuAddSubMenu(menu, text, disabled)
local item = menu:addItem(text, disabled, "")
item.subMenu = GUI.contextMenu(1, 1)
item.subMenu.colors = menu.colors
return item.subMenu
end
function GUI.contextMenu(x, y, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, backgroundTransparency, shadowTransparency)
local menu = GUI.dropDownMenu(x, y, 1, math.ceil(buffer.getHeight() * 0.5), 1,
backgroundColor or GUI.colors.contextMenu.default.background,
textColor or GUI.colors.contextMenu.default.text,
backgroundPressedColor or GUI.colors.contextMenu.pressed.background,
textPressedColor or GUI.colors.contextMenu.pressed.text,
disabledColor or GUI.colors.contextMenu.disabled,
separatorColor or GUI.colors.contextMenu.separator,
backgroundTransparency or GUI.colors.contextMenu.transparency.background,
shadowTransparency or GUI.colors.contextMenu.transparency.shadow
)
menu.colors.transparency.background = menu.colors.transparency.background or GUI.colors.contextMenu.transparency.background
menu.colors.transparency.shadow = menu.colors.transparency.shadow or GUI.colors.contextMenu.transparency.shadow
menu.show = contextMenuShow
menu.addSubMenu = contextMenuAddSubMenu
menu.addItem = contextMenuAddItem
menu.addSeparator = contextMenuAddSeparator
return menu
end
-----------------------------------------------------------------------
local function drawComboBox(object)
buffer.square(object.x, object.y, object.width, object.height, object.colors.default.background, object.colors.default.text, " ")
if object.dropDownMenu.itemsContainer.children[object.selectedItem] then
buffer.text(object.x + 1, math.floor(object.y + object.height / 2), object.colors.default.text, string.limit(object.dropDownMenu.itemsContainer.children[object.selectedItem].text, object.width - object.height - 2, "right"))
end
GUI.button(object.x + object.width - object.height * 2 + 1, object.y, object.height * 2 - 1, object.height, object.colors.arrow.background, object.colors.arrow.text, 0x0, 0x0, object.pressed and "" or ""):draw()
return object
end
local function comboBoxGetItem(object, index)
return object.dropDownMenu.itemsContainer.children[index]
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:deleteChildren()
object.selectedItem = 1
return object
end
local function comboBoxIndexOfItem(object, text)
for i = 1, #object.dropDownMenu.itemsContainer.children do
if object.dropDownMenu.itemsContainer.children[i].text == text then
return i
end
end
end
local function comboBoxSelect(object)
object.pressed = true
object:draw()
object.dropDownMenu.x, object.dropDownMenu.y = object.x, object.y + object.height
object.dropDownMenu.width = object.width
local _, selectedItem = object.dropDownMenu:show()
object.selectedItem = selectedItem or object.selectedItem
object.pressed = false
object:draw()
buffer.draw()
return object
end
local function comboBoxEventHandler(mainContainer, object, e1, ...)
if e1 == "touch" and #object.dropDownMenu.itemsContainer.children > 0 then
object:select()
if object.onItemSelected then
object.onItemSelected(mainContainer, object, e1, ...)
end
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, itemSize, backgroundColor, textColor, arrowBackgroundColor, arrowTextColor)
local object = GUI.object(x, y, width, itemSize)
object.eventHandler = comboBoxEventHandler
object.colors = {
default = {
background = backgroundColor,
text = textColor
},
pressed = {
background = GUI.colors.contextMenu.pressed.background,
text = GUI.colors.contextMenu.pressed.text
},
arrow = {
background = arrowBackgroundColor,
text = arrowTextColor
}
}
object.dropDownMenu = GUI.dropDownMenu(1, 1, 1, math.ceil(buffer.getHeight() * 0.5), itemSize,
object.colors.default.background,
object.colors.default.text,
object.colors.pressed.background,
object.colors.pressed.text,
GUI.colors.contextMenu.disabled,
GUI.colors.contextMenu.separator,
GUI.colors.contextMenu.transparency.background,
GUI.colors.contextMenu.transparency.shadow
)
object.selectedItem = 1
object.addItem = comboBoxAddItem
object.removeItem = comboBoxRemoveItem
object.addSeparator = comboBoxAddSeparator
object.draw = drawComboBox
object.select = comboBoxSelect
object.clear = comboBoxClear
object.indexOfItem = comboBoxIndexOfItem
object.getItem = comboBoxGetItem
object.count = comboBoxCount
return object
end
-----------------------------------------------------------------------
local function switchAndLabelDraw(switchAndLabel)
switchAndLabel.label.width = switchAndLabel.width
switchAndLabel.switch.localX = switchAndLabel.width - switchAndLabel.switch.width
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 math.roundToDecimalPlaces(object.maximumValue, 2)), tostring(object.roundValues and math.floor(object.minimumValue) 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.round((object.value - object.minimumValue) / (object.maximumValue - object.minimumValue) * object.width)
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.text(activeWidth >= object.width and object.x + activeWidth - 1 or object.x + activeWidth, object.y, object.colors.pipe, "")
return object
end
local function sliderEventHandler(mainContainer, object, e1, e2, e3, ...)
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
mainContainer:drawOnScreen()
if object.onValueChanged then
object.onValueChanged(mainContainer, object, e1, e2, e3, ...)
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.showMaximumAndMinimumValues = showMaximumAndMinimumValues
object.currentValuePrefix = currentValuePrefix
object.currentValuePostfix = currentValuePostfix
object.roundValues = false
return object
end
-----------------------------------------------------------------------
local function switchDraw(switch)
buffer.text(switch.x - 1, switch.y, switch.colors.passive, "")
buffer.square(switch.x, switch.y, switch.width, 1, switch.colors.passive, 0x000000, " ")
buffer.text(switch.x + switch.width, switch.y, switch.colors.passive, "")
buffer.text(switch.x - 1, switch.y, switch.colors.active, "")
buffer.square(switch.x, switch.y, switch.pipePosition - 1, 1, switch.colors.active, 0x000000, " ")
buffer.text(switch.x + switch.pipePosition - 2, switch.y, switch.colors.pipe, "")
buffer.square(switch.x + switch.pipePosition - 1, switch.y, 2, 1, switch.colors.pipe, 0x000000, " ")
buffer.text(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 - 1 or 1
return switch
end
local function switchEventHandler(mainContainer, switch, e1, ...)
if e1 == "touch" then
local eventData = {...}
switch.state = not switch.state
switch:addAnimation(
function(mainContainer, animation)
if switch.state then
switch.pipePosition = math.round(1 + animation.position * (switch.width - 2))
else
switch.pipePosition = math.round(1 + (1 - animation.position) * (switch.width - 2))
end
end,
function(mainContainer, animation)
animation:delete()
if switch.onStateChanged then
switch.onStateChanged(mainContainer, 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.update = switchUpdate
switch.animated = true
switch.animationDuration = 0.3
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.sizePolicies.absolute then
absoluteTotalSize = absoluteTotalSize + array[i].size
end
end
return absoluteTotalSize
end
local function layoutGetCalculatedSize(array, index, dependency)
if array[index].sizePolicy == GUI.sizePolicies.percentage 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].width, layout.cells[row][column].height = 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.fitting.horizontal then
child.width = math.round(layout.columnSizes[layoutColumn].calculatedSize - cell.fitting.horizontalRemove)
end
if cell.fitting.vertical then
child.height = math.round(layout.rowSizes[layoutRow].calculatedSize - cell.fitting.verticalRemove)
end
-- Направление и расчет размеров
if cell.direction == GUI.directions.horizontal then
cell.width = cell.width + child.width + cell.spacing
cell.height = math.max(cell.height, child.height)
else
cell.width = math.max(cell.width, child.width)
cell.height = cell.height + 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 = x,
y = y,
width = layout.columnSizes[column].calculatedSize,
height = layout.rowSizes[row].calculatedSize,
alignment = cell.alignment,
},
{
width = cell.width - (cell.direction == GUI.directions.horizontal and cell.spacing or 0),
height = cell.height - (cell.direction == GUI.directions.vertical and cell.spacing or 0),
}
)
-- Учитываем отступы от краев ячейки
if cell.margin then
cell.x, cell.y = GUI.getMarginCoordinates(cell)
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, child)
if cell.direction == GUI.directions.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 layoutSetCellPosition(layout, column, row, object)
layoutCheckCell(layout, column, row)
object.layoutRow = row
object.layoutColumn = column
return object
end
local function layoutSetCellDirection(layout, column, row, direction)
layoutCheckCell(layout, column, row)
layout.cells[row][column].direction = direction
return layout
end
local function layoutSetCellSpacing(layout, column, row, spacing)
layoutCheckCell(layout, column, row)
layout.cells[row][column].spacing = spacing
return layout
end
local function layoutSetCellAlignment(layout, column, row, horizontalAlignment, verticalAlignment)
layoutCheckCell(layout, column, row)
layout.cells[row][column].alignment.horizontal, layout.cells[row][column].alignment.vertical = horizontalAlignment, verticalAlignment
return layout
end
local function layoutSetCellMargin(layout, column, row, horizontalMargin, verticalMargin)
layoutCheckCell(layout, column, row)
layout.cells[row][column].margin = {
horizontal = horizontalMargin,
vertical = verticalMargin
}
return layout
end
local function layoutNewCell()
return {
alignment = {
horizontal = GUI.alignment.horizontal.center,
vertical = GUI.alignment.vertical.center
},
direction = GUI.directions.vertical,
fitting = {
horizontal = false, vertical = false},
spacing = 1,
}
end
local function layoutCalculatePercentageSize(changingExistent, array, index)
if array[index].sizePolicy == GUI.sizePolicies.percentage then
local allPercents, beforeFromIndexPercents = 0, 0
for i = 1, #array do
if array[i].sizePolicy == GUI.sizePolicies.percentage 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.sizePolicies.percentage 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)
-- GUI.error(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)
-- GUI.error(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.sizePolicies.percentage, 1 / i)
end
for i = 1, columnCount do
layoutAddColumn(layout, GUI.sizePolicies.percentage, 1 / i)
end
return layout
end
local function layoutDraw(layout)
layoutUpdate(layout)
GUI.drawContainerContent(layout)
if layout.showGrid then
local x, y = layout.x, layout.y
for j = 1, #layout.columnSizes do
for i = 1, #layout.rowSizes do
buffer.frame(
math.round(x),
math.round(y),
math.round(layout.columnSizes[j].calculatedSize),
math.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.directions.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.directions.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 layoutSetCellFitting(layout, column, row, horizontal, vertical, horizontalRemove, verticalRemove )
layoutCheckCell(layout, column, row)
layout.cells[row][column].fitting = {
horizontal = horizontal,
vertical = vertical,
horizontalRemove = horizontalRemove or 0,
verticalRemove = verticalRemove or 0,
}
return layout
end
local function layoutAddChild(layout, object, ...)
object.layoutRow = layout.defaultRow
object.layoutColumn = layout.defaultColumn
GUI.addChildToContainer(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.setCellPosition = layoutSetCellPosition
layout.setCellDirection = layoutSetCellDirection
layout.setGridSize = layoutSetGridSize
layout.setCellSpacing = layoutSetCellSpacing
layout.setCellAlignment = layoutSetCellAlignment
layout.setCellMargin = layoutSetCellMargin
layout.fitToChildrenSize = layoutFitToChildrenSize
layout.setCellFitting = layoutSetCellFitting
layout.update = layoutUpdate
layout.addChild = layoutAddChild
layout.draw = layoutDraw
layoutSetGridSize(layout, 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.filesystemModes.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
GUI.drawContainerContent(filesystemDialog)
GUI.windowShadow(filesystemDialog.x, filesystemDialog.y, filesystemDialog.width, filesystemDialog.height, GUI.colors.contextMenu.transparency.shadow, true)
return filesystemDialog
end
local function filesystemDialogSetMode(filesystemDialog, IOMode, filesystemMode)
filesystemDialog.IOMode = IOMode
filesystemDialog.filesystemMode = filesystemMode
if filesystemDialog.IOMode == GUI.filesystemModes.save then
filesystemDialog.filesystemTree.showMode = GUI.filesystemModes.directory
filesystemDialog.filesystemTree.selectionMode = GUI.filesystemModes.directory
filesystemDialog.input.disabled = false
filesystemDialog.extensionComboBox.hidden = filesystemDialog.filesystemMode ~= GUI.filesystemModes.file or not filesystemDialog.filesystemTree.extensionFilters
else
if filesystemDialog.filesystemMode == GUI.filesystemModes.file then
filesystemDialog.filesystemTree.showMode = GUI.filesystemModes.both
filesystemDialog.filesystemTree.selectionMode = GUI.filesystemModes.file
else
filesystemDialog.filesystemTree.showMode = GUI.filesystemModes.directory
filesystemDialog.filesystemTree.selectionMode = GUI.filesystemModes.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.len(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.draw = filesystemDialogDraw
filesystemDialog.setMode = filesystemDialogSetMode
filesystemDialog.addExtensionFilter = filesystemDialogAddExtensionFilter
filesystemDialog.expandPath = filesystemDialogExpandPath
filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file)
return filesystemDialog
end
local function filesystemDialogShow(filesystemDialog)
filesystemDialog.filesystemTree:updateFileList()
filesystemDialog:addAnimation(
function(mainContainer, animation)
filesystemDialog.localY = math.floor(1 + (1.0 - animation.position) * (-filesystemDialog.height))
end,
function(mainContainer, animation)
animation:delete()
end
):start(0.5)
return filesystemDialog
end
-----------------------------------------------------------------------
function GUI.addFilesystemDialogToContainer(parentContainer, width, height, addPanel, ...)
local container = GUI.addFadeContainer(parentContainer, addPanel, false, nil)
local filesystemDialog = container:addChild(GUI.filesystemDialog(1, 1, width, height, ...))
filesystemDialog.localX = math.floor(container.width / 2 - filesystemDialog.width / 2)
filesystemDialog.localY = -filesystemDialog.height
local function onAnyTouch()
local firstParent = filesystemDialog:getFirstParent()
container:delete()
firstParent:drawOnScreen()
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.filesystemModes.save then
path = path .. filesystemDialog.input.text
if filesystemDialog.filesystemMode == GUI.filesystemModes.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)
buffer.square(object.x, object.y, object.width - tipWidth, object.height, object.colors.background, object.colors.text, " ")
buffer.square(object.x + object.width - tipWidth, object.y, tipWidth, object.height, object.pressed and object.colors.tipText or object.colors.tipBackground, object.pressed and object.colors.tipBackground or object.colors.tipText, " ")
buffer.text(object.x + object.width - math.floor(tipWidth / 2) - 1, y, object.pressed and object.colors.tipBackground or object.colors.tipText, "")
buffer.text(object.x + 1, y, object.colors.text, string.limit(object.path or object.placeholderText, object.width - tipWidth - 2, "left"))
return filesystemChooser
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(mainContainer, object, e1)
if e1 == "touch" then
object.pressed = true
mainContainer:drawOnScreen()
local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), false, 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.filesystemModes.open and object.path or fs.path(object.path)
filesystemDialog.input.text = fs.name(object.path)
filesystemDialog:expandPath(object.IOMode == GUI.filesystemModes.open and fs.path(object.path) or fs.path(fs.path(object.path)))
end
filesystemDialog.onCancel = function()
object.pressed = false
mainContainer:drawOnScreen()
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.eventHandler = comboBoxEventHandler
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.filesystemModes.file
object.IOMode = GUI.filesystemModes.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
buffer.text(object.x, math.floor(object.y + object.height / 2), object.colors.helper, string.rep("", object.width))
if object.lastTouchX then
buffer.text(object.lastTouchX, object.lastTouchY, object.colors.arrow, "")
end
else
local x = math.floor(object.x + object.width / 2)
local bufferWidth, bufferHeight, index = buffer.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 = buffer.getIndex(x, i)
buffer.rawSet(index, buffer.rawGet(index), object.colors.helper, "")
end
end
if object.lastTouchX then
buffer.text(object.lastTouchX - 1, object.lastTouchY, object.colors.arrow, "←→")
end
end
end
local function resizerEventHandler(mainContainer, object, e1, e2, e3, e4)
if e1 == "touch" then
object.lastTouchX, object.lastTouchY = e3, e4
mainContainer:drawOnScreen()
elseif e1 == "drag" and object.lastTouchX then
if object.onResize then
object.onResize(mainContainer, object, e3 - object.lastTouchX, e4 - object.lastTouchY)
end
object.lastTouchX, object.lastTouchY = e3, e4
mainContainer:drawOnScreen()
elseif e1 == "drop" then
if object.onResizeFinished then
object.onResizeFinished(mainContainer, object)
end
object.lastTouchX, object.lastTouchY = nil, nil
mainContainer:drawOnScreen()
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 = buffer.get(scrollBar.x, y)
buffer.set(scrollBar.x, y, background, y >= y1 and y <= y2 and scrollBar.colors.foreground or scrollBar.colors.background, "")
end
else
buffer.square(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ")
buffer.square(
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 = buffer.get(x, scrollBar.y)
buffer.set(x, scrollBar.y, background, x >= x1 and x <= x2 and scrollBar.colors.foreground or scrollBar.colors.background, "")
end
else
buffer.square(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ")
buffer.square(
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(mainContainer, object, e1, e2, e3, e4, e5, ...)
local newValue = object.value
if e1 == "touch" or e1 == "drag" then
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(mainContainer, object, e1, e2, e3, e4, e5, ...)
end
mainContainer:drawOnScreen()
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
buffer.square(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
buffer.square(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
buffer.text(tree.x + tree.items[i].offset, y, arrowColor, tree.expandedItems[tree.items[i].definition] and "" or "")
end
buffer.text(tree.x + tree.items[i].offset + 2, y, textColor, unicode.sub(text .. tree.items[i].name, 1, textLimit - tree.items[i].offset - 2))
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(mainContainer, tree, e1, e2, e3, e4, e5, ...)
if e1 == "touch" then
local i = e4 - tree.y + tree.fromItem
if tree.items[i] then
if
tree.items[i].expandable and
(
tree.selectionMode == GUI.filesystemModes.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.filesystemModes.both or
tree.selectionMode == GUI.filesystemModes.directory and tree.items[i].expandable or
tree.selectionMode == GUI.filesystemModes.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
mainContainer:drawOnScreen()
end
elseif e1 == "scroll" then
if e5 == 1 then
if tree.fromItem > 1 then
tree.fromItem = tree.fromItem - 1
mainContainer:drawOnScreen()
end
else
if tree.fromItem < #tree.items then
tree.fromItem = tree.fromItem + 1
mainContainer:drawOnScreen()
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 = {}
for file in fs.list(path) do
table.insert(list, file)
end
local i, expandables = 1, {}
while i <= #list do
if fs.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.filesystemModes.both or tree.showMode == GUI.filesystemModes.directory then
for i = 1, #expandables do
tree:addItem(fs.name(expandables[i]), 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.filesystemModes.both or tree.showMode == GUI.filesystemModes.file then
for i = 1, #list do
tree:addItem(list[i], path .. list[i], offset, false, tree.extensionFilters and not tree.extensionFilters[fs.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 textBoxCalculate(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 = string.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)
textBoxCalculate(object)
if object.colors.background then
buffer.square(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, text, 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
text, textColor = string.limit(object.linesCopy[i], object.textWidth), object.colors.text
elseif lineType == "table" then
text, textColor = string.limit(object.linesCopy[i].text, object.textWidth), object.linesCopy[i].color
else
error("Unknown TextBox line type: " .. tostring(lineType))
end
x = GUI.getAlignmentCoordinates(
{
x = object.x + object.offset.horizontal,
y = 1,
width = object.textWidth,
height = 1,
alignment = object.alignment
},
{
width = unicode.len(text),
height = 1
}
)
buffer.text(math.floor(x), y, textColor, text)
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(mainContainer, object, e1, e2, e3, e4, e5)
if e1 == "scroll" then
if e5 == 1 then
object:scrollUp()
else
object:scrollDown()
end
mainContainer:drawOnScreen()
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.eventHandler = textBoxScrollEventHandler
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 = textBoxDraw
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
textBoxCalculate(object)
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)
buffer.text(x, y, color, text)
end
local function inputDraw(input)
local background, foreground, transparency, text
if input.focused 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
buffer.square(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 = buffer.getIndex(input.x + input.cursorPosition - input.textCutFrom + input.textOffset, y)
local background = buffer.rawGet(index)
buffer.rawSet(index, background, input.colors.cursor, input.cursorSymbol)
end
if input.autoCompleteEnabled then
input.autoComplete.x = input.x
if input.autoCompleteVerticalAlignment == GUI.alignment.vertical.top then
input.autoComplete.y = input.y - input.autoComplete.height
else
input.autoComplete.y = input.y + input.height
end
input.autoComplete.width = input.width
input.autoComplete:draw()
end
end
local function inputStartInput(input)
local mainContainer = input:getFirstParent()
local textOnStart = input.text
input.focused = true
if input.historyEnabled then
input.historyIndex = input.historyIndex + 1
end
if input.eraseTextOnFocus then
input.text = ""
end
input.cursorBlinkState = true
input:setCursorPosition(unicode.len(input.text) + 1)
if input.autoCompleteEnabled then
input.autoCompleteMatchMethod()
end
mainContainer:drawOnScreen()
local e1, e2, e3, e4, e5, e6
while true do
e1, e2, e3, e4, e5, e6 = event.pull(input.cursorBlinkDelay)
if e1 == "touch" or e1 == "drag" then
if input:isPointInside(e3, e4) then
input:setCursorPosition(input.textCutFrom + e3 - input.x - input.textOffset)
input.cursorBlinkState = true
mainContainer:drawOnScreen()
elseif input.autoComplete:isPointInside(e3, e4) then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, e1, e2, e3, e4, e5, e6)
else
input.cursorBlinkState = false
break
end
elseif e1 == "scroll" then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, e1, e2, e3, e4, e5, e6)
elseif e1 == "key_down" then
-- Return
if e4 == 28 then
if input.autoCompleteEnabled and input.autoComplete.itemCount > 0 then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, e1, e2, e3, e4, e5, e6)
else
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
input.cursorBlinkState = false
break
end
-- Arrows up/down/left/right
elseif e4 == 200 then
if input.autoCompleteEnabled and input.autoComplete.selectedItem > 1 then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, e1, e2, e3, e4, e5, e6)
else
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)
if input.autoCompleteEnabled then
input.autoCompleteMatchMethod()
end
end
end
elseif e4 == 208 then
if input.autoCompleteEnabled and input.historyIndex == #input.history + 1 then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, e1, e2, e3, e4, e5, e6)
else
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)
if input.autoCompleteEnabled then
input.autoCompleteMatchMethod()
end
end
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)
if input.autoCompleteEnabled then
input.autoCompleteMatchMethod()
end
-- Delete
elseif e4 == 211 then
input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. unicode.sub(input.text, input.cursorPosition + 1, -1)
if input.autoCompleteEnabled then
input.autoCompleteMatchMethod()
end
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)
if input.autoCompleteEnabled then
input.autoCompleteMatchMethod()
end
end
end
input.cursorBlinkState = true
mainContainer:drawOnScreen()
elseif e1 == "clipboard" 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))
input.cursorBlinkState = true
mainContainer:drawOnScreen()
elseif not e1 then
input.cursorBlinkState = not input.cursorBlinkState
mainContainer:drawOnScreen()
end
end
input.focused = false
if input.autoCompleteEnabled then
input.autoComplete:clear()
end
if input.validator then
if not input.validator(input.text) then
input.text = textOnStart
input:setCursorPosition(unicode.len(input.text) + 1)
end
end
if input.onInputFinished then
input.onInputFinished(mainContainer, input)
end
mainContainer:drawOnScreen()
end
local function inputEventHandler(mainContainer, input, e1)
if e1 == "touch" then
input:startInput()
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
input.autoComplete = GUI.autoComplete(1, 1, 30, 7, 0xE1E1E1, 0x969696, 0x3C3C3C, 0x3C3C3C, 0x969696, 0xE1E1E1, 0xC3C3C3, 0x4B4B4B)
input.autoCompleteEnabled = false
input.autoCompleteVerticalAlignment = GUI.alignment.vertical.bottom
return input
end
-----------------------------------------------------------------------
local function autoCompleteDraw(object)
local y, yEnd = object.y, object.y + object.height - 1
buffer.square(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
buffer.square(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
buffer.text(object.x + 1, y, textMatchColor, unicode.sub(object.matchText, 1, object.width - 2))
buffer.text(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(mainContainer, 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(mainContainer, object, e1, e2, e3, e4, e5, ...)
if e1 == "touch" then
object.selectedItem = e4 - object.y + object.fromItem
mainContainer:drawOnScreen()
if object.onItemSelected then
os.sleep(0.2)
object.onItemSelected(mainContainer, object, e1, e2, e3, e4, e5, ...)
end
elseif e1 == "scroll" then
autoCompleteScroll(mainContainer, object, -e5)
mainContainer:drawOnScreen()
elseif e1 == "key_down" then
if e4 == 28 then
if object.onItemSelected then
object.onItemSelected(mainContainer, 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(mainContainer, object, -1)
end
mainContainer:drawOnScreen()
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(mainContainer, object, 1)
end
mainContainer:drawOnScreen()
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 = buffer.getIndex(brailleCanvas.x + x - 1, brailleCanvas.y + y - 1)
background, foreground, symbol = buffer.rawGet(index)
buffer.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 mainContainer = GUI.fullScreenContainer()
mainContainer:addChild(palette)
palette.onSubmit = function()
mainContainer:stopEventHandling()
end
palette.cancelButton.onTouch = palette.onSubmit
mainContainer:drawOnScreen()
mainContainer:startEventHandling()
return palette.color.integer
end
function GUI.palette(x, y, startColor)
local palette = GUI.container(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 = buffer.get(x, y)
local r, g, b = color.integerToRGB(background)
buffer.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
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)
buffer.text(object.x, object.y, 0x0, ">")
buffer.text(object.x + 4, object.y, 0x0, "<")
end
local colorPanel = palette:addChild(GUI.panel(58, 2, 12, 3, 0x0))
palette:addChild(GUI.roundedButton(58, 6, 12, 1, 0x4B4B4B, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "OK")).onTouch = function()
if palette.onSubmit then
palette.onSubmit()
end
end
palette:addChild(GUI.roundedButton(58, 8, 12, 1, 0xFFFFFF, 0x696969, 0x2D2D2D, 0xFFFFFF, "Cancel")).onTouch = function()
if palette.onCancel then
palette.onCancel()
end
end
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:getFirstParent():drawOnScreen()
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, 0x000000, 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, 0x000000, "", "", true))
inputs[i].validator = validator
inputs[i].onInputFinished = onInputFinished
y = y + 2
end
local favourites
if fs.exists(GUI.paletteConfigPath) then
favourites = table.fromFile(GUI.paletteConfigPath)
else
favourites = {}
for i = 1, 6 do favourites[i] = color.HSBToInteger(math.random(0, 360), 1, 1) end
table.toFile(GUI.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(mainContainer)
paletteSwitchColorFromHex(favourites[i])
paletteRefreshBigImage()
paletteUpdateCrestsCoordinates()
mainContainer:drawOnScreen()
end
end
palette:addChild(GUI.button(58, 25, 12, 1, 0xFFFFFF, 0x4B4B4B, 0x2D2D2D, 0xFFFFFF, "+")).onTouch = function(mainContainer)
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
table.toFile(GUI.paletteConfigPath, favourites)
mainContainer:drawOnScreen()
end
end
bigImage.eventHandler = function(mainContainer, object, e1, e2, e3, e4)
if e1 == "touch" or e1 == "drag" then
bigCrest.localX, bigCrest.localY = e3 - palette.x - 1, e4 - palette.y
paletteSwitchColorFromHex(select(3, component.gpu.get(e3, e4)))
mainContainer:drawOnScreen()
end
end
bigCrest.eventHandler = bigImage.eventHandler
miniImage.eventHandler = function(mainContainer, object, e1, e2, e3, e4)
if e1 == "touch" or e1 == "drag" then
miniCrest.localY = e4 - palette.y + 1
paletteSwitchColorFromHsb((e4 - miniImage.y) * 360 / miniImage.height, palette.color.hsb.saturation, palette.color.hsb.brightness)
paletteRefreshBigImage()
mainContainer:drawOnScreen()
end
end
palette.show = paletteShow
paletteSwitchColorFromHex(startColor)
paletteUpdateCrestsCoordinates()
paletteRefreshBigImage()
paletteRefreshMiniImage()
return palette
end
-----------------------------------------------------------------------
local function textUpdate(object)
object.width = unicode.len(object.text)
return object
end
local function textDraw(object)
object:update()
buffer.text(object.x, object.y, object.color, object.text)
return object
end
function GUI.text(x, y, color, text)
local object = GUI.object(x, y, 1, 1)
object.text = text
object.color = color
object.update = textUpdate
object.draw = textDraw
object:update()
return object
end
-----------------------------------------------------------------------
function GUI.addFadeContainer(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, 0x0, GUI.colors.fadeContainer.transparency))
container.panel.eventHandler = function(parentContainer, object, e1)
if e1 == "touch" then
container:delete()
parentContainer:drawOnScreen()
end
end
end
if addLayout then
container.layout = container:addChild(GUI.layout(1, 1, container.width, container.height, 3, 1))
container.layout.defaultColumn = 2
container.layout:setColumnWidth(1, GUI.sizePolicies.percentage, 0.375)
container.layout:setColumnWidth(2, GUI.sizePolicies.percentage, 0.25)
container.layout:setColumnWidth(3, GUI.sizePolicies.percentage, 0.375)
container.layout:setCellFitting(2, 1, true, false)
if title then
container.label = container.layout:addChild(GUI.label(1, 1, 1, 1, GUI.colors.fadeContainer.title, title)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)
end
end
return container
end
-----------------------------------------------------------------------
function GUI.addPaletteWindowToContainer(parentContainer, color)
local palette = parentContainer:addChild(GUI.windowFromContainer(GUI.palette(1, 1, color or 0x9900FF)))
palette.localX, palette.localY = math.floor(parentContainer.width / 2 - palette.width / 2), math.floor(parentContainer.height / 2 - palette.height / 2)
return palette
end
-----------------------------------------------------------------------
local function listUpdate(object)
object.backgroundPanel.width, object.backgroundPanel.height = object.width, object.height
object.backgroundPanel.colors.background = object.colors.default.background
object.itemsLayout.width, object.itemsLayout.height = object.width, object.height
local step, child = false
for i = 1, #object.itemsLayout.children do
child = object.itemsLayout.children[i]
-- Жмяканье пизды
child.pressed = i == object.selectedItem
-- Цвет залупы
if step then
child.colors.default = object.colors.alternating
else
child.colors.default = object.colors.default
end
child.colors.pressed, step = object.colors.pressed, not step
-- Размеры хуйни
if object.itemsLayout.cells[1][1].direction == GUI.directions.horizontal then
if object.offsetMode then
child.width, child.height = object.itemSize * 2 + unicode.len(child.text), object.height
else
child.width, child.height = object.itemSize, object.height
end
else
if object.offsetMode then
child.width, child.height = object.width, object.itemSize * 2 + 1
else
child.width, child.height = object.width, object.itemSize
end
end
end
return list
end
local function listSelect(object, index)
object.selectedItem = index
object:update()
return object
end
local function listDeselect(object)
object.selectedItem = nil
object:update()
return object
end
local function listItemEventHandler(mainContainer, object, e1, ...)
if e1 == "touch" or e1 == "drag" then
object.parent.parent:select(object:indexOf())
mainContainer:drawOnScreen()
if object.onTouch then
object.onTouch(mainContainer, object, e1, ...)
end
end
end
local function listAddItem(object, text)
local item = object.itemsLayout:addChild(GUI.button(1, 1, 1, 1, 0, 0, 0, 0, text))
item.switchMode = true
item.animated = false
item.eventHandler = listItemEventHandler
object:update()
return item
end
local function listSetAlignment(object, ...)
object.itemsLayout:setCellAlignment(1, 1, ...)
return object
end
local function listSetSpacing(object, ...)
object.itemsLayout:setCellSpacing(1, 1, ...)
return object
end
local function listSetDirection(object, ...)
object.itemsLayout:setCellDirection(1, 1, ...)
object:update()
return object
end
local function listGetItem(object, index)
return object.itemsLayout.children[index]
end
function GUI.list(x, y, width, height, itemSize, spacing, backgroundColor, textColor, backgroundAlternatingColor, textAlternatingColor, backgroundSelectedColor, textSelectedColor, offsetMode)
local object = GUI.container(x, y, width, height)
object.colors = {
default = {
background = backgroundColor,
text = textColor
},
alternating = {
background = backgroundAlternatingColor,
text = textAlternatingColor
},
pressed = {
background = backgroundSelectedColor,
text = textSelectedColor
},
}
object.selectedItem = 1
object.select = listSelect
object.deselect = listDeselect
object.offsetMode = offsetMode
object.itemSize = itemSize
object.backgroundPanel = object:addChild(GUI.panel(1, 1, width, height, backgroundColor))
object.itemsLayout = object:addChild(GUI.layout(1, 1, width, height, 1, 1))
object.update = listUpdate
object.addItem = listAddItem
object.getItem = listGetItem
object.setAlignment = listSetAlignment
object.setSpacing = listSetSpacing
object.setDirection = listSetDirection
object:setAlignment(GUI.alignment.horizontal.left, GUI.alignment.vertical.top)
object:setSpacing(spacing)
object:setDirection(GUI.directions.vertical)
return object
end
------------------------------------------------------------------------------------------
function GUI.highlightString(x, y, fromChar, limit, indentationWidth, colorScheme, patterns, s)
fromChar = fromChar or 1
local counter, symbols, colors, stringLength, bufferIndex, newFrameBackgrounds, newFrameForegrounds, newFrameSymbols, searchFrom, starting, ending = indentationWidth, {}, {}, unicode.len(s), buffer.getIndex(x, y), buffer.getNewFrameTables()
local toChar = math.min(stringLength, fromChar + limit - 1)
for i = 1, stringLength do
symbols[i] = unicode.sub(s, i, i)
end
for j = 1, #patterns do
searchFrom = 1
while true do
starting, ending = string.unicodeFind(s, patterns[j][1], searchFrom)
if starting then
for i = starting + patterns[j][3], ending - patterns[j][4] do
colors[i] = colorScheme[patterns[j][2]]
end
else
break
end
searchFrom = ending + 1 - patterns[j][4]
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
-----------------------------------------------------------------------
function windowDraw(window)
GUI.drawContainerContent(window)
GUI.windowShadow(window.x, window.y, window.width, window.height, GUI.colors.windows.shadowTransparency, true)
return window
end
local function windowCheck(window, x, y)
local child
for i = #window.children, 1, -1 do
child = window.children[i]
if child.children then
if windowCheck(child, x, y) then
return true
end
elseif child.eventHandler and not child.hidden and not child.disabled and child:isPointInside(x, y) then
return true
end
end
end
local function windowEventHandler(mainContainer, window, e1, e2, e3, e4)
if e1 == "touch" then
if not windowCheck(window, e3, e4) then
window.lastTouchX, window.lastTouchY = e3, e4
end
if window ~= window.parent.children[#window.parent.children] then
window:moveToFront()
mainContainer:drawOnScreen()
end
elseif e1 == "drag" and window.lastTouchX and not windowCheck(window, e3, e4) then
local xOffset, yOffset = e3 - window.lastTouchX, e4 - window.lastTouchY
if xOffset ~= 0 or yOffset ~= 0 then
window.localX, window.localY = window.localX + xOffset, window.localY + yOffset
window.lastTouchX, window.lastTouchY = e3, e4
mainContainer:drawOnScreen()
end
elseif e1 == "drop" then
window.lastTouchX, window.lastTouchY = nil, nil
end
end
local function windowResize(window, width, height)
window.width, window.height = width, height
if window.onResize then
window.onResize(width, height)
end
return window
end
function GUI.windowFromContainer(container)
container.eventHandler = windowEventHandler
container.draw = windowDraw
return container
end
function GUI.window(x, y, width, height)
local window = GUI.windowFromContainer(GUI.container(x, y, width, height))
window.resize = windowResize
return window
end
------------------------------------------------------------------------------------------
local function keyAndValueUpdate(object)
object.keyLength, object.valueLength = unicode.len(object.key), unicode.len(object.value)
object.width = object.keyLength + object.valueLength
end
local function keyAndValueDraw(object)
keyAndValueUpdate(object)
buffer.text(object.x, object.y, object.colors.key, object.key)
buffer.text(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
------------------------------------------------------------------------------------------
return GUI