MineOS/Libraries/GUI.lua
2024-02-01 23:47:49 +03:00

5102 lines
153 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.

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