Skip to content

Instantly share code, notes, and snippets.

@skyem123
Last active August 29, 2015 14:16
Show Gist options
  • Save skyem123/529593c86a36f1d38a83 to your computer and use it in GitHub Desktop.
Save skyem123/529593c86a36f1d38a83 to your computer and use it in GitHub Desktop.
skyem's GUI and Integrated Circuits editor for OpenComputers for Minecraft
-- GUILIB INDEV by Skye Macdonald
-- CC0 Licence
-- For debug purposes
local serialization = require("serialization")
local component = require("component")
local colour = require("colors")
local gpu = component.gpu
local unicode = require("unicode")
local event = require("event")
local guiLib = {math = { ceil = math.ceil, floor = math.floor, max = math.max, min = math.min}}
function guiLib.math.round(num, idp)
local mult = 10^(idp or 0)
return guiLib.math.floor(num * mult + 0.5) / mult
end
guiLib.colour = {
white = 0xFFFFFF,
orange = 0xFFA500,
magenta = 0xFF00FF,
lightblue = 0xADD8E6,
yellow = 0xFFFF00,
lime = 0x00FF00,
pink = 0xFFC0CB,
gray = 0x808080,
silver = 0xC0C0C0,
cyan = 0x00FFFF,
purple = 0x800080,
blue = 0x0000FF,
brown = 0xA52A2A,
green = 0x008000,
red = 0xFF0000,
black = 0x000000,
}
do
local keys = {}
for k in pairs(guiLib.colour) do
table.insert(keys, k)
end
for _, k in pairs(keys) do
guiLib.colour[guiLib.colour[k]] = k
end
end
guiLib.colour["grey"] = guiLib.colour["gray"]
guiLib.color = guiLib.colour
function guiLib.colourFormatConvTo(colourFrom)
checkArg(1, colourFrom, "number", "string")
if type(colourFrom) == "number" then
return guiLib.colour[colour[colourFrom]]
else
return guiLib.colour[colourFrom]
end
end
function guiLib.colourFormatConvFrom(colourFrom)
checkArg(1, colourFrom, "number", "string")
if type(colourFrom) == "number" then
return colour[guiLib.colour[colourFrom]]
else
return guiLib.colour[colourFrom]
end
end
guiLib.colorFormatConvTo = guiLib.colourFormatConvTo
guiLib.colorFormatConvFrom = guiLib.colourFormatConvFrom
function guiLib.getSize() return gpu.getResolution() end
-- Puts a character (or a string) onto the screen, (x: number, y: number, value: string[, vertical:boolean, foreColor: number, backColor: number]): boolean
function guiLib.putChar(x,y, character, ...)
print("putChar", x, y, character, ...)
checkArg(1, x, "number")
checkArg(2, y, "number")
checkArg(3, character, "string")
local extraParams = {...}
local extraParamNum = 1
local vertical = false
if type(extraParams[extraParamNum]) == "boolean" then
vertical = extraParams[extraParamNum]
extraParamNum = extraParamNum + 1
end
local foreColour, backColour -- just to let Lua know they exist
local colourChange = false
if type(extraParams[extraParamNum]) == "number" then
checkArg(extraParamNum + 3, extraParams[extraParamNum], "number")
foreColour, backColour = extraParams[extraParamNum], extraParams[extraParamNum + 1]
extraParamNum = extraParamNum + 2
colourChange = true
else checkArg(extraParamNum + 4, extraParams[extraParamNum], "number", "nil") end
local currentForeColour, currentForeIndex = false, false
local currentBackColour, currentBackIndex = false, false
if colourChange then -- Set the colour of the char that we are going to set
currentForeColour, currentForeIndex = gpu.setForeground(foreColour)
currentBackColour, currentBackIndex = gpu.setBackground(backColour)
end
-- Set the char(s)
assert(gpu.set(x, y, character, vertical), "Unable to set character(s) at (" .. x,y .. ")")
if colourChange then -- Revert the colour
gpu.setForeground(currentForeIndex or currentForeColour, not not currentForeIndex)
gpu.setBackground(currentBackIndex or currentBackColour, not not currentBackIndex)
end
end
-- Draws a line given a start and end point
function guiLib.drawLine(x1,y1, x2,y2, ...)
checkArg(1, x1, "number")
checkArg(2, y1, "number")
checkArg(3, x2, "number")
checkArg(4, y2, "number")
local extraParams = {...}
local extraParamNum = 1
local foreColour, backColour
local charIter
if type(extraParams[extraParamNum]) == "number" then -- We have colour choices
checkArg(5 + extraParamNum, extraParams[extraParamNum], "number")
foreColour, backColour = extraParams[extraParamNum], extraParams[extraParamNum + 1]
extraParamNum = extraParamNum + 2
elseif type(extraParams[extraParamNum]) == "function" then -- We have a function that will pick colours AND the character
charIter = extraParams[extraParamNum]
extraParamNum = extraParamNum + 1
end
local character = unicode.char(0x2588)
if type(extraParams[extraParamNum]) == "string" and not charIter then
character = extraParams[extraParamNum]
extraParamNum = extraParamNum + 1
end
if x1 > x2 then x1, x2 = x2, x1 end
if y1 > y2 then y1, y2 = y2, y1 end
-- Is it a simple (non diagonal) line?
if y1 == y2 then -- This is a horizontal line
if charIter then for character, foreColor, backColor in charIter(x1,x2, y1,y2) do guiLib.putChar(x1, y1, unicode.sub(character:rep(math.ceil((x2 - x1) / unicode.len(character))), 1, (x2 + 1) - x1), false, foreColour, backColour) end
else guiLib.putChar(x1, y1, unicode.sub(character:rep(math.ceil((x2 - x1) / unicode.len(character))), 1, (x2 + 1) - x1), false, foreColour, backColour) end
elseif x1 == x2 then -- This is a vertical line
if charIter then for character, foreColor, backColor in charIter(x1,x2, y1,y2) do guiLib.putChar(x1, y1, unicode.sub(character:rep(math.ceil((x2 - x1) / unicode.len(character))), 1, (x2 + 1) - x1), false, foreColour, backColour) end
else guiLib.putChar(x1, y1, unicode.sub(character:rep(math.ceil((y2 - y1) / unicode.len(character))), 1, (y2 + 1) - y1), true, foreColour, backColour) end
else error("Diagonal line drawing is not yet implemented because skyem is too dumb.", 1)
end
end
local guiObj = {}
function guiObj:init()
self.registeredClicks = {}
self.eventTable = {}
self.quit = false
end
function guiObj:new(object, ...)
object = object or {} -- create object if user does not provide one
setmetatable(object, self)
self.__index = self
object:init(...)
return object
end
function guiObj:drawAll() end
function guiObj:registerClick(x1,y1, x2,y2, callback, clickType)
clickType = clickType or "single"
table.insert(self.registeredClicks, {x1 = x1, x2 = x2, y1 = y1, y2 = y2, clickType = clickType, callback = callback})
end
function guiObj:eventListen(name, callback)
-- Create an callback table if it doesn't exist for this event
if self.eventTable[name] == nil then self.eventTable[name] = {} end
-- Add the callback the the end of the callback table for this event
table.insert(self.eventTable[name], callback)
-- Return if it was inserted into the event table
return self.eventTable[name][#self.eventTable[name]] and true
end
function guiObj:eventIgnore(name, callback)
-- If the name for the event doesn't exit, we can do nothing
if self.eventTable[name] == nil then return end
for i = 1, #self.eventTable[name] do
if self.eventTable[name][i] == callback then
table.remove(self.eventTable[name], i)
if #self.eventTable[name] == 0 then
self.eventTable[name] = nil
end
return true
end
end
-- Return if it was inserted into the event table
return self.eventTable[name][#self.eventTable[name]] and true
end
-- DO NOT CALL DIRECTLY!
-- This may crash and leave unwanted stuff on your system
function guiObj:unsafeLoop()
local function singleClick(_, _, x,y, _, _)
for _, click in ipairs(self.registeredClicks) do
--print(x,click.x1,click.x2, y,click.y1,click.y2)
if click.clickType == "single" then
-- Do a bounds check
--print(serialization.serialize(click, true), "\n", x,y)
if click.x1 <= x and click.x2 >= x and click.y1 <= y and click.y2 >= y then
click.callback(x - click.x1, y - click.y1) -- If it's within the bounds of the button, call it.
end
end
end
end
self:eventListen("touch", singleClick)
while not self.quit do -- Event loop
-- Pull and event, wait forever for it if there is no sleepTime set (nil means wait forever)
gotEvent = table.pack(event.pull(sleepTime))
-- Loop through the events registered
for event, callbacks in pairs(self.eventTable) do
-- If the event we got is registered
if gotEvent[1] == event then
for _, callback in ipairs(callbacks) do
callback(table.unpack(gotEvent))
end
end
end
end
end
-- Safe loop that cleans up after the unsafe loop
function guiObj:mainLoop(...)
-- Safely call the main loop
retVal = table.pack(pcall(self.unsafeLoop, self, ...))
local success = retVal[1]
-- If there is an error, log it to stderr
if not success then io.stderr:write("Error: " .. tostring(retVal[2]) .. "\n" .. debug.traceback() .. "\n") end
-- Return the return value and error
return table.unpack(retVal)
end
function guiObj:placeComponent(item, foreColour, backColour)
local width, height = guiLib.getSize()
-- TODO: Make error more informative
if not (item.relX and item.relY) then error("No position for item!",1) end
local x, y, item_height, item_width
if item.relX == 0 then x = 1 else x = guiLib.math.round(width * item.relX) end
if item.relY == 0 then y = 1 else y = guiLib.math.round(height * item.relY) end
if x == 0 then x = 1 end
if y == 0 then y = 1 end
if item.height then item_height = item.height else item_height = 1 end
if item.width then item_width = item.width else item_width = 1 end
print(x,y)
if item.itemType == "text" then
local length = item.length or unicode.len(item.text)
if item.alignment == "centre" then
x = x - guiLib.math.floor(length / 2)
end
guiLib.putChar(x, y, item.text, item.foreColour or foreColour, item.backColour or backColour)
end
if item.itemType == "button" then
guiLib.putChar(x, y, item.text, item.foreColour or foreColour, item.backColour or backColour)
local length = item.length or unicode.len(item.text)
self:registerClick(x,y, x + length - 1, y + (item.height or 1), item.action)
end
end
-- (Sets and) draws the title bar
function guiObj:drawTitleBar(setTitle)
if setTitle then self.title = setTitle end
if self.title == nil then return end -- Exit if there is no title bar set
local width, _ = guiLib.getSize()
guiLib.drawLine(1,1, width +1 ,1, self.title.backColour, self.title.backColour)
local position = 1
for key, item in ipairs(self.title) do
item.relY = 0
item.height = 1
self:placeComponent(item, self.title.foreColour, self.title.backColour)
end
end
function guiLib.getObject(...)
return guiObj:new({}, ...)
end
return guiLib
local term = require("term")
term.clear()
print()
local serialization = require("serialization")
require("package").loaded.guiLib = nil
local guiLib = require("guiLib")
local guiObj = guiLib.getObject()
local colour = guiLib.colour
local unicode = require("unicode")
print(serialization.serialize(guiObj, true))
print(serialization.serialize(guiObj.__index, true))
print(serialization.serialize(guiLib, true))
--guiLib.putChar(1,2, "H")
--guiLib.putChar(2,2, "e", colour.white, colour.red)
--guiLib.putChar(3,2, unicode.char(0x2588), colour.red, colour.white)
--guiLib.putChar(1,3, "Hello!", colour.white, colour.red)
--guiLib.drawLine(1,5, 10,5)
--guiLib.drawLine(1,7, 1,10, colour.red, colour.white)
--guiLib.drawLine(3,7, 3,10, colour.red, colour.white, "Hi ")
print(pcall(guiLib.drawLine, 40,1, 20,30))
guiObj:drawTitleBar({
{itemType = "button", text = "X", foreColour = colour.white, backColour = colour.red, relX = 1, action = function() guiObj.quit = true end },
{itemType = "text", text = "ICs OC 0.1", relX = 0.5, alignment = "centre"},
foreColour = colour.white, backColour = colour.blue})
guiObj:placeComponent({itemType = "button", text = "Hello!", foreColour = colour.white, backColour = colour.red, relX = 0.5, relY = 0.5, action = function() print("Hello, world!") end }, colour.blue, colour.white)
guiObj:placeComponent({itemType = "text", text = "I am some text.", foreColour = colour.black, backColour = colour.white, relX = 0.7, relY = 0.7}, colour.blue, colour.white)
guiLib.putChar(50, 25, "a")
print(guiObj.mainLoop(guiObj))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment