Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- UI Library by 1lann
- -- Some asynchronous operators
- local timeouts = {}
- local oldPullEvent = os.pullEvent
- function os.pullEvent(...)
- while true do
- local eventData = {oldPullEvent(...)}
- if eventData[1] == "timer" and timeouts[eventData[2]] then
- timeouts[eventData[2]]()
- else
- return unpack(eventData)
- end
- end
- end
- function setTimeout(func, time)
- local id = os.startTimer(time)
- timeouts[id] = func
- end
- function clearTimeout()
- timeouts[id] = nil
- end
- -- Coordinate manipulation
- Coord = {}
- Coord.__index = Coord
- Coord.w, Coord.h = term.getSize()
- function Coord.new(x, y)
- if type(x) ~= "number" then
- return error("ui: number expected for x value, got " .. type(x))
- end
- if type(y) ~= "number" then
- return error("ui: number expected for y value, got " .. type(y))
- end
- local self = {}
- setmetatable(self, Coord)
- self.x = x
- self.y = y
- return self
- end
- function Coord:unpack()
- return self.x, self.y
- end
- function Coord:setCursorPos()
- return term.setCursorPos(self.x, self.y)
- end
- function Coord:setPosition(x, y)
- if type(x) ~= "number" then
- return error("ui: number expected for x value, got " .. type(x))
- end
- if type(y) ~= "number" then
- return error("ui: number expected for y value, got " .. type(y))
- end
- self.x = x
- self.y = y
- end
- function Coord.assert(coord)
- if type(coord) ~= "table" then
- return error("ui: coordinate expected, got " .. type(coord), 2)
- end
- if getmetatable(coord) ~= Coord then
- if #coord == 2 then
- if type(coord[1]) ~= "number" or type(coord[2]) ~= "number" then
- return error("ui: coordinate expected, got unknown value", 2)
- end
- return Coord.new(unpack(coord))
- end
- if coord.type then
- return error("ui: coordinate expected, got " .. coord.type, 2)
- else
- return error("ui: coordinate expected, got unknown value", 2)
- end
- end
- return coord
- end
- function Coord.__add(a, b)
- local a = Coord.assert(a)
- local b = Coord.assert(b)
- return Coord.new(a.x + b.x, a.y + b.y)
- end
- function Coord.__tostring(a)
- return "(" .. tostring(a.x) .. ", " .. tostring(a.y) .. ")"
- end
- -- Renderer
- Renderer = {}
- Renderer.__index = Renderer
- Renderer.incrementer = 0
- Renderer.forceRenderEventPrefix = "ui_renderer_force_update_"
- Renderer.stopExecution = "ui_renderer_stop_execution"
- function Renderer.new()
- local self = {}
- setmetatable(self, Renderer)
- self.tree = {}
- self.position = Coord.new(1, 1)
- self.width, self.height = term.getSize()
- self.defaultBgColor = colors.black
- self.defaultTextColor = colors.white
- self.scrollable = false
- self.scrollableHeight = self.height
- self.scrollPosition = 0
- self.id = Renderer.uid()
- self.window = nil -- TODO
- self.attr = {}
- self.forceRenderEvent = Renderer.forceRenderEventPrefix .. self.id
- return self
- end
- function Renderer:setSize(width, height)
- if type(width) ~= "number" then
- return error("ui: number expected for width, got " .. type(width))
- end
- if type(height) ~= "number" then
- return error("ui: number expected for height, got " .. type(height))
- end
- self.width = width
- self.height = height
- end
- function Renderer.setPosition(position)
- self.position = Coord.assert(position)
- end
- function Renderer:add(child)
- table.insert(self.tree, child)
- end
- function Renderer:setDefaultBackgroundColor(color)
- if type(color) ~= "number" then
- return error("ui: number expected for color, got " .. type(color))
- end
- self.defaultBgColor = color
- end
- function Renderer:setDefaultTextColor(color)
- if type(color) ~= "number" then
- return error("ui: number expected for color, got " .. type(color))
- end
- self.defaultTextColor = color
- end
- function Renderer:prepareRender()
- end
- function Renderer:render()
- local layers = {}
- local sortedLayers = {}
- for k, child in pairs(self.tree) do
- if not layers[child.layer] then
- layers[child.layer] = {}
- table.insert(sortedLayers, child.layer)
- end
- table.insert(layers[child.layer], child)
- end
- table.sort(sortedLayers, function(a, b) return a > b end)
- term.setBackgroundColor(self.defaultBgColor)
- term.clear()
- for _, layer in pairs(sortedLayers) do
- for _, child in pairs(layers[layer]) do
- if not child.draw then
- return error("ui: missing draw function on renderer child element")
- end
- term.setBackgroundColor(self.defaultBgColor)
- term.setTextColor(self.defaultTextColor)
- child:draw({self.position.x, self.position.y - self.scrollPosition})
- end
- end
- end
- function Renderer:setScrollHeight(height)
- if type(height) ~= "number" then
- return error("ui: number expected for height, got " .. type(height))
- end
- self.scrollableHeight = height
- end
- function Renderer:setScrollable(scrollable)
- if type(scrollable) ~= "boolean" then
- return error("ui: boolean expected for scrollable, got " ..
- type(scrollable))
- end
- self.scrollable = scrollable
- end
- function Renderer:forceRender()
- os.queueEvent(self.forceRenderEvent)
- end
- function Renderer:execute()
- self:prepareRender()
- local shouldRender = true
- while true do
- if shouldRender then
- self:render()
- end
- shouldRender = false
- local event, p1, p2, p3, p4, p5 = os.pullEvent()
- if event == self.forceRenderEvent then
- shouldRender = true
- elseif event == "mouse_click" then
- local clickCoords = Coord.new(p2, p3)
- for _, child in pairs(self.tree) do
- if child.click then
- shouldRender = true
- local resp = child:click(clickCoords + {0, self.scrollPosition})
- if type(resp) == "function" then
- return resp()
- elseif type(resp) == "string" and
- resp == Renderer.stopExecution then
- return
- end
- end
- end
- elseif event == "key" then
- for _, child in pairs(self.tree) do
- if child.key then
- shouldRender = true
- local resp = child:key(p1)
- if type(resp) == "function" then
- return resp()
- elseif type(resp) == "string" and
- resp == Renderer.stopExecution then
- return
- end
- end
- end
- elseif event == "char" then
- for _, child in pairs(self.tree) do
- if child.char then
- shouldRender = true
- local resp = child:char(p1)
- if type(resp) == "function" then
- return resp()
- elseif type(resp) == "string" and
- resp == Renderer.stopExecution then
- return
- end
- end
- end
- elseif event == "mouse_scroll" and self.scrollable then
- self.scrollPosition = self.scrollPosition + p1
- if self.scrollPosition > self.scrollableHeight - self.height then
- self.scrollPosition = self.scrollableHeight - self.height
- elseif self.scrollPosition < 0 then
- self.scrollPosition = 0
- end
- shouldRender = true
- end
- end
- end
- function Renderer.uid()
- Renderer.incrementer = Renderer.incrementer + 1
- return Renderer.incrementer
- end
- -- Contents
- Content = {}
- Content.__index = Content
- function Content.new(width, height, initial)
- if type(width) ~= "number" then
- return error("ui: number expected for width, got " .. type(height))
- end
- if type(height) ~= "number" then
- return error("ui: number expected for height, got " .. type(width))
- end
- local self = {}
- setmetatable(self, Content)
- self.width = width
- self.height = height
- self.layer = 0
- if initial then
- if type(initial) ~= "table" then
- return error("ui: instruction list expected, got " .. type(initial))
- end
- self.instructions = initial
- else
- self.instructions = {}
- end
- return self
- end
- function Content:setLayer(layer)
- if type(layer) ~= "number" then
- return error("ui: number expected for layer, got " .. type(layer))
- end
- self.layer = layer
- end
- function Content:setAttr(key, value)
- self.attr[key] = value
- end
- function Content:getAttr(key)
- return self.attr[key]
- end
- function Content:clear()
- self.instructions = {}
- end
- function Content:addLine(text, position)
- if type(text) ~= "string" then
- return error("ui: string expected for text, got " .. type(text))
- end
- if type(position) ~= "string" then
- return error("ui: string expected for position, got " .. type(position))
- end
- if position == "left" then
- table.insert(self.instructions, {
- ["action"] = "text",
- ["text"] = text
- })
- self:newLine()
- elseif position == "right" then
- self:setXPosition(width - #text)
- table.insert(self.instructions, {
- ["action"] = "text",
- ["text"] = text
- })
- self:newLine()
- elseif position == "center" then
- self:setXPosition(math.ceil((self.width / 2) - (#text / 2)))
- table.insert(self.instructions, {
- ["action"] = "text",
- ["text"] = text
- })
- self:newLine()
- else
- return error("ui: invalid position string")
- end
- end
- function Content:newLine()
- table.insert(self.instructions, {
- ["action"] = "position",
- ["positionType"] = "newline"
- })
- end
- function Content:setPosition(coord)
- local coord = Coord.assert(coord)
- table.insert(self.instructions, {
- ["action"] = "position",
- ["positionType"] = "manual",
- ["position"] = coord
- })
- end
- function Content:setXPosition(x)
- if type(x) ~= "number" then
- return error("ui: number expected for x value, got " .. type(x))
- end
- table.insert(self.instructions, {
- ["action"] = "position",
- ["positionType"] = "x",
- ["x"] = x
- })
- end
- function Content:setTextColor(color)
- if type(color) ~= "number" then
- return error("ui: number expected for color, got " .. type(color))
- end
- table.insert(self.instructions, {
- ["action"] = "textColor",
- ["color"] = color
- })
- end
- function Content:setBackgroundColor(color)
- if type(color) ~= "number" then
- return error("ui: number expected for color, got " .. type(color))
- end
- table.insert(self.instructions, {
- ["action"] = "bgColor",
- ["color"] = color
- })
- end
- function Content:write(...)
- local args = {...}
- local text = ""
- for k, v in pairs(args) do
- text = text .. tostring(v)
- end
- table.insert(self.instructions, {
- ["action"] = "text",
- ["text"] = text
- })
- end
- function Content:append(instructions)
- if type(instructions) ~= "table" then
- return error("ui: instruction list expected, got " ..
- type(instructions))
- end
- for k, v in pairs(instructions) do
- table.insert(self.instructions, v)
- end
- end
- function Content:draw(origin)
- local origin = Coord.assert(origin)
- origin:setCursorPos()
- local currentLine = 0
- for k, v in pairs(self.instructions) do
- if type(v) ~= "table" then
- return error("ui: instruction expected, got " .. type(v))
- end
- if type(v.action) ~= "string" then
- return error("ui: string expected for action, got " ..
- type(v.action))
- end
- if v.action == "position" then
- if v.positionType == "manual" then
- local position = Coord.assert(v.position)
- local absolutePos = origin + position
- absolutePos:setCursorPos()
- currentLine = absolutePos.y
- elseif v.positionType == "x" then
- local absolutePos = origin + {v.x, currentLine}
- absolutePos:setCursorPos()
- elseif v.positionType == "newline" then
- positionImmunity = false
- currentLine = currentLine + 1
- local absolutePos = (origin + {0, currentLine})
- absolutePos:setCursorPos()
- else
- return error("ui: unknown position type: " .. v.positionType)
- end
- elseif v.action == "textColor" then
- term.setTextColor(v.color)
- elseif v.action == "bgColor" then
- term.setBackgroundColor(v.color)
- elseif v.action == "text" and not positionImmunity then
- local x = term.getCursorPos()
- if #v.text + x > origin.x + self.width then
- if origin.x + self.width - x > 0 then
- local text = v.text:sub(origin.x + self.width - x)
- term.write(text)
- end
- else
- term.write(v.text)
- end
- else
- return error("ui: unknown action type: " .. v.action)
- end
- end
- end
- -- Button
- Button = {}
- Button.__index = Button
- function Button.new(onclick)
- local self = {}
- setmetatable(self, Button)
- self.height = 0
- self.width = 0
- self.position = Coord.new(0, 0)
- self.content = Content.new(0, 0)
- self.transparent = false
- self.color = colors.white
- self.enterAsClick = false
- self.attr = {}
- self.layer = 0
- if onclick then
- if type(onclick) == "function" then
- self.onclick = onclick
- else
- return error("ui: function expected for onclick, got " ..
- type(onclick))
- end
- else
- self.onclick = function() end
- end
- return self
- end
- function Button:setSize(width, height)
- if type(width) ~= "number" then
- return error("ui: number expected for width, got " .. type(width))
- end
- if type(height) ~= "number" then
- return error("ui: number expected for height, got " .. type(height))
- end
- self.width = width
- self.height = height
- self.content = Content.new(self.width, self.height,
- self.content.instructions)
- end
- function Button:setOnClick(onclick)
- if type(onclick) ~= "function" then
- return error("ui: function expected for onclick, got " .. type(onclick))
- end
- self.onclick = onclick
- end
- function Button:setLayer(layer)
- if type(layer) ~= "number" then
- return error("ui: number expected for layer, got " .. type(layer))
- end
- self.layer = layer
- end
- function Button:setColor(color)
- if type(color) ~= "number" then
- return error("ui: number expected for color, got " .. type(color))
- end
- self.color = color
- end
- function Button:setPosition(position)
- self.position = Coord.assert(position)
- end
- function Button:getContent()
- return self.content
- end
- function Button:setEnterAsClick(enterAsClick)
- if type(enterAsClick) ~= "boolean" then
- return error("ui: boolean expected, got " .. type(enterAsClick))
- end
- self.enterAsClick = enterAsClick
- end
- function Button:click(coord)
- local coord = Coord.assert(coord)
- if coord.y > self.position.y and
- coord.y <= self.position.y + self.height and
- coord.x > self.position.x and
- coord.x <= self.position.x + self.width then
- self.onclick(coord.x - self.position.x, coord.y - self.position.y)
- end
- end
- function Button:key(key)
- if key == keys.enter and self.enterAsClick then
- return self.onclick()
- end
- end
- function Button:setAttr(key, value)
- self.attr[key] = value
- end
- function Button:getAttr(key)
- return self.attr[key]
- end
- function Button:draw(origin)
- local origin = Coord.assert(origin)
- local position = origin + self.position
- term.setBackgroundColor(self.color)
- for i = 0, self.height - 1 do
- local absolutePos = position + {0, i}
- absolutePos:setCursorPos()
- term.write(string.rep(" ", self.width))
- end
- self.content:draw(position)
- end
- -- Text Field
- -- Rect
- Rect = Button
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement