MineOS/lib/GUI.lua
2017-10-17 23:36:55 +03:00

3524 lines
120 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.

----------------------------------------- Libraries -----------------------------------------
require("advancedLua")
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")
----------------------------------------- Constants -----------------------------------------
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.dropDownMenuElementTypes = enum(
"default",
"separator"
)
GUI.filesystemModes = enum(
"file",
"directory",
"both",
"open",
"save"
)
GUI.colors = {
disabled = {
background = 0x888888,
text = 0xAAAAAA
},
contextMenu = {
separator = 0x888888,
default = {
background = 0xFFFFFF,
text = 0x2D2D2D
},
disabled = 0x888888,
pressed = {
background = 0x3366CC,
text = 0xFFFFFF
},
transparency = {
background = 0.24,
shadow = 0.4
}
},
windows = {
title = {
background = 0xE1E1E1,
text = 0x2D2D2D
},
backgroundPanel = 0xF0F0F0,
tabBar = {
default = {
background = 0x2D2D2D,
text = 0xF0F0F0
},
selected = {
background = 0xF0F0F0,
text = 0x2D2D2D
}
}
}
}
----------------------------------------- Interface objects -----------------------------------------
function GUI.callMethod(method, ...)
if method then method(...) end
end
local function objectIsClicked(object, x, y)
return
x >= object.x and
y >= object.y and
x <= object.x + object.width - 1 and
y <= object.y + object.height - 1 and
not object.disabled and
not object.hidden
end
local function objectDraw(object)
return object
end
function GUI.object(x, y, width, height)
return {
x = x,
y = y,
width = width,
height = height,
isClicked = objectIsClicked,
draw = objectDraw
}
end
----------------------------------------- Object alignment -----------------------------------------
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 = math.floor(object.x + object.width / 2 - subObject.width / 2)
elseif object.alignment.horizontal == GUI.alignment.horizontal.right then
x = object.x + object.width - subObject.width
else
error("Unknown horizontal alignment: " .. tostring(object.alignment.horizontal))
end
if object.alignment.vertical == GUI.alignment.vertical.top then
y = object.y
elseif object.alignment.vertical == GUI.alignment.vertical.center then
y = math.floor(object.y + object.height / 2 - subObject.height / 2)
elseif object.alignment.vertical == GUI.alignment.vertical.bottom then
y = object.y + object.height - subObject.height
else
error("Unknown vertical alignment: " .. tostring(object.alignment.vertical))
end
return x, y
end
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
----------------------------------------- Containers -----------------------------------------
local function checkObjectParentExists(object)
if not object.parent then error("Object doesn't have a parent container") end
end
local function containerObjectIndexOf(object)
checkObjectParentExists(object)
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 = object:indexOf()
if objectIndex < #object.parent.children then
object.parent.children[index], object.parent.children[index + 1] = swap(object.parent.children[index], object.parent.children[index + 1])
end
return object
end
local function containerObjectMoveBackward(object)
local objectIndex = object:indexOf()
if objectIndex > 1 then
object.parent.children[objectIndex], object.parent.children[objectIndex - 1] = swap(object.parent.children[objectIndex], object.parent.children[objectIndex - 1])
end
return object
end
local function containerObjectMoveToFront(object)
local objectIndex = object:indexOf()
table.insert(object.parent.children, object)
table.remove(object.parent.children, objectIndex)
return object
end
local function containerObjectMoveToBack(object)
local objectIndex = object:indexOf()
table.insert(object.parent.children, 1, object)
table.remove(object.parent.children, objectIndex + 1)
return object
end
local function containerObjectGetFirstParent(object)
if object.parent then
local currentParent = object.parent
while currentParent.parent do
currentParent = currentParent.parent
end
return currentParent
else
error("Object doesn't have any parents")
end
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("GUIAnimationStart")
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 firstParent = object:getFirstParent()
firstParent.animations = firstParent.animations or {}
table.insert(firstParent.animations, {
object = object,
position = 0,
start = containerObjectAnimationStart,
stop = containerObjectAnimationStop,
delete = containerObjectAnimationDelete,
frameHandler = frameHandler,
onFinish = onFinish,
})
return firstParent.animations[#firstParent.animations]
end
function GUI.addChildToContainer(container, object, atIndex)
object.localPosition = {
x = object.x,
y = 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 <= R2Y2 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.calculateChildAbsolutePosition(object)
object.x, object.y = object.parent.x + object.localPosition.x - 1, object.parent.y + object.localPosition.y - 1
end
function GUI.drawContainerContent(container)
local R1X1, R1Y1, R1X2, R1Y2 = 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 objectIndex = 1, #container.children do
if not container.children[objectIndex].hidden then
GUI.calculateChildAbsolutePosition(container.children[objectIndex])
container.children[objectIndex]:draw()
end
end
buffer.setDrawLimit(R1X1, R1Y1, R1X2, R1Y2)
end
return container
end
local function containerHandler(isScreenEvent, mainContainer, currentContainer, eventData, x1, y1, x2, y2)
local breakRecursion = false
if not isScreenEvent or x1 and eventData[3] >= x1 and eventData[4] >= y1 and eventData[3] <= x2 and eventData[4] <= y2 then
for i = #currentContainer.children, 1, -1 do
if not currentContainer.children[i].hidden then
if currentContainer.children[i].children then
local intersectionX1, intersectionY1, intersectionX2, intersectionY2 = getRectangleIntersection(
x1, y1, x2, y2,
currentContainer.children[i].x,
currentContainer.children[i].y,
currentContainer.children[i].x + currentContainer.children[i].width - 1,
currentContainer.children[i].y + currentContainer.children[i].height - 1
)
if intersectionX1 then
if containerHandler(isScreenEvent, mainContainer, currentContainer.children[i], eventData, intersectionX1, intersectionY1, intersectionX2, intersectionY2) then
breakRecursion = true
break
end
end
else
if isScreenEvent then
if currentContainer.children[i]:isClicked(eventData[3], eventData[4]) then
GUI.callMethod(currentContainer.children[i].eventHandler, mainContainer, currentContainer.children[i], eventData)
breakRecursion = true
break
end
else
GUI.callMethod(currentContainer.children[i].eventHandler, mainContainer, currentContainer.children[i], eventData)
end
end
end
end
GUI.callMethod(currentContainer.eventHandler, mainContainer, currentContainer, eventData)
end
if breakRecursion then
return true
end
end
function GUI.isScreenEvent(eventType)
return
eventType == "touch" or
eventType == "drag" or
eventType == "drop" or
eventType == "scroll" or
eventType == "double_touch"
end
local function containerStartEventHandling(container, eventHandlingDelay)
container.eventHandlingDelay = eventHandlingDelay
local eventData, animationIndex, needDraw
while true do
eventData = {event.pull(container.animations and 0 or container.eventHandlingDelay)}
containerHandler(
GUI.isScreenEvent(eventData[1]),
container,
container,
eventData,
container.x,
container.y,
container.x + container.width - 1,
container.y + container.height - 1
)
if container.animations then
animationIndex = 1
while animationIndex <= #container.animations do
if container.animations[animationIndex].started then
needDraw = true
container.animations[animationIndex].position = (computer.uptime() - container.animations[animationIndex].startUptime) / container.animations[animationIndex].duration
if container.animations[animationIndex].position <= 1 then
container.animations[animationIndex].frameHandler(container, container.animations[animationIndex].object, container.animations[animationIndex])
else
container.animations[animationIndex].position = 1
container.animations[animationIndex].started = false
container.animations[animationIndex].frameHandler(container, container.animations[animationIndex].object, container.animations[animationIndex])
if container.animations[animationIndex].onFinish then
needDraw = false
container:draw()
buffer.draw()
container.animations[animationIndex].onFinish(container, container.animations[animationIndex].object, container.animations[animationIndex])
end
if container.animations[animationIndex].deleteLater then
table.remove(container.animations, animationIndex)
animationIndex = animationIndex - 1
end
end
end
animationIndex = animationIndex + 1
end
if needDraw then
container:draw()
buffer.draw()
end
if #container.animations == 0 then
container.animations = nil
end
end
if container.dataToReturn then
local dataToReturn = container.dataToReturn
container.dataToReturn = nil
return table.unpack(dataToReturn)
end
end
end
local function containerReturnData(container, ...)
container.dataToReturn = {...}
end
local function containerStopEventHandling(container)
containerReturnData(container, nil)
end
function GUI.container(x, y, width, height)
local container = GUI.object(x, y, width, height)
container.children = {}
container.draw = GUI.drawContainerContent
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.width, buffer.height)
end
----------------------------------------- Buttons -----------------------------------------
local function buttonDraw(object)
local xText, yText = GUI.getAlignmentCoordinates(object, {width = unicode.len(object.text), height = 1})
local buttonColor, textColor = object.colors.default.background, object.colors.default.text
if object.disabled then
buttonColor, textColor = object.colors.disabled.background, object.colors.disabled.text
else
if object.pressed then
buttonColor, textColor = object.colors.pressed.background, object.colors.pressed.text
end
end
if buttonColor then
if object.buttonType == 1 then
buffer.square(object.x, object.y, object.width, object.height, buttonColor, textColor, " ")
elseif object.buttonType == 2 then
local x2, y2 = object.x + object.width - 1, object.y + object.height - 1
if object.height > 1 then
buffer.text(object.x + 1, object.y, buttonColor, string.rep("", object.width - 2))
buffer.text(object.x, object.y, buttonColor, "")
buffer.text(x2, object.y, buttonColor, "")
buffer.square(object.x, object.y + 1, object.width, object.height - 2, buttonColor, textColor, " ")
buffer.text(object.x + 1, y2, buttonColor, string.rep("", object.width - 2))
buffer.text(object.x, y2, buttonColor, "")
buffer.text(x2, y2, buttonColor, "")
else
buffer.square(object.x, object.y, object.width, object.height, buttonColor, textColor, " ")
GUI.roundedCorners(object.x, object.y, object.width, object.height, buttonColor)
end
else
buffer.frame(object.x, object.y, object.width, object.height, buttonColor)
end
end
buffer.text(xText, yText, textColor, object.text)
return object
end
local function buttonPress(object)
object.pressed = true
buttonDraw(object)
end
local function buttonRelease(object)
object.pressed = nil
buttonDraw(object)
end
local function buttonPressAndRelease(object, pressTime)
buttonPress(object)
buffer.draw()
os.sleep(pressTime or 0.2)
buttonRelease(object)
buffer.draw()
end
local function buttonEventHandler(mainContainer, object, eventData)
if eventData[1] == "touch" then
if object.switchMode then
object.pressed = not object.pressed
mainContainer:draw()
buffer.draw()
GUI.callMethod(object.onTouch, mainContainer, object, eventData)
else
object.pressed = true
mainContainer:draw()
buffer.draw()
os.sleep(0.2)
object.pressed = false
mainContainer:draw()
buffer.draw()
GUI.callMethod(object.onTouch, mainContainer, object, eventData)
end
end
end
local function buttonCreate(buttonType, x, y, width, height, buttonColor, textColor, buttonPressedColor, textPressedColor, text, disabledState)
local object = GUI.object(x, y, width, height)
object.colors = {
default = {
background = buttonColor,
text = textColor
},
pressed = {
background = buttonPressedColor,
text = textPressedColor
},
disabled = {
background = GUI.colors.disabled.background,
text = GUI.colors.disabled.text,
}
}
object.eventHandler = buttonEventHandler
object.buttonType = buttonType
object.disabled = disabledState
object.text = text
object.press = buttonPress
object.release = buttonRelease
object.pressAndRelease = buttonPressAndRelease
object.draw = buttonDraw
object.setAlignment = GUI.setAlignment
object:setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.center)
return object
end
function GUI.button(...)
return buttonCreate(1, ...)
end
function GUI.adaptiveButton(x, y, xOffset, yOffset, buttonColor, textColor, buttonPressedColor, textPressedColor, text, ...)
return buttonCreate(1, x, y, unicode.len(text) + xOffset * 2, yOffset * 2 + 1, buttonColor, textColor, buttonPressedColor, textPressedColor, text, ...)
end
function GUI.roundedButton(...)
return buttonCreate(2, ...)
end
function GUI.adaptiveRoundedButton(x, y, xOffset, yOffset, buttonColor, textColor, buttonPressedColor, textPressedColor, text, ...)
return buttonCreate(2, x, y, unicode.len(text) + xOffset * 2, yOffset * 2 + 1, buttonColor, textColor, buttonPressedColor, textPressedColor, text, ...)
end
function GUI.framedButton(...)
return buttonCreate(3, ...)
end
function GUI.adaptiveFramedButton(x, y, xOffset, yOffset, buttonColor, textColor, buttonPressedColor, textPressedColor, text, ...)
return buttonCreate(3, x, y, unicode.len(text) + xOffset * 2, yOffset * 2 + 1, buttonColor, textColor, buttonPressedColor, textPressedColor, text, ...)
end
----------------------------------------- TabBar -----------------------------------------
local function tabBarTabEventHandler(mainContainer, object, eventData)
if eventData[1] == "touch" then
object.parent.selectedItem = object:indexOf() - 1
mainContainer:draw()
buffer.draw()
GUI.callMethod(object.onTouch, mainContainer, object, eventData)
end
end
local function tabBarDraw(tabBar)
tabBar.backgroundPanel.width, tabBar.backgroundPanel.height, tabBar.backgroundPanel.colors.background = tabBar.width, tabBar.height, tabBar.colors.default.background
local totalWidth = 0
for i = 2, #tabBar.children do
totalWidth = totalWidth + tabBar.children[i].width + tabBar.spaceBetweenTabs
end
totalWidth = totalWidth - tabBar.spaceBetweenTabs
local x = math.floor(tabBar.width / 2 - totalWidth / 2)
for i = 2, #tabBar.children do
tabBar.children[i].localPosition.x = x
x = x + tabBar.children[i].width + tabBar.spaceBetweenTabs
tabBar.children[i].pressed = (i - 1) == tabBar.selectedItem
end
GUI.drawContainerContent(tabBar)
return tabBar
end
local function tabBarAddItem(tabBar, text)
local item = tabBar:addChild(GUI.button(1, 1, unicode.len(text) + tabBar.horizontalTabOffset * 2, tabBar.height, tabBar.colors.default.background, tabBar.colors.default.text, tabBar.colors.selected.background, tabBar.colors.selected.text, text))
item.switchMode = true
item.eventHandler = tabBarTabEventHandler
return item
end
function GUI.tabBar(x, y, width, height, horizontalTabOffset, spaceBetweenTabs, backgroundColor, textColor, backgroundSelectedColor, textSelectedColor, ...)
local tabBar = GUI.container(x, y, width, height)
tabBar.backgroundPanel = tabBar:addChild(GUI.panel(1, 1, 1, 1, backgroundColor))
tabBar.horizontalTabOffset = horizontalTabOffset
tabBar.spaceBetweenTabs = spaceBetweenTabs
tabBar.colors = {
default = {
background = backgroundColor,
text = textColor
},
selected = {
background = backgroundSelectedColor,
text = textSelectedColor
}
}
tabBar.selectedItem = 1
tabBar.draw = tabBarDraw
tabBar.addItem = tabBarAddItem
local items = {...}
for i = 1, #items do
tabBar:addItem(items[i])
end
return tabBar
end
----------------------------------------- Panel -----------------------------------------
local function drawPanel(object)
buffer.square(object.x, object.y, object.width, object.height, object.colors.background, 0x000000, " ", object.colors.transparency)
return object
end
function GUI.panel(x, y, width, height, color, transparency)
local object = GUI.object(x, y, width, height)
object.colors = {
background = color,
transparency = transparency
}
object.draw = drawPanel
return object
end
----------------------------------------- Label -----------------------------------------
local function drawLabel(object)
local xText, yText = GUI.getAlignmentCoordinates(object, {width = unicode.len(object.text), height = 1})
buffer.text(xText, 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
----------------------------------------- Image -----------------------------------------
local function drawImage(object)
buffer.image(object.x, object.y, object.image)
return object
end
function GUI.image(x, y, image)
local object = GUI.object(x, y, image[1], image[2])
object.image = image
object.draw = drawImage
return object
end
----------------------------------------- Action buttons -----------------------------------------
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
----------------------------------------- Menu -----------------------------------------
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, eventData)
if eventData[1] == "touch" then
if object.onTouch then
object.pressed = true
mainContainer:draw()
buffer.draw()
object.onTouch(eventData)
object.pressed = false
mainContainer:draw()
buffer.draw()
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.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
----------------------------------------- ProgressBar Object -----------------------------------------
local function drawProgressBar(object)
local activeWidth = math.floor(object.value * object.width / 100)
if object.thin then
buffer.text(object.x, object.y, object.colors.passive, string.rep("", object.width))
buffer.text(object.x, object.y, object.colors.active, string.rep("", activeWidth))
else
buffer.square(object.x, object.y, object.width, object.height, object.colors.passive, 0x0, " ")
buffer.square(object.x, object.y, activeWidth, object.height, object.colors.active, 0x0, " ")
end
if object.showValue then
local stringValue = tostring((object.valuePrefix or "") .. object.value .. (object.valuePostfix or ""))
buffer.text(math.floor(object.x + object.width / 2 - unicode.len(stringValue) / 2), object.y + 1, object.colors.value, stringValue)
end
return object
end
function GUI.progressBar(x, y, width, activeColor, passiveColor, valueColor, value, thin, showValue, valuePrefix, valuePostfix)
local object = GUI.object(x, y, width, 1)
object.value = value
object.colors = {active = activeColor, passive = passiveColor, value = valueColor}
object.thin = thin
object.draw = drawProgressBar
object.showValue = showValue
object.valuePrefix = valuePrefix
object.valuePostfix = valuePostfix
return object
end
----------------------------------------- Other GUI elements -----------------------------------------
function GUI.windowShadow(x, y, width, height, transparency, thin)
transparency = transparency
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
------------------------------------------------- Error window -------------------------------------------------------------------
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 width = math.floor(buffer.width * 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(buffer.height / 2 - height / 2), buffer.width, height + offset * 2)
local oldPixels = buffer.copy(mainContainer.x, mainContainer.y, mainContainer.width, mainContainer.height)
local x, y = math.floor(buffer.width / 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, eventData)
if eventData[1] == "key_down" and eventData[4] == 28 then
button:pressAndRelease()
button.onTouch()
end
end
mainContainer:draw()
buffer.draw(true)
mainContainer:startEventHandling()
end
----------------------------------------- CodeView object -----------------------------------------
local function codeViewDraw(codeView)
local syntax = require("syntax")
local toLine = codeView.fromLine + codeView.height - 1
-- 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
buffer.square(codeView.x, codeView.y, codeView.lineNumbersWidth, codeView.height, syntax.colorScheme.lineNumbersBackground, syntax.colorScheme.lineNumbersText, " ")
buffer.square(codeView.codeAreaPosition, codeView.y, codeView.codeAreaWidth, codeView.height, syntax.colorScheme.background, syntax.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], syntax.colorScheme.text, " ", 0.3)
buffer.square(codeView.codeAreaPosition, y, codeView.codeAreaWidth, 1, codeView.highlights[line], syntax.colorScheme.text, " ")
end
buffer.text(codeView.codeAreaPosition - unicode.len(text) - 1, y, syntax.colorScheme.lineNumbersText, text)
y = y + 1
else
break
end
end
local oldDrawLimitX1, oldDrawLimitY1, oldDrawLimitX2, oldDrawLimitY2 = buffer.getDrawLimit()
buffer.setDrawLimit(codeView.codeAreaPosition, codeView.y, codeView.codeAreaPosition + codeView.codeAreaWidth - 1, codeView.y + codeView.height - 1)
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 syntax.colorScheme.selection, syntax.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 syntax.colorScheme.selection, syntax.colorScheme.text, " "
)
end
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 syntax.colorScheme.selection, syntax.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 syntax.colorScheme.selection, syntax.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
syntax.highlightString(codeView.codeAreaPosition - codeView.fromSymbol + 2, y, codeView.lines[i], codeView.indentationWidth)
else
buffer.text(codeView.codeAreaPosition - codeView.fromSymbol + 2, y, syntax.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.scrollBars.vertical.hidden = false
codeView.scrollBars.vertical.colors.background, codeView.scrollBars.vertical.colors.foreground = syntax.colorScheme.scrollBarBackground, syntax.colorScheme.scrollBarForeground
codeView.scrollBars.vertical.minimumValue, codeView.scrollBars.vertical.maximumValue, codeView.scrollBars.vertical.value, codeView.scrollBars.vertical.shownValueCount = 1, #codeView.lines, codeView.fromLine, codeView.height
codeView.scrollBars.vertical.localPosition.x = codeView.width
codeView.scrollBars.vertical.localPosition.y = 1
codeView.scrollBars.vertical.height = codeView.height - 1
else
codeView.scrollBars.vertical.hidden = true
end
if codeView.maximumLineLength > codeView.codeAreaWidth - 2 then
codeView.scrollBars.horizontal.hidden = false
codeView.scrollBars.horizontal.colors.background, codeView.scrollBars.horizontal.colors.foreground = syntax.colorScheme.scrollBarBackground, syntax.colorScheme.scrollBarForeground
codeView.scrollBars.horizontal.minimumValue, codeView.scrollBars.horizontal.maximumValue, codeView.scrollBars.horizontal.value, codeView.scrollBars.horizontal.shownValueCount = 1, codeView.maximumLineLength, codeView.fromSymbol, codeView.codeAreaWidth - 2
codeView.scrollBars.horizontal.localPosition.x = codeView.lineNumbersWidth + 1
codeView.scrollBars.horizontal.localPosition.y = codeView.height
codeView.scrollBars.horizontal.width = codeView.codeAreaWidth - 1
else
codeView.scrollBars.horizontal.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.scrollBars = {
vertical = codeView:addChild(GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1, true)),
horizontal = 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
----------------------------------------- Color Selector object -----------------------------------------
local function colorSelectorDraw(colorSelector)
local overlayColor = colorSelector.color < 0x7FFFFF and 0xFFFFFF or 0x000000
buffer.square(colorSelector.x, colorSelector.y, colorSelector.width, colorSelector.height, colorSelector.color, overlayColor, " ")
if colorSelector.pressed then
buffer.square(colorSelector.x, colorSelector.y, colorSelector.width, colorSelector.height, overlayColor, overlayColor, " ", 0.8)
end
if colorSelector.height > 1 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, eventData)
if eventData[1] == "touch" then
object.pressed = true
mainContainer:draw()
buffer.draw()
object.color = require("palette").show(math.floor(mainContainer.width / 2 - 35), math.floor(mainContainer.height / 2 - 12), object.color) or object.color
object.pressed = false
mainContainer:draw()
buffer.draw()
GUI.callMethod(object.onTouch, eventData)
end
end
function GUI.colorSelector(x, y, width, height, color, text)
local colorSelector = GUI.object(x, y, width, height)
colorSelector.eventHandler = colorSelectorEventHandler
colorSelector.color = color
colorSelector.text = text
colorSelector.draw = colorSelectorDraw
return colorSelector
end
----------------------------------------- Chart object -----------------------------------------
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.shortenNumber(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
----------------------------------------- Window object -----------------------------------------
local function windowDraw(window)
GUI.windowShadow(window.x, window.y, window.width, window.height, nil, true)
GUI.drawContainerContent(window)
return window
end
local function windowCheck(container, x, y)
for i = #container.children, 1, -1 do
if container.children[i].children then
if windowCheck(container.children[i], x, y) then
return true
end
elseif container.children[i].eventHandler and container.children[i]:isClicked(x, y) then
return true
end
end
end
local function windowEventHandler(mainContainer, object, eventData)
if eventData[1] == "touch" then
object.lastTouchPosition = object.lastTouchPosition or {}
object.lastTouchPosition.x, object.lastTouchPosition.y = eventData[3], eventData[4]
if object ~= object.parent.children[#object.parent.children] then
object:moveToFront()
mainContainer:draw()
buffer.draw()
end
elseif eventData[1] == "drag" and object.lastTouchPosition and not windowCheck(object, eventData[3], eventData[4]) then
local xOffset, yOffset = eventData[3] - object.lastTouchPosition.x, eventData[4] - object.lastTouchPosition.y
object.lastTouchPosition.x, object.lastTouchPosition.y = eventData[3], eventData[4]
if xOffset ~= 0 or yOffset ~= 0 then
object.localPosition.x, object.localPosition.y = object.localPosition.x + xOffset, object.localPosition.y + yOffset
mainContainer:draw()
buffer.draw()
end
elseif eventData[1] == "drop" then
object.lastTouchPosition = nil
elseif eventData[1] == "key_down" then
-- Ctrl or CMD
if keyboard.isKeyDown(29) or keyboard.isKeyDown(219) then
-- W
if eventData[4] == 17 then
if object == object.parent.children[#object.parent.children] and not eventData.windowHandled then
eventData.windowHandled = true
object:close()
mainContainer:draw()
buffer.draw()
end
end
end
end
end
function GUI.window(x, y, width, height)
local window = GUI.container(x, y, width, height)
window.eventHandler = windowEventHandler
window.draw = windowDraw
return window
end
function GUI.filledWindow(x, y, width, height, backgroundColor)
local window = GUI.window(x, y, width, height)
window.backgroundPanel = window:addChild(GUI.panel(1, 1, width, height, backgroundColor))
window.actionButtons = window:addChild(GUI.actionButtons(2, 1, false))
return window
end
function GUI.titledWindow(x, y, width, height, title, addTitlePanel)
local window = GUI.filledWindow(x, y, width, height, GUI.colors.windows.backgroundPanel)
if addTitlePanel then
window.titlePanel = window:addChild(GUI.panel(1, 1, width, 1, GUI.colors.windows.title.background))
window.backgroundPanel.localPosition.y, window.backgroundPanel.height = 2, window.height - 1
end
window.titleLabel = window:addChild(GUI.label(1, 1, width, height, GUI.colors.windows.title.text, title)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)
window.actionButtons:moveToFront()
return window
end
function GUI.tabbedWindow(x, y, width, height, ...)
local window = GUI.filledWindow(x, y, width, height, GUI.colors.windows.backgroundPanel)
window.tabBar = window:addChild(GUI.tabBar(1, 1, window.width, 3, 2, 0, GUI.colors.windows.tabBar.default.background, GUI.colors.windows.tabBar.default.text, GUI.colors.windows.tabBar.selected.background, GUI.colors.windows.tabBar.selected.text, ...))
window.backgroundPanel.localPosition.y, window.backgroundPanel.height = 4, window.height - 3
window.actionButtons:moveToFront()
window.actionButtons.localPosition.y = 2
return window
end
----------------------------------------- Dropdown Menu -----------------------------------------
local function dropDownMenuItemDraw(item)
local yText = item.y + math.floor(item.height / 2)
if item.type == GUI.dropDownMenuElementTypes.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, eventData)
if eventData[1] == "touch" then
if object.type == GUI.dropDownMenuElementTypes.default then
object.pressed = true
mainContainer:draw()
buffer.draw()
if object.subMenu then
object.subMenu.y = object.parent.y + object.localPosition.y - 1
object.subMenu.x = object.parent.x + object.parent.width
if buffer.width - 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:draw()
buffer.draw()
mainContainer.selectedItem = object:indexOf()
GUI.callMethod(object.onTouch)
end
mainContainer:stopEventHandling()
end
end
local function dropDownMenuCalculateSizes(menu)
local totalHeight = 0
for i = 1, #menu.itemsContainer.children do
totalHeight = totalHeight + (menu.itemsContainer.children[i].type == GUI.dropDownMenuElementTypes.separator and 1 or menu.itemHeight)
menu.itemsContainer.children[i].width = menu.width
end
menu.height = math.min(totalHeight, menu.maximumHeight, buffer.height - menu.y)
menu.itemsContainer.width, menu.itemsContainer.height = menu.width, menu.height
menu.nextButton.localPosition.y = menu.height
menu.prevButton.width, menu.nextButton.width = menu.width, menu.width
menu.prevButton.hidden = menu.itemsContainer.children[1].localPosition.y >= 1
menu.nextButton.hidden = menu.itemsContainer.children[#menu.itemsContainer.children].localPosition.y + menu.itemsContainer.children[#menu.itemsContainer.children].height - 1 <= menu.height
end
local function dropDownMenuAddItem(menu, text, disabled, shortcut, color)
local item = menu.itemsContainer:addChild(GUI.object(1, #menu.itemsContainer.children == 0 and 1 or menu.itemsContainer.children[#menu.itemsContainer.children].localPosition.y + menu.itemsContainer.children[#menu.itemsContainer.children].height, menu.width, menu.itemHeight))
item.type = GUI.dropDownMenuElementTypes.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.dropDownMenuElementTypes.separator
item.height = 1
return item
end
local function dropDownMenuScrollDown(menu)
if menu.itemsContainer.children[1].localPosition.y < 1 then
for i = 1, #menu.itemsContainer.children do
menu.itemsContainer.children[i].localPosition.y = menu.itemsContainer.children[i].localPosition.y + 1
end
end
menu:draw()
buffer.draw()
end
local function dropDownMenuScrollUp(menu)
if menu.itemsContainer.children[#menu.itemsContainer.children].localPosition.y + menu.itemsContainer.children[#menu.itemsContainer.children].height - 1 > menu.height then
for i = 1, #menu.itemsContainer.children do
menu.itemsContainer.children[i].localPosition.y = menu.itemsContainer.children[i].localPosition.y - 1
end
end
menu:draw()
buffer.draw()
end
local function dropDownMenuEventHandler(mainContainer, object, eventData)
if eventData[1] == "scroll" then
if eventData[5] == 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()
mainContainer:addChild(GUI.object(1, 1, mainContainer.width, mainContainer.height)).eventHandler = function(mainContainer, object, eventData)
if eventData[1] == "touch" then
mainContainer:stopEventHandling()
end
end
mainContainer:addChild(menu)
menu:draw()
buffer.draw()
mainContainer:startEventHandling()
buffer.paste(menu.x, menu.y, menu.oldPixels)
buffer.draw()
if mainContainer.selectedItem then
return menu.itemsContainer.children[mainContainer.selectedItem].text, 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.default.transparency, menu.nextButton.colors.default.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.draw = dropDownMenuDraw
menu.show = dropDownMenuShow
menu.maximumHeight = maximumHeight
menu.eventHandler = dropDownMenuEventHandler
return menu
end
----------------------------------------- Context Menu -----------------------------------------
local function contextMenuCalculate(menu)
local widestItem, widestShortcut = 0, 0
for i = 1, #menu.itemsContainer.children do
if menu.itemsContainer.children[i].type == GUI.dropDownMenuElementTypes.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)
if menu.y + menu.height >= buffer.height then menu.y = buffer.height - menu.height end
if menu.x + menu.width + 1 >= buffer.width then menu.x = buffer.width - menu.width - 1 end
return dropDownMenuShow(menu)
end
local function contextMenuAddItem(menu, ...)
contextMenuCalculate(menu)
return dropDownMenuAddItem(menu, ...)
end
local function contextMenuAddSeparator(menu, ...)
contextMenuCalculate(menu)
return dropDownMenuAddSeparator(menu, ...)
end
local function contextMenuAddSubMenu(menu, text)
local item = menu:addItem(text, false, "")
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.height * 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
----------------------------------------- Combo Box Object -----------------------------------------
local function drawComboBox(object)
buffer.square(object.x, object.y, object.width, object.height, object.colors.default.background, object.colors.default.text, " ")
local x, y, limit, arrowSize = object.x + 1, math.floor(object.y + object.height / 2), object.width - 3, object.height
if object.dropDownMenu.itemsContainer.children[object.selectedItem] then
buffer.text(x, y, object.colors.default.text, string.limit(object.dropDownMenu.itemsContainer.children[object.selectedItem].text, limit, "right"))
end
GUI.button(object.x + object.width - arrowSize * 2 + 1, object.y, arrowSize * 2 - 1, arrowSize, 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 comboBoxClear(object)
object.dropDownMenu.itemsContainer:deleteChildren()
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 selectComboBoxItem(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, eventData)
if eventData[1] == "touch" then
object:selectItem()
GUI.callMethod(object.onItemSelected, object.dropDownMenu.itemsContainer.children[object.selectedItem], eventData)
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, elementHeight, backgroundColor, textColor, arrowBackgroundColor, arrowTextColor)
local object = GUI.object(x, y, width, elementHeight)
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.height * 0.5), elementHeight,
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.addSeparator = comboBoxAddSeparator
object.draw = drawComboBox
object.selectItem = selectComboBoxItem
object.clear = comboBoxClear
object.indexOfItem = comboBoxIndexOfItem
object.getItem = comboBoxGetItem
return object
end
----------------------------------------- Switch and label object -----------------------------------------
local function switchAndLabelDraw(switchAndLabel)
switchAndLabel.label.width = switchAndLabel.width
switchAndLabel.switch.localPosition.x = switchAndLabel.width - switchAndLabel.switch.width
GUI.calculateChildAbsolutePosition(switchAndLabel.label)
GUI.calculateChildAbsolutePosition(switchAndLabel.switch)
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
----------------------------------------- Horizontal Slider Object -----------------------------------------
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.floor(object.width - ((object.maximumValue - object.value) * object.width / (object.maximumValue - object.minimumValue)))
buffer.text(object.x, object.y, object.colors.passive, string.rep("", object.width))
buffer.text(object.x, object.y, object.colors.active, string.rep("", activeWidth))
buffer.text(object.x + activeWidth - 1, object.y, object.colors.pipe, "")
return object
end
local function sliderEventHandler(mainContainer, object, eventData)
if eventData[1] == "touch" or eventData[1] == "drag" then
local clickPosition = eventData[3] - object.x + 1
object.value = object.minimumValue + (clickPosition * (object.maximumValue - object.minimumValue) / object.width)
mainContainer:draw()
buffer.draw()
GUI.callMethod(object.onValueChanged, object.value, eventData)
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
----------------------------------------- Switch object -----------------------------------------
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 switchUpdate(switch)
switch.pipePosition = switch.state and switch.width - 1 or 1
end
local function switchEventHandler(mainContainer, switch, eventData)
if eventData[1] == "touch" then
switch.state = not switch.state
switch:addAnimation(
function(mainContainer, switch, 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, switch, animation)
animation:delete()
GUI.callMethod(switch.onStateChanged)
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:update()
return switch
end
--------------------------------------------------------------------------------------------------------------------------------
local function brailleCanvasDraw(brailleCanvas)
local index, background, foreground, symbol
for y = 1, brailleCanvas.height do
for x = 1, brailleCanvas.width do
index = buffer.getIndexByCoordinates(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)
if xReal <= brailleCanvas.width and yReal <= brailleCanvas.height then
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
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]
)
end
return brailleCanvas
end
function GUI.brailleCanvas(x, y, width, height)
local brailleCanvas = GUI.object(x, y, width, height)
brailleCanvas.pixels = {}
brailleCanvas.set = brailleCanvasSet
brailleCanvas.draw = brailleCanvasDraw
for j = 1, height * 4 do
brailleCanvas.pixels[j] = {}
for i = 1, width * 2 do
brailleCanvas.pixels[j][i] = { 0, 0, 0, 0, 0, 0, 0, 0, 0x0, " " }
end
end
return brailleCanvas
end
----------------------------------------- Layout object -----------------------------------------
local function layoutCheckCell(layout, column, row)
if column < 1 or column > #layout.grid.columnSizes or row < 1 or row > #layout.grid.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.grid.columnSizes), layout.height - layoutGetAbsoluteTotalSize(layout.grid.rowSizes)
for row = 1, #layout.grid.rowSizes do
layoutGetCalculatedSize(layout.grid.rowSizes, row, rowPercentageTotalSize)
for column = 1, #layout.grid.columnSizes do
layoutGetCalculatedSize(layout.grid.columnSizes, column, columnPercentageTotalSize)
layout.grid[row][column].totalWidth, layout.grid[row][column].totalHeight = 0, 0
end
end
-- Подготавливаем объекты к расположению и подсчитываем тотальные размеры
for i = 1, #layout.children do
if not layout.children[i].hidden then
-- Проверка на позицию в сетке
if layout.children[i].layoutGridPosition.row >= 1 and layout.children[i].layoutGridPosition.row <= #layout.grid.rowSizes and layout.children[i].layoutGridPosition.column >= 1 and layout.children[i].layoutGridPosition.column <= #layout.grid.columnSizes then
-- Авто-фиттинг объектов
if layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].fitting.horizontal then
layout.children[i].width = math.round(layout.grid.columnSizes[layout.children[i].layoutGridPosition.column].calculatedSize - layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].fitting.horizontalRemove)
end
if layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].fitting.vertical then
layout.children[i].height = math.round(layout.grid.rowSizes[layout.children[i].layoutGridPosition.row].calculatedSize - layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].fitting.verticalRemove)
end
-- Направление и расчет размеров
if layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].direction == GUI.directions.horizontal then
layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalWidth = layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalWidth + layout.children[i].width + layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].spacing
layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalHeight = math.max(layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalHeight, layout.children[i].height)
else
layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalWidth = math.max(layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalWidth, layout.children[i].width)
layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalHeight = layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalHeight + layout.children[i].height + layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].spacing
end
else
error("Layout child with index " .. i .. " has been assigned to cell (" .. layout.children[i].layoutGridPosition.column .. "x" .. layout.children[i].layoutGridPosition.row .. ") out of layout grid range")
end
end
end
-- Высчитываем позицицию объектов
local x, y = 1, 1
for row = 1, #layout.grid.rowSizes do
for column = 1, #layout.grid.columnSizes do
layout.grid[row][column].x, layout.grid[row][column].y = GUI.getAlignmentCoordinates(
{
x = x,
y = y,
width = layout.grid.columnSizes[column].calculatedSize,
height = layout.grid.rowSizes[row].calculatedSize,
alignment = layout.grid[row][column].alignment,
},
{
width = layout.grid[row][column].totalWidth - (layout.grid[row][column].direction == GUI.directions.horizontal and layout.grid[row][column].spacing or 0),
height = layout.grid[row][column].totalHeight - (layout.grid[row][column].direction == GUI.directions.vertical and layout.grid[row][column].spacing or 0),
}
)
if layout.grid[row][column].margin then
layout.grid[row][column].x, layout.grid[row][column].y = GUI.getMarginCoordinates(layout.grid[row][column])
end
x = x + layout.grid.columnSizes[column].calculatedSize
end
x, y = 1, y + layout.grid.rowSizes[row].calculatedSize
end
-- Размещаем все объекты
for i = 1, #layout.children do
if not layout.children[i].hidden then
if layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].direction == GUI.directions.horizontal then
layout.children[i].localPosition.x = math.round(layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].x)
layout.children[i].localPosition.y = math.round(layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].y + layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalHeight / 2 - layout.children[i].height / 2)
layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].x = layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].x + layout.children[i].width + layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].spacing
else
layout.children[i].localPosition.x = math.round(layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].x + layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].totalWidth / 2 - layout.children[i].width / 2)
layout.children[i].localPosition.y = math.round(layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].y)
layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].y = layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].y + layout.children[i].height + layout.grid[layout.children[i].layoutGridPosition.row][layout.children[i].layoutGridPosition.column].spacing
end
end
end
end
local function layoutSetCellPosition(layout, column, row, object)
layoutCheckCell(layout, column, row)
object.layoutGridPosition = {column = column, row = row}
return object
end
local function layoutSetCellDirection(layout, column, row, direction)
layoutCheckCell(layout, column, row)
layout.grid[row][column].direction = direction
return layout
end
local function layoutSetCellSpacing(layout, column, row, spacing)
layoutCheckCell(layout, column, row)
layout.grid[row][column].spacing = spacing
return layout
end
local function layoutSetCellAlignment(layout, column, row, horizontalAlignment, verticalAlignment)
layoutCheckCell(layout, column, row)
layout.grid[row][column].alignment.horizontal, layout.grid[row][column].alignment.vertical = horizontalAlignment, verticalAlignment
return layout
end
local function layoutSetCellMargin(layout, column, row, horizontalMargin, verticalMargin)
layoutCheckCell(layout, column, row)
layout.grid[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.grid.columnSizes[column].sizePolicy, layout.grid.columnSizes[column].size = sizePolicy, size
layoutCalculatePercentageSize(true, layout.grid.columnSizes, column)
return layout
end
local function layoutSetRowHeight(layout, row, sizePolicy, size)
layout.grid.rowSizes[row].sizePolicy, layout.grid.rowSizes[row].size = sizePolicy, size
layoutCalculatePercentageSize(true, layout.grid.rowSizes, row)
return layout
end
local function layoutAddColumn(layout, sizePolicy, size)
for i = 1, #layout.grid.rowSizes do
table.insert(layout.grid[i], layoutNewCell())
end
table.insert(layout.grid.columnSizes, {
sizePolicy = sizePolicy,
size = size
})
layoutCalculatePercentageSize(false, layout.grid.columnSizes, #layout.grid.columnSizes)
-- GUI.error(layout.grid.columnSizes)
return layout
end
local function layoutAddRow(layout, sizePolicy, size)
local row = {}
for i = 1, #layout.grid.columnSizes do
table.insert(row, layoutNewCell())
end
table.insert(layout.grid, row)
table.insert(layout.grid.rowSizes, {
sizePolicy = sizePolicy,
size = size
})
layoutCalculatePercentageSize(false, layout.grid.rowSizes, #layout.grid.rowSizes)
-- GUI.error(layout.grid.rowSizes)
return layout
end
local function layoutRemoveRow(layout, row)
table.remove(layout.grid, row)
layout.grid.rowSizes[row].size = 0
layoutCalculatePercentageSize(false, layout.grid.rowSizes, row)
table.remove(layout.grid.rowSizes, row)
return layout
end
local function layoutRemoveColumn(layout, column)
for i = 1, #layout.grid.rowSizes do
table.remove(layout.grid[i], column)
end
layout.grid.columnSizes[column].size = 0
layoutCalculatePercentageSize(false, layout.grid.columnSizes, column)
table.remove(layout.grid.columnSizes, column)
return layout
end
local function layoutSetGridSize(layout, columnCount, rowCount)
layout.grid = {
rowSizes = {},
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.grid.columnSizes do
for i = 1, #layout.grid.rowSizes do
buffer.frame(
math.round(x),
math.round(y),
math.round(layout.grid.columnSizes[j].calculatedSize),
math.round(layout.grid.rowSizes[i].calculatedSize),
0xFF0000
)
y = y + layout.grid.rowSizes[i].calculatedSize
end
x, y = x + layout.grid.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.grid[row][column].direction == GUI.directions.horizontal then
layout.width = layout.width + layout.children[i].width + layout.grid[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.grid[row][column].spacing
end
end
end
if layout.grid[row][column].direction == GUI.directions.horizontal then
layout.width = layout.width - layout.grid[row][column].spacing
else
layout.height = layout.height - layout.grid[row][column].spacing
end
return layout
end
local function layoutSetCellFitting(layout, column, row, horizontal, vertical, horizontalRemove, verticalRemove )
layoutCheckCell(layout, column, row)
layout.grid[row][column].fitting = {
horizontal = horizontal,
vertical = vertical,
horizontalRemove = horizontalRemove or 0,
verticalRemove = verticalRemove or 0,
}
return layout
end
local function layoutAddChild(layout, object, ...)
GUI.addChildToContainer(layout, object, ...).layoutGridPosition = {
column = layout.defaultColumn,
row = layout.defaultRow
}
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.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.localPosition.x - 4
else
filesystemDialog.input.width = filesystemDialog.extensionComboBox.localPosition.x - 3
end
if filesystemDialog.IOMode == GUI.filesystemModes.save then
filesystemDialog.submitButton.disabled = not 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
filesystemDialog.filesystemTree:updateFileList()
end
local function filesystemDialogAddExtensionFilter(filesystemDialog, extension)
filesystemDialog.extensionComboBox:addItem(extension)
filesystemDialog.extensionComboBox.width = math.max(filesystemDialog.extensionComboBox.width, unicode.len(extension) + 3)
filesystemDialog.extensionComboBox.localPosition.x = filesystemDialog.cancelButton.localPosition.x - filesystemDialog.extensionComboBox.width - 2
filesystemDialog.filesystemTree:addExtensionFilter(extension)
filesystemDialog:setMode(filesystemDialog.IOMode, filesystemDialog.filesystemMode)
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, 2, 0, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xE1E1E1, cancelButtonText))
filesystemDialog.submitButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 2, 0, 0x3C3C3C, 0xE1E1E1, 0xE1E1E1, 0x3C3C3C, submitButtonText))
filesystemDialog.submitButton.localPosition.x = filesystemDialog.width - filesystemDialog.submitButton.width - 1
filesystemDialog.cancelButton.localPosition.x = filesystemDialog.submitButton.localPosition.x - filesystemDialog.cancelButton.width - 2
filesystemDialog.extensionComboBox = filesystemDialog:addChild(GUI.comboBox(1, height - 1, 1, 1, 0xE1E1E1, 0x666666, 0xC3C3C3, 0x888888))
filesystemDialog.extensionComboBox.hidden = true
filesystemDialog.input = filesystemDialog:addChild(GUI.input(2, height - 1, 1, 1, 0xE1E1E1, 0x666666, 0x999999, 0xE1E1E1, 0x3C3C3C, "", placeholderText))
filesystemDialog.filesystemTree = filesystemDialog:addChild(GUI.filesystemTree(1, 1, width, height - 3, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xAAAAAA, 0x3C3C3C, 0xE1E1E1, 0xBBBBBB, 0xAAAAAA, 0xC3C3C3, 0x444444))
filesystemDialog.filesystemTree.workPath = path
filesystemDialog.draw = filesystemDialogDraw
filesystemDialog.setMode = filesystemDialogSetMode
filesystemDialog.addExtensionFilter = filesystemDialogAddExtensionFilter
filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file)
return filesystemDialog
end
local function filesystemDialogShow(filesystemDialog)
filesystemDialog:addAnimation(
function(mainContainer, object, animation)
object.localPosition.y = math.floor(1 + (1.0 - animation.position) * (-object.height))
end,
function(mainContainer, switch, animation)
animation:delete()
end
):start(0.5)
return filesystemDialog
end
--------------------------------------------------------------------------------------------------------------------------------
function GUI.addFilesystemDialogToContainer(parentContainer, ...)
local container = parentContainer:addChild(GUI.container(1, 1, parentContainer.width, parentContainer.height))
container:addChild(GUI.object(1, 1, container.width, container.height))
local filesystemDialog = container:addChild(GUI.filesystemDialog(1, 1, math.floor(container.width * 0.35), math.floor(container.height * 0.8), ...))
filesystemDialog.localPosition.x = math.floor(container.width / 2 - filesystemDialog.width / 2)
filesystemDialog.localPosition.y = -filesystemDialog.height
local function onAnyTouch()
local firstParent = filesystemDialog:getFirstParent()
container:delete()
firstParent:draw()
buffer.draw()
end
filesystemDialog.cancelButton.onTouch = function()
onAnyTouch()
GUI.callMethod(filesystemDialog.onCancel)
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
GUI.callMethod(filesystemDialog.onSubmit, path)
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, filesystemMode)
object.filesystemMode = filesystemMode
end
local function filesystemChooserEventHandler(mainContainer, object, eventData)
if eventData[1] == "touch" then
object.pressed = true
mainContainer:draw()
buffer.draw()
local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, object.submitButtonText, object.cancelButtonText, object.placeholderText, object.filesystemDialogPath)
for key in pairs(object.extensionFilters) do
filesystemDialog:addExtensionFilter(key)
end
filesystemDialog:setMode(GUI.filesystemModes.open, object.filesystemMode)
filesystemDialog.onCancel = function()
object.pressed = false
mainContainer:draw()
buffer.draw()
end
filesystemDialog.onSubmit = function(path)
object.path = path
object.pressed = false
mainContainer:draw()
buffer.draw()
GUI.callMethod(object.onSubmit, object.path)
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.extensionFilters = {}
object.draw = filesystemChooserDraw
object.eventHandler = filesystemChooserEventHandler
object.addExtensionFilter = filesystemChooserAddExtensionFilter
object.setMode = filesystemChooserSetMode
return object
end
------------------------------------------------------------------------------------------------------
local function resizerDraw(object)
local horizontalMode = object.width >= object.height
local x, y, symbol
if horizontalMode then
buffer.text(object.x, math.floor(object.y + object.height / 2), object.colors.helper, string.rep("", object.width))
else
local x = math.floor(object.x + object.width / 2)
for i = object.y, object.y + object.height - 1 do
local index = buffer.getIndexByCoordinates(x, i)
buffer.rawSet(index, buffer.rawGet(index), object.colors.helper, "")
end
end
if object.touchPosition then
buffer.text(object.touchPosition.x - 1, object.touchPosition.y, object.colors.arrow, "←→")
end
end
local function resizerEventHandler(mainContainer, object, eventData)
if eventData[1] == "touch" then
object.touchPosition = {x = eventData[3], y = eventData[4]}
mainContainer:draw()
buffer.draw()
elseif eventData[1] == "drag" and object.touchPosition then
local x, y = object.touchPosition.x, object.touchPosition.y
object.touchPosition.x, object.touchPosition.y = eventData[3], eventData[4]
if object.onResize then
object.onResize(eventData[3] - x, eventData[4] - y)
end
mainContainer:draw()
buffer.draw()
elseif eventData[1] == "drop" then
object.touchPosition = nil
if object.onResizeFinished then
object.onResizeFinished()
end
mainContainer:draw()
buffer.draw()
end
end
function GUI.resizer(x, y, width, height, helperColor, arrowColor)
local object = GUI.object(x, y, width, height)
object.colors = {
helper = helperColor,
arrow = arrowColor
}
object.draw = resizerDraw
object.eventHandler = resizerEventHandler
return object
end
----------------------------------------- Scrollbar object -----------------------------------------
local function scrollBarDraw(scrollBar)
local isVertical = scrollBar.height > scrollBar.width
local valuesDelta = scrollBar.maximumValue - scrollBar.minimumValue + 1
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, eventData)
local newValue = object.value
if eventData[1] == "touch" or eventData[1] == "drag" then
local delta = object.maximumValue - object.minimumValue + 1
if object.height > object.width then
newValue = math.floor((eventData[4] - object.y + 1) / object.height * delta)
else
newValue = math.floor((eventData[3] - object.x + 1) / object.width * delta)
end
elseif eventData[1] == "scroll" then
if eventData[5] == 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 eventData[1] == "touch" or eventData[1] == "drag" or eventData[1] == "scroll" then
object.value = newValue
GUI.callMethod(object.onTouch, eventData)
mainContainer:draw()
buffer.draw()
end
end
function GUI.scrollBar(x, y, width, height, backgroundColor, foregroundColor, minimumValue, maximumValue, value, shownValueCount, onScrollValueIncrement, thin)
local scrollBar = GUI.object(x, y, width, height)
scrollBar.eventHandler = scrollBarEventHandler
scrollBar.maximumValue = maximumValue
scrollBar.minimumValue = minimumValue
scrollBar.value = value
scrollBar.onScrollValueIncrement = onScrollValueIncrement
scrollBar.shownValueCount = shownValueCount
scrollBar.thin = thin
scrollBar.colors = {
background = backgroundColor,
foreground = foregroundColor,
}
scrollBar.ghostPosition = {}
scrollBar.draw = scrollBarDraw
return scrollBar
end
----------------------------------------- Tree object -----------------------------------------
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 .. fs.name(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, eventData)
if eventData[1] == "touch" then
local i = eventData[4] - tree.y + tree.fromItem
if tree.items[i] then
if
tree.items[i].expandable and
(
tree.selectionMode == GUI.filesystemModes.file or
eventData[3] >= tree.x + tree.items[i].offset - 1 and eventData[3] <= 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
GUI.callMethod(tree.onItemExpanded, tree.selectedItem, eventData)
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
GUI.callMethod(tree.onItemSelected, tree.selectedItem, eventData)
end
end
mainContainer:draw()
buffer.draw()
end
elseif eventData[1] == "scroll" then
if eventData[5] == 1 then
if tree.fromItem > 1 then
tree.fromItem = tree.fromItem - 1
mainContainer:draw()
buffer.draw()
end
else
if tree.fromItem < #tree.items then
tree.fromItem = tree.fromItem + 1
mainContainer:draw()
buffer.draw()
end
end
end
end
local function treeAddItem(tree, name, definition, offset, expandable, disabled)
table.insert(tree.items, {name = name, expandable = expandable, offset = offset or 0, definition = definition, disabled = disabled})
return tree
end
function GUI.tree(x, y, width, height, backgroundColor, expandableColor, notExpandableColor, arrowColor, backgroundSelectedColor, anySelectionColor, arrowSelectionColor, disabledColor, scrollBarBackground, scrollBarForeground, showMode, selectionMode)
local tree = GUI.container(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
----------------------------------------- FilesystemTree object -----------------------------------------
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(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
function GUI.filesystemTree(...)
local tree = GUI.tree(...)
tree.workPath = "/"
tree.updateFileList = filesystemTreeUpdateFileList
tree.addExtensionFilter = filesystemTreeAddExtensionFilter
tree.onItemExpanded = function()
tree:updateFileList()
end
return tree
end
----------------------------------------- Text Box object -----------------------------------------
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(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, eventData)
if eventData[1] == "scroll" then
if eventData[5] == 1 then
object:scrollUp()
mainContainer:draw()
buffer.draw()
else
object:scrollDown()
mainContainer:draw()
buffer.draw()
end
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, 0x444444, 1, 1, 1, 1, 1, true)
object.scrollBarEnabled = false
textBoxCalculate(object)
return object
end
----------------------------------------- Input object -----------------------------------------
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 = input.colors.default.background, input.colors.default.text, input.colors.default.transparency, input.text
if input.focused then
background, foreground, transparency = input.colors.focused.background, input.colors.focused.text, input.colors.focused.transparency
elseif input.text == "" then
input.textCutFrom = 1
text, foreground = input.placeholderText or "", input.colors.placeholderText
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(
input.textMask and string.rep(input.textMask, unicode.len(text)) or text,
input.textCutFrom,
input.textCutFrom + input.width - 1 - input.textOffset * 2
)
)
if input.cursorBlinkState then
local index = buffer.getIndexByCoordinates(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 inputEventHandler(mainContainer, input, mainEventData)
if mainEventData[1] == "touch" then
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:draw()
buffer.draw()
while true do
local eventData = { event.pull(input.cursorBlinkDelay) }
if eventData[1] == "touch" or eventData[1] == "drag" then
if input:isClicked(eventData[3], eventData[4]) then
input:setCursorPosition(input.textCutFrom + eventData[3] - input.x - input.textOffset)
input.cursorBlinkState = true
mainContainer:draw()
buffer.draw()
elseif input.autoComplete:isClicked(eventData[3], eventData[4]) then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
else
input.cursorBlinkState = false
break
end
elseif eventData[1] == "scroll" then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
elseif eventData[1] == "key_down" then
-- Return
if eventData[4] == 28 then
if input.autoCompleteEnabled and input.autoComplete.itemCount > 0 then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
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 eventData[4] == 200 then
if input.autoCompleteEnabled and input.autoComplete.selectedItem > 1 then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
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 eventData[4] == 208 then
if input.autoCompleteEnabled and input.historyIndex == #input.history + 1 then
input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData)
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 eventData[4] == 203 then
input:setCursorPosition(input.cursorPosition - 1)
elseif eventData[4] == 205 then
input:setCursorPosition(input.cursorPosition + 1)
-- Backspace
elseif eventData[4] == 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 eventData[4] == 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(eventData[3])
if not keyboard.isControl(eventData[3]) 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:draw()
buffer.draw()
elseif eventData[1] == "clipboard" then
input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. eventData[3] .. unicode.sub(input.text, input.cursorPosition, -1)
input:setCursorPosition(input.cursorPosition + unicode.len(eventData[3]))
input.cursorBlinkState = true
mainContainer:draw()
buffer.draw()
elseif not eventData[1] then
input.cursorBlinkState = not input.cursorBlinkState
mainContainer:draw()
buffer.draw()
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
GUI.callMethod(input.onInputFinished, mainContainer, input, mainEventData, input.text)
mainContainer:draw()
buffer.draw()
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.autoComplete = GUI.autoComplete(1, 1, 30, 7, 0xE1E1E1, 0x999999, 0x3C3C3C, 0x3C3C3C, 0x999999, 0xE1E1E1, 0xC3C3C3, 0x444444)
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, object.matchText)
buffer.text(object.x + 1 + object.matchTextLength, y, textColor, unicode.sub(object.items[i], object.matchTextLength + 1, object.width - 2 - object.matchTextLength))
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 + 1
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, eventData)
if eventData[1] == "touch" then
object.selectedItem = eventData[4] - object.y + object.fromItem
mainContainer:draw()
buffer.draw()
GUI.callMethod(object.onItemSelected, mainContainer, object, eventData, object.selectedItem)
elseif eventData[1] == "scroll" then
autoCompleteScroll(mainContainer, object, -eventData[5])
mainContainer:draw()
buffer.draw()
elseif eventData[1] == "key_down" then
if eventData[4] == 28 then
GUI.callMethod(object.onItemSelected, mainContainer, object, eventData, object.selectedItem)
elseif eventData[4] == 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:draw()
buffer.draw()
elseif eventData[4] == 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:draw()
buffer.draw()
end
end
end
local function autoCompleteClear(object)
object.items = {}
object.itemCount = 0
object.fromItem = 1
object.selectedItem = 1
object.height = 0
end
local function autoCompleteMatch(object, variants, text)
object:clear()
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
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
--------------------------------------------------------------------------------------------------------------------------------
-- buffer.clear()
-- buffer.draw(true)
-- -- Создаем полноэкранный контейнер, добавляем на него загруженное изображение и полупрозрачную черную панель
-- local mainContainer = GUI.fullScreenContainer()
-- mainContainer:addChild(GUI.image(1, 1, image.load("/MineOS/Pictures/Raspberry.pic")))
-- local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, "Save", "Cancel", "File name", "/")
-- filesystemDialog:setMode(GUI.filesystemModes.save, GUI.filesystemModes.file)
-- filesystemDialog:addExtensionFilter(".pic")
-- filesystemDialog:show()
-- filesystemDialog.onSubmit = function(path)
-- GUI.error(path)
-- end
-- mainContainer:draw()
-- buffer.draw(true)
-- mainContainer:startEventHandling()
--------------------------------------------------------------------------------------------------------------------------------
return GUI