Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local event = require("event")
- local gpu = require("component").gpu
- local math = require("math")
- local keyboardEvent = require("keyboardEvent")
- local CountDownLatch = require("countDownLatch")
- local logger = require("logger")
- local log = logger.getLogger("gui")
- local ALIGN = {
- left = "left",
- right = "right",
- center = "center"
- }
- local COLORS = {
- blue = 0x4286F4,
- purple = 0xB673d6,
- red = 0xC14141,
- lightGreen = 0x7ED321,
- green = 0xDA841,
- pink = 0xF4A7B9,
- yellow = 0xfff950,
- black = 0x000000,
- white = 0xFFFFFF,
- grey = 0x47494C,
- lightGrey = 0xBBBBBB
- }
- local disabled = false
- function debug(...)
- if disabled then return end
- local bg = gpu.getBackground()
- local fg = gpu.getForeground()
- gpu.setBackground(COLORS.black)
- gpu.setForeground(COLORS.white)
- print(...)
- gpu.setBackground(bg)
- gpu.setForeground(fg)
- end
- local function truncateString(str, width, padding)
- if string.len(str) > (width - padding) then
- local maxLen = width - padding - 2
- local halfLen = math.floor(maxLen / 2)
- local rmHalfLen = math.ceil((string.len(str) - maxLen) / 2)
- local truncatedStr = string.sub(str, 1, halfLen - rmHalfLen) .. ".." .. string.sub(str, -halfLen + rmHalfLen)
- return truncatedStr
- end
- return str
- end
- local function addFillersOnString(str, align, filler, number_filler)
- local fillerCount = math.floor(number_filler / 2)
- local leftFiller = string.rep(filler, fillerCount)
- local rightFiller = string.rep(filler, number_filler - fillerCount)
- if align == "left" then
- return str .. rightFiller
- elseif align == "right" then
- return leftFiller .. str
- elseif align == "center" then
- return leftFiller .. str .. rightFiller
- else
- return str
- end
- end
- local function addPaddingOnString(str, padding, filler)
- if padding < 0 then
- return str
- end
- return string.rep(filler, padding) .. str .. string.rep(filler, padding)
- end
- local Component = {}
- function Component:new(name, x, y, width, height)
- local obj = {
- name = name,
- x = x,
- y = y,
- xMin = x,
- yMin = y,
- xMax = x + width - 1,
- yMax = y + height - 1,
- width = width,
- height = height,
- parent = nil,
- isHidden = false,
- disabled = false,
- onTouchCallback = nil,
- gpu = gpu,
- rerender = true,
- components = {},
- componentsPrioritized = {},
- defaultBackgroundColor = COLORS.black
- }
- self.__index = self
- return setmetatable(obj, self)
- end
- function Component:init()
- table.sort(self.componentsPrioritized, function(a, b)
- return a.renderPriority < b.renderPriority
- end)
- end
- function Component:setOnTouch(callback)
- self.onTouchCallback = callback
- end
- function Component:hide()
- if self.isHidden then return end
- self.isHidden = true
- self.rerender = true
- end
- function Component:show()
- if not self.isHidden then return end
- self.isHidden = false
- self.rerender = true
- end
- function Component:setParent(parent)
- self.parent = parent
- end
- function Component:getAbsXY()
- if self.parent and self.parent.getAbsXY then
- local absParentX, absParentY = self.parent:getAbsXY()
- return self.x + absParentX, self.y + absParentY
- end
- return self.x, self.y
- end
- function Component:setPosition(x, y)
- if self.x ~= x or self.y ~= y then
- self.rerender = true
- end
- self.x = x
- self.y = y
- end
- function Component:setSize(width, height)
- if self.width ~= width or self.height ~= height then
- self.rerender = true
- end
- self.width = width
- self.height = height
- self.xMax = self.x + width - 1
- self.yMax = self.y + height - 1
- end
- function Component:isTouched(x, y)
- local absParentX, absParentY = self.parent:getAbsXY()
- local xMix = self.xMin + absParentX
- local xMax = self.xMax + absParentX
- local yMin = self.yMin + absParentY
- local yMax = self.yMax + absParentY
- if x >= xMix and x <= xMax and y >= yMin and y <= yMax then
- return true
- end
- return false
- end
- function Component:notTouch(x, y)
- for _, component in pairs(self.components) do
- if component:isTouched(x, y) then
- component:onTouch(x, y)
- else
- component:notTouch(x, y)
- end
- end
- end
- function Component:onTouch(x, y)
- if self.onTouchCallback then
- self.onTouchCallback(self, x, y)
- end
- for _, component in pairs(self.components) do
- if component:isTouched(x, y) then
- component:onTouch(x, y)
- else
- component:notTouch(x, y)
- end
- end
- end
- function Component:addComponent(component, renderPriority)
- component:setParent(self)
- self.components[component.name] = component
- table.insert(self.componentsPrioritized, { renderPriority = renderPriority, component = component })
- end
- function Component:render()
- for _, prioritized in pairs(self.componentsPrioritized) do
- prioritized.component:render()
- end
- end
- local Screen = {}
- setmetatable(Screen, { __index = Component })
- function Screen:new(width, height, backgroundColor)
- local obj = Component.new(self, "Screen", 1, 1, width, height)
- obj.backgroundColor = backgroundColor
- gpu.setResolution(width, height)
- gpu.setBackground(backgroundColor)
- gpu.fill(obj.x, obj.y, width, height, " ")
- return obj
- end
- function Screen:init()
- table.sort(self.componentsPrioritized, function(a, b)
- return a.renderPriority < b.renderPriority
- end)
- for _, component in pairs(self.components) do
- component:init()
- end
- event.listen("touch", function(_, _, x, y, _, _)
- for _, component in pairs(self.componentsPrioritized) do
- if component.component:isTouched(x, y) then
- component.component:onTouch(x, y)
- else
- component.component:notTouch(x, y)
- end
- end
- end)
- end
- function Screen:addComponent(component, renderPriority)
- component:setParent(self)
- self.components[component.name] = component
- table.insert(self.componentsPrioritized, { renderPriority = renderPriority, component = component })
- end
- function Screen:getComponent(componentName)
- return self.components[componentName]
- end
- function Screen:render()
- for _, component in pairs(self.components) do
- component:render()
- end
- self.gpu.setBackground(COLORS.black)
- self.gpu.setForeground(COLORS.white)
- end
- local Section = {}
- setmetatable(Section, { __index = Component })
- function Section:new(name, x, y, width, height, title, borderColor, titleColor, titleBackground)
- local obj = Component.new(self, name, x, y, width, height)
- obj.borderColor = borderColor
- obj.titleColor = titleColor
- obj.titleBackground = titleBackground
- obj.title = title
- obj.rerender = true
- return obj
- end
- function Section:setTitle(title, borderColor, titleColor, titleBackground)
- if title and title ~= self.title then
- self.title = title
- self.rerender = true
- end
- if borderColor and borderColor ~= self.borderColor then
- self.borderColor = borderColor
- self.rerender = true
- end
- if titleColor and titleColor ~= self.titleColor then
- self.titleColor = titleColor
- self.rerender = true
- end
- if titleBackground and titleBackground ~= self.titleBackground then
- self.titleBackground = titleBackground
- self.rerender = true
- end
- end
- function Section:render()
- if self.rerender then
- local x = self.x + self.parent.x
- local y = self.y + self.parent.y
- local xMax = self.xMax + self.parent.x
- local yMax = self.yMax + self.parent.y
- self.gpu.setBackground(self.defaultBackgroundColor)
- self.gpu.fill(x, y, self.width, self.height, " ")
- self.gpu.setBackground(self.borderColor)
- self.gpu.fill(x, y, self.width, 1, " ")
- self.gpu.fill(x, y, 2, self.height, " ")
- self.gpu.fill(x, yMax, self.width, 1, " ")
- self.gpu.fill(xMax - 1, y, 2, self.height, " ")
- self.gpu.setBackground(self.titleBackground)
- self.gpu.setForeground(self.titleColor)
- self.gpu.fill(x + 4, y, string.len(self.title) + 2, 1, " ")
- self.gpu.set(x + 5, y, self.title)
- self.rerender = false
- end
- for _, prioritized in pairs(self.componentsPrioritized) do
- prioritized.component:render()
- end
- end
- local Label = {}
- setmetatable(Label, { __index = Component })
- function Label:new(name, x, y, width, padding, text, textColor, backgroundColor)
- local obj = Component.new(self, name, x, y, width, 1)
- obj.text = text
- obj.render_text = truncateString(text, width, padding)
- obj.textColor = textColor
- obj.backgroundColor = backgroundColor
- obj.padding = padding
- obj.align = ALIGN.left
- return obj
- end
- function Label:setText(text)
- if text and text ~= self.text then
- self.text = text
- self.render_text = truncateString(self.text, self.width, self.padding)
- self.rerender = true
- end
- end
- function Label:setTextColor(textColor)
- if textColor and textColor ~= self.textColor then
- self.textColor = textColor
- self.rerender = true
- end
- end
- function Label:setBackgroundColor(backgroundColor)
- if backgroundColor and backgroundColor ~= self.backgroundColor then
- self.backgroundColor = backgroundColor
- self.rerender = true
- end
- end
- function Label:setAlign(align)
- if align and align ~= self.align then
- self.align = align
- self.rerender = true
- end
- end
- function Label:setPadding(padding)
- if padding and padding ~= self.padding then
- self.padding = padding
- self.render_text = truncateString(self.text, self.width, self.padding)
- self.rerender = true
- end
- end
- function Label:render()
- if self.rerender then
- local absParentX, absParentY = self.parent:getAbsXY()
- local x = self.x + absParentX
- local y = self.y + absParentY
- self.gpu.setBackground(self.defaultBackgroundColor)
- self.gpu.fill(x, y, self.width, 1, " ")
- if not self.isHidden then
- self.gpu.setBackground(self.backgroundColor)
- self.gpu.setForeground(self.textColor)
- self.gpu.fill(x, y, self.width, 1, " ")
- local render_text = addFillersOnString(self.render_text, self.align, " ",
- self.width - string.len(self.render_text) - self.padding * 2)
- render_text = addPaddingOnString(render_text, self.padding, " ")
- self.gpu.set(x, y, render_text)
- end
- self.rerender = false
- end
- for _, prioritized in pairs(self.componentsPrioritized) do
- prioritized.component:render()
- end
- end
- local Button = {}
- setmetatable(Button, { __index = Component })
- function Button:new(name, x, y, width, height, text, textColor, backgroundColor)
- local obj = Component.new(self, name, x, y, width, height)
- obj.text = text
- obj.textColor = textColor
- obj.backgroundColor = backgroundColor
- return obj
- end
- function Button:setText(text)
- if text and text ~= self.text then
- self.text = text
- self.rerender = true
- end
- end
- function Button:setTextColor(textColor)
- if textColor and textColor ~= self.textColor then
- self.textColor = textColor
- self.rerender = true
- end
- end
- function Button:setBackgroundColor(backgroundColor)
- if backgroundColor and backgroundColor ~= self.backgroundColor then
- self.backgroundColor = backgroundColor
- self.rerender = true
- end
- end
- function Button:render()
- if self.rerender then
- local absParentX, absParentY = self.parent:getAbsXY()
- local x = self.x + absParentX
- local y = self.y + absParentY
- self.gpu.setBackground(self.backgroundColor)
- self.gpu.fill(x, y, self.width, self.height, " ")
- self.gpu.setForeground(self.textColor)
- local render_text = addFillersOnString(self.text, self.align, " ", self.width - string.len(self.text))
- local textX = x + math.floor((self.width - string.len(self.text)) / 2)
- local textY = y + math.floor(self.height / 2)
- self.gpu.set(textX, textY, render_text)
- self.rerender = false
- end
- for _, prioritized in pairs(self.componentsPrioritized) do
- prioritized.component:render()
- end
- end
- local ScrollBar = {}
- setmetatable(ScrollBar, { __index = Component })
- function ScrollBar:new(name, x, y, height, textColor, backgroundColor)
- local obj = Component.new(self, name, x, y, 1, height)
- obj.currentPos = 1
- obj.maxPos = 1
- obj.textColor = textColor
- obj.backgroundColor = backgroundColor
- return obj
- end
- function ScrollBar:setMaxPos(maxPos)
- if maxPos and maxPos ~= self.maxPos then
- self.maxPos = maxPos
- self.rerender = true
- end
- end
- function ScrollBar:setCurrentPos(currentPos)
- if currentPos and currentPos ~= self.currentPos then
- self.currentPos = currentPos
- self.rerender = true
- end
- end
- function ScrollBar:scrollUp()
- if self.currentPos > 1 then
- self:setCurrentPos(self.currentPos - 1)
- end
- end
- function ScrollBar:scrollDown()
- if self.currentPos < self.maxPos then
- self:setCurrentPos(self.currentPos + 1)
- end
- end
- function ScrollBar:yToBarPos(y, absParentY)
- local relativeY = y - absParentY
- local maxBarHeight = self.height - 4
- local pos = math.ceil((relativeY - 2) / maxBarHeight * self.maxPos)
- return math.min(pos, self.maxPos)
- end
- function ScrollBar:isScrollUpTouched(x, y, absParentX, absParentY)
- local xMin = self.xMin + absParentX
- local xMax = self.xMax + absParentX
- local yMin = self.yMin + absParentY
- local yMax = self.yMin + absParentY
- if x >= xMin and x <= xMax and y >= yMin and y <= yMax then
- return true
- end
- return false
- end
- function ScrollBar:isScrollDownTouched(x, y, absParentX, absParentY)
- local xMin = self.xMin + absParentX
- local xMax = self.xMax + absParentX
- local yMin = self.yMax + absParentY
- local yMax = self.yMax + absParentY
- if x >= xMin and x <= xMax and y >= yMin and y <= yMax then
- return true
- end
- return false
- end
- function ScrollBar:isBarTouched(x, y, absParentX, absParentY)
- local xMin = self.xMin + absParentX
- local xMax = self.xMax + absParentX
- local yMin = self.yMin + absParentY + 2
- local yMax = self.yMax + absParentY - 2
- if x >= xMin and x <= xMax and y >= yMin and y <= yMax then
- return true
- end
- return false
- end
- function ScrollBar:render()
- if self.rerender then
- local absParentX, absParentY = self.parent:getAbsXY()
- local x = self.x + absParentX
- local y = self.y + absParentY
- self.gpu.setBackground(self.defaultBackgroundColor)
- self.gpu.fill(x, y, self.width, self.height, " ")
- self.gpu.setBackground(self.backgroundColor)
- self.gpu.setForeground(self.textColor)
- self.gpu.set(x, y, "^")
- self.gpu.set(x, y + self.height - 1, "v")
- local barHeight = math.ceil((self.height - 4) / self.maxPos)
- local barStart = y + 2 + barHeight * (self.currentPos - 1)
- if self.currentPos == self.maxPos then
- local ajustedBarHeight = barStart + barHeight - (self.yMax + absParentY - 1)
- self.gpu.fill(x, barStart, self.width, barHeight - ajustedBarHeight, "▒")
- else
- self.gpu.fill(x, barStart, self.width, barHeight, "▒")
- end
- self.rerender = false
- end
- end
- local TextArea = {}
- setmetatable(TextArea, { __index = Component })
- function TextArea:new(name, x, y, width, height, textColor, backgroundColor)
- local obj = Component.new(self, name, x, y, width, height)
- obj.lines = {}
- obj.textColor = textColor
- obj.backgroundColor = backgroundColor
- return obj
- end
- function TextArea:setText(lines)
- self.lines = lines
- self.rerender = true
- end
- function TextArea:setTextColor(textColor)
- if textColor and textColor ~= self.textColor then
- self.textColor = textColor
- self.rerender = true
- end
- end
- function TextArea:setBackgroundColor(backgroundColor)
- if backgroundColor and backgroundColor ~= self.backgroundColor then
- self.backgroundColor = backgroundColor
- self.rerender = true
- end
- end
- function TextArea:wrapText(text)
- local lines = {}
- local currentLine = ""
- for word in text:gmatch("%S+") do
- if #currentLine + #word <= self.width then
- currentLine = currentLine .. word .. " "
- else
- table.insert(lines, currentLine)
- currentLine = word .. " "
- end
- end
- table.insert(lines, currentLine)
- return lines
- end
- function TextArea:render()
- if self.rerender then
- local absParentX, absParentY = self.parent:getAbsXY()
- local x = self.x + absParentX
- local y = self.y + absParentY
- self.gpu.setBackground(self.defaultBackgroundColor)
- self.gpu.fill(x, y, self.width, self.height, " ")
- self.gpu.setBackground(self.backgroundColor)
- self.gpu.setForeground(self.textColor)
- for i, line in pairs(self.lines) do
- self.gpu.set(x, y + i - 1, line)
- end
- self.rerender = false
- end
- end
- local List = {}
- setmetatable(List, { __index = Component })
- function List:new(name, x, y, width, height, maxItemsSize, textColor, backgroundColor)
- local obj = Component.new(self, name, x, y, width, height)
- obj.textList = {}
- obj.maxItemsSize = maxItemsSize
- obj.textColor = textColor
- obj.backgroundColor = backgroundColor
- obj.padding = 2
- obj.align = ALIGN.left
- for i = 1, maxItemsSize do
- local label = Label:new(name .. "_label_" .. i, 0, 0 + (i - 1) * 2, width, obj.padding, "", textColor,
- backgroundColor)
- label.idx = i
- label:hide()
- obj:addComponent(label, i)
- end
- return obj
- end
- function List:setTextList(textList)
- self.textList = textList
- for _, prioritizedComponent in pairs(self.componentsPrioritized) do
- local label = prioritizedComponent.component
- if textList[label.idx] then
- label:setText(textList[label.idx])
- label:show()
- else
- label:hide()
- label.setText("")
- end
- end
- end
- function List:setTextColor(textColor)
- if textColor and textColor ~= self.textColor then
- for _, component in pairs(self.components) do
- component:setTextColor(textColor)
- end
- self.rerender = true
- end
- end
- function List:setBackgroundColor(backgroundColor)
- if backgroundColor and backgroundColor ~= self.backgroundColor then
- for _, component in pairs(self.components) do
- component:setBackgroundColor(backgroundColor)
- end
- self.rerender = true
- end
- end
- function List:setAlign(align)
- if align and align ~= self.align then
- for _, component in pairs(self.components) do
- component:setAlign(align)
- end
- self.rerender = true
- end
- end
- function List:setPadding(padding)
- if padding and padding ~= self.padding then
- for _, component in pairs(self.components) do
- component:setPadding(padding)
- end
- self.rerender = true
- end
- end
- function List:setOnTouch(callback)
- for _, component in pairs(self.components) do
- component:setOnTouch(callback)
- end
- end
- function List:addComponent(component, renderPriority)
- if getmetatable(component) ~= Label then
- return
- end
- component:setParent(self)
- self.components[component.name] = component
- table.insert(self.componentsPrioritized, { renderPriority = renderPriority, component = component })
- end
- function List:render()
- for _, component in pairs(self.components) do
- component:render()
- end
- end
- local InputBox = {}
- setmetatable(InputBox, { __index = Component })
- function InputBox:new(name, x, y, width, textColor, backgroundColor)
- local obj = Component.new(self, name, x, y, width, 1)
- obj.text = ""
- obj.focus = false
- obj.textColor = textColor
- obj.onChangeDelayTime = 0.5
- obj.onChangeCallback = nil
- obj.currCursor = "_"
- obj.cursorBlinkEventId = nil
- obj.countDownLatch = CountDownLatch:new(0)
- obj.backgroundColor = backgroundColor
- return obj
- end
- function InputBox:setText(text)
- if text and text ~= self.text then
- self.text = text
- self.rerender = true
- end
- end
- function InputBox:registerKeyDown()
- keyboardEvent.register("inputBox_" .. self.name,
- function(eventName, addr, char, key, player)
- return self.focus
- end,
- function(eventName, addr, char, key, player)
- if char ~= 0 and key ~= 14 and string.len(self.text) < self.width - 1 then
- self:setText(self.text .. string.char(char))
- self:onChangeDelay()
- elseif key == 14 and string.len(self.text) > 0 then
- self:setText(string.sub(self.text, 1, -2))
- self:onChangeDelay()
- end
- end)
- end
- function InputBox:isTouched(x, y)
- local absParentX, absParentY = self.parent:getAbsXY()
- local xMix = self.xMin + absParentX
- local xMax = self.xMax + absParentX
- local yMin = self.yMin + absParentY
- local yMax = self.yMax + absParentY
- if x >= xMix and x <= xMax and y >= yMin and y <= yMax then
- self.focus = true
- self:cursorBlink()
- return true
- end
- self.focus = false
- self:cursorBlink()
- self.rerender = true
- return false
- end
- function InputBox:cursorBlink()
- if self.focus and self.cursorBlinkEventId == nil then
- local absParentX, absParentY = self.parent:getAbsXY()
- local x = self.x + absParentX
- local y = self.y + absParentY
- self.cursorBlinkEventId = event.timer(0.5, function()
- local currbg = self.gpu.getBackground()
- if (self.currCursorChar == "_") then
- self.gpu.setBackground(self.textColor)
- self.currCursorChar = " "
- else
- self.gpu.setBackground(self.backgroundColor)
- self.currCursorChar = "_"
- end
- self.gpu.set(x + string.len(self.text), y, self.currCursorChar)
- self.gpu.setBackground(currbg)
- end, math.huge)
- elseif not self.focus and self.cursorBlinkEventId ~= nil then
- event.cancel(self.cursorBlinkEventId)
- self.cursorBlinkEventId = nil
- self.rerender = true
- end
- end
- function InputBox:onChangeDelay()
- self.countDownLatch:countUp()
- event.timer(self.onChangeDelayTime, function(...)
- self.countDownLatch:countDown()
- if self.countDownLatch:isZero() then
- self:onChangeCallBack(self)
- end
- end, 1)
- end
- function InputBox:setOnchangeDelayTime(time)
- self.onChangeDelayTime = time
- end
- function InputBox:setOnChange(callback)
- self.onChangeCallBack = callback
- end
- function InputBox:render()
- local absParentX, absParentY, x, y = nil, nil, nil, nil
- if self.rerender then
- if absParentX == nil or absParentY == nil then
- local absParentX, absParentY = self.parent:getAbsXY()
- x = self.x + absParentX
- y = self.y + absParentY
- end
- self.gpu.setBackground(self.defaultBackgroundColor)
- self.gpu.fill(x, y, self.width, 1, " ")
- self.gpu.setBackground(self.backgroundColor)
- self.gpu.setForeground(self.textColor)
- self.gpu.fill(x, y, self.width, 1, "_")
- self.gpu.set(x, y, self.text)
- if self.focus then
- local currbg = self.gpu.getBackground()
- self.gpu.setBackground(self.textColor)
- self.currCursorChar = " "
- self.gpu.set(x + string.len(self.text), y, " ")
- self.gpu.setBackground(currbg)
- end
- self.rerender = false
- end
- for _, prioritized in pairs(self.componentsPrioritized) do
- prioritized.component:render()
- end
- end
- local Fill = {}
- setmetatable(Fill, { __index = Component })
- function Fill:new(name, x, y, width, height, char, color)
- local obj = Component.new(self, name, x, y, width, height)
- obj.char = char
- obj.color = color
- return obj
- end
- function Fill:setColor(color)
- if color and color ~= self.color then
- self.color = color
- self.rerender = true
- end
- end
- function Fill:setChar(char)
- if char and char ~= self.char then
- self.char = char
- self.rerender = true
- end
- end
- function Fill:render()
- if self.rerender then
- local absParentX, absParentY = self.parent:getAbsXY()
- local x = self.x + absParentX
- local y = self.y + absParentY
- self.gpu.setBackground(self.defaultBackgroundColor)
- self.gpu.setForeground(self.color)
- self.gpu.fill(x, y, self.width, self.height, self.char)
- self.rerender = false
- end
- for _, prioritized in pairs(self.componentsPrioritized) do
- prioritized.component:render()
- end
- end
- return {
- Screen = Screen,
- Section = Section,
- Label = Label,
- Button = Button,
- ScrollBar = ScrollBar,
- TextArea = TextArea,
- List = List,
- COLORS = COLORS,
- InputBox = InputBox,
- Fill = Fill,
- debug = debug
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement