Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- ######################################## --
- -- --
- -- Better UI API for ComputerCraft v3 inDev --
- -- --
- -- ######################################## --
- -- ################################################################################
- --
- -- Base functions being used for many things
- function cur(target, x, y) target.setCursorPos(x, y) end
- function color(target,fg,bg) if fg ~= 0 then target.setTextColor(fg) end if bg ~= 0 then target.setBackgroundColor(bg) end end
- function drawPixel(target, x, y, nColour) if nColour then term.setBackgroundColor( nColour ) end cur(target, x, y) v.write(' ') end
- function split(se,sep) local sep, fields = sep or ":", {} local pattern = string.format("([^%s]+)", sep) se:gsub(pattern, function(c) fields[#fields+1] = c end) return fields end
- function aRaw(text)
- return text:gsub('%%[fb][0123456789abcdef]','')
- end
- function aWrite(target, x, y, textRaw)
- if y and textRaw then
- target.setCursorPos(x, y)
- else
- textRaw = x
- end
- local cols = {}
- for n=1,16 do
- cols[string.sub( '0123456789abcdef',n,n ) ] = 2^(n-1)
- end
- local function color(t, c)
- if t == 'f' then target.setTextColor(cols[c]) end
- if t == 'b' then target.setBackgroundColor(cols[c]) end
- end
- local len = textRaw:len()
- i = 1
- while i ~= len + 1 do
- if i < len - 1 and textRaw:sub(i, len):find('%%[fb][0123456789abcdef]') == 1 then
- t, c = textRaw:sub(i, len):match('%%([fb])([0123456789abcdef])')
- color(t, c)
- i = i + 2
- else
- target.write(textRaw:sub(i,i))
- end
- i = i + 1
- end
- end
- function inTable(tbl, item)
- if type(item) == 'table' then
- for i, v in pairs(item) do for key,value in pairs(tbl) do if value == v then return i, key end end end
- elseif type(item) == 'string' then
- for key,value in pairs(tbl) do if value == item then return key end end
- end
- return 0
- end
- function unpackPat(tbl, pattern)
- local a = {}
- for i, v in ipairs(pattern) do
- a[i] = tbl[v]
- end
- return unpack(a)
- end
- function contPos(self,x,y,cont)
- local loop = true
- newx, newy = 0, 0
- while loop do
- newx = newx + self.ui[cont].x
- newy = newy + self.ui[cont].y
- if self.ui[cont].cont then
- cont = self.ui[cont].cont
- else
- loop = false
- end
- return newx, newy
- end
- end
- function pairsByKey(t, f)
- local a = {}
- for n in pairs(t) do table.insert(a, n) end
- table.sort(a, f)
- local i = 0 -- iterator variable
- local iter = function () -- iterator function
- i = i + 1
- if a[i] == nil then return nil
- else return a[i], t[a[i]]
- end
- end
- return iter
- end
- function noPat(str)
- local newStr = ''
- local function inStr(s, c)
- for i=1, s:len() do
- if s:sub(i,i) == c then return true end
- end
- return false
- end
- for i=1, str:len() do
- local char = str:sub(i,i)
- if inStr('%-+^.[]()$?', char) then
- newStr = newStr..'%'..char
- else
- newStr = newStr..char
- end
- end
- return newStr
- end
- --
- -- ################################################################################
- -- ################################################################################
- -- New UI list constructor
- --
- function new(defTag, defDis)
- local uiList = {}
- uiList.ui = {}
- uiList.set = {}
- uiList.get = {}
- uiList.utils = {}
- uiList.config = {}
- uiList.config.defDis = defDis or {term}
- uiList.config.defTag = defTag or {'all'}
- uiList.config.defFG = colors.white
- uiList.config.defBG = colors.black
- uiList.config.pattern = {} -- Defines the patterns the different element types use that can be edited (setCommonAttribute)
- uiList.config.pattern.button = {'x', 'y', 'colors', 'tag', 'display', 'text', 'enabled', 'exe', 'ret', 'visible', 'cont'}
- uiList.config.pattern.label = {'x', 'y', 'colors', 'tag', 'display', 'text', 'enabled', 'cont'}
- uiList.config.pattern.radio = {'x', 'y', 'colors', 'tag', 'display', 'text', 'enabled', 'exe', 'ret', 'visible', 'value', 'cont'}
- uiList.config.pattern.checkbox = {'x', 'y', 'colors', 'tag', 'display', 'text', 'enabled', 'exe', 'ret', 'visible', 'value', 'cont'}
- uiList.config.pattern.point = {'x', 'y', 'colors', 'text', 'enabled', 'cont'}
- uiList.config.pattern.cycler = {'x', 'y', 'colors', 'tag', 'display', 'enabled', 'exe', 'ret', 'visible', 'cont', 'values', 'loop'}
- uiList.config.pattern.progress = {'x', 'y', 'w', 'colors', 'text', 'enabled', 'cont', 'value'}
- function uiList:addPoint(key, x, y, enabled) -- Add a point used to mark position
- if not(key) or not(x) or not(y) then return false end
- self.ui[key] = {name = key, type = 'point', x = x, y = y, enabled = true}
- local temp = self.ui[key]
- function temp:setColors(fg,bg) self.colors = {fg, bg} end
- return self.ui[key]
- end
- function uiList:addLabel(key, x, y, text, tag, display)
- if not(key) or not(x) or not(y) or not(text) or self.ui[key] then return false end
- self.ui[key] = {name = key, type = 'label', x = x, y = y, text = text, tag = tag or self.config.defTag, display = display or self.config.defDis, visible = true, enabled = true}
- local temp = self.ui[key]
- function temp:setColors(fg,bg) self.colors = {fg, bg} end
- return self.ui[key]
- end
- function uiList:addButton(key, x, y, text, tag, display)
- if not(key) or not(x) or not(y) or not(text) then return false end
- self.ui[key] = {name = key, type = 'button', x = x, y = y, text = text, tag = tag or self.config.defTag, display = display or self.config.defDis, visible = true, enabled = true}
- local temp = self.ui[key]
- function temp:setColors(fg,bg) self.colors = {fg, bg} end
- function temp:setAction(act) if type(act) == 'function' then self.exe = act else self.ret = act end end
- return self.ui[key]
- end
- function uiList:addRadio(key, x, y, text, tag, display)
- if not(key) or not(x) or not(y) or not(text) then return false end
- self.ui[key] = {name = key, type = 'radio', x = x, y = y, text = text, tag = tag or self.config.defTag, display = display or self.config.defDis, visible = true, enabled = true, value = 0}
- local temp = self.ui[key]
- function temp:setColors(fg,bg) self.colors = {fg, bg} end
- function temp:setAction(act) if type(act) == 'function' then self.exe = act else self.ret = act end end
- function temp:setChar(disabled, enabled) self.custChar = {disabled,enabled} end
- return self.ui[key]
- end
- function uiList:addCheckbox(key, x, y, text, tag, display)
- if not(key) or not(x) or not(y) or not(text) then return false end
- self.ui[key] = {name = key, type = 'checkbox', x = x, y = y, text = text, tag = tag or self.config.defTag, display = display or self.config.defDis, visible = true, enabled = true, value = 0}
- local temp = self.ui[key]
- function temp:setColors(fg,bg) self.colors = {fg, bg} end
- function temp:setAction(act) if type(act) == 'function' then self.exe = act else self.ret = act end end
- function temp:setChar(disabled, enabled) self.custChar = {disabled,enabled} end
- return self.ui[key]
- end
- function uiList:addCycler(key, x, y, text, cyclers, values, tag, display)
- if not(key) or not(x) or not(y) or not(text) or not(cyclers) or not(values) then return false end
- --Parameter for values, if is string
- if type(values) == 'string' then
- local vals = values
- local param, step, fill, max
- local from, to = vals:match('([%d]+).([%d]+)')
- if vals:find(':') then
- param = vals:match(':(.+)')
- if param:find('step=') ~= nil then step = tonumber(param:match('step=(%d+)')) end
- if param:find('fill=') ~= nil then fill = param:match('fill=(.)') end
- end
- if not(step) then step = 1 end
- if from > to then step = math.abs(step)*-1 else math.abs(step) end
- values = {}
- if fill then max = math.abs(#tostring(from) - #tostring(to)) + 1 end
- for i=from, to, step do
- local len = #tostring(i)
- local str = tostring(i)
- if fill and max - len > 0 then
- table.insert(values, str..string.rep(fill, max - len))
- else
- table.insert(values, str)
- end
- end
- end
- --#####################################
- self.ui[key] = {name = key, type = 'cycler', x = x, y = y, text = text, values = values, value = 1, loop = false, tag = tag or self.config.defTag, display = display or self.config.defDis, visible = true, enabled = true, cyclers = cyclers}
- local temp = self.ui[key]
- function temp:setColors(fg,bg) self.colors = {fg, bg} end
- function temp:setAction(act) if type(act) == 'function' then self.exe = act else self.ret = act end end
- function temp:getText() return self.values[self.value] end
- return self.ui[key]
- end
- function uiList:addProgress(key, x, y, text, w, tag, display)
- if not(key) or not(x) or not(y) or not(text) or not(w) then return false end
- self.ui[key] = {name = key, type = 'progress', x = x, y = y, text = text, w = w, tag = tag or self.config.defTag, display = display or self.config.defDis, visible = true, enabled = true, value = 0}
- local temp = self.ui[key]
- function temp:setColors(fg,bg) self.colors = {fg, bg} end
- function temp:progress(val) local w = self.w self.value = math.floor(val) end
- return self.ui[key]
- end
- function uiList:del(key) self.ui[key] = nil return true end -- Working
- function uiList:draw(tags)
- local ut = self.utils
- local tags = tags or self.config.tag or self.config.defTag
- for key, value in ut.pairsByKey(self.ui) do
- local t, x, y, text, tag, display, enabled= ut.unpackPat(value,{'type', 'x', 'y', 'text', 'tag', 'display', 'enabled'})
- local visible = value.visible
- if visible == nil then
- local visible = true
- end
- local colors = value.colors
- local cont = value.cont
- local custChar = value.custChar
- local val = value.value
- local vals = value.values
- local cyclers = value.cyclers
- f, b = nil, nil
- if colors then f, b = unpack(colors) end
- if cont and self.ui[cont] then
- x = self.ui[cont].x + x
- y = self.ui[cont].y + y
- if not(b) and self.ui[cont].colors then
- b = self.ui[cont].colors[2]
- end
- enabled = self.ui[cont].enabled
- end
- fg = f or self.config.defFG
- bg = b or self.config.defBG
- if enabled and visible and t ~= 'point' and ut.inTable(tags, tag) > 0 then
- if type(text) == 'table' then
- local textMax = 0
- for i, v in ipairs(text) do
- v = ut.aRaw(v)
- if type(v) ~= 'table' and v:len() > textMax then textMax = v:len() end
- if type(v) == 'table' and v[1]:len() > textMax then textMax = v[1]:len() end
- end
- for i, v in ipairs(text) do
- if type(v) ~= 'table' and ut.aRaw(v):len() < textMax then text[i] = text[i]..string.rep(' ', textMax - ut.aRaw(v):len()) end
- end
- end
- for _, dis in pairs(display) do
- ut.color(dis, fg, bg)
- if type(text) == 'table' then
- for i, v in ipairs(text) do
- ut.color(dis, fg, bg)
- ut.cur(dis, x, y + (i - 1))
- ut.aWrite(dis,v)
- end
- else
- if t == 'progress' and value.value > 0 then
- text = value.text:sub(1,1):rep(math.floor(value.value / 100 * value.w))
- end
- if t == 'radio' or t == 'checkbox' then
- custChar = custChar or {'O','0'}
- if t == 'radio' or t == 'checkbox' then text = text:gsub('%%I%%', ut.noPat(custChar[val + 1])) end
- end
- if t == 'cycler' then
- text = text:gsub('%%D%%',ut.noPat(cyclers[1]))
- text = text:gsub('%%U%%',ut.noPat(cyclers[2]))
- text = text:gsub('%%V%%',ut.noPat(vals[val]))
- end
- ut.cur(dis, x, y)
- ut.aWrite(dis,text)
- end
- end
- end
- end
- return true
- end
- function uiList:mouse(pos, tags, display)
- local ut = self.utils
- local mx, my = unpack(pos)
- tags = tags or self.config.defTag
- disp = display or self.config.defDis
- clicked = {}
- for key, value in ut.pairsByKey(self.ui) do
- t, x, y, text, tag, display, enabled = ut.unpackPat(value,{'type', 'x', 'y', 'text', 'tag', 'display', 'enabled'})
- if ut.inTable(tags, tag) > 0 and enabled and ut.inTable({'button','radio','checkbox','cycler'}, t) > 0 then
- if type(text) == 'table' then
- for i in ipairs(text) do
- text[i] = ut.aRaw(text[i])
- end
- else
- text = ut.aRaw(text)
- end
- local cont = value.cont
- if cont and self.ui[cont] then
- x = self.ui[cont].x + x
- y = self.ui[cont].y + y
- end
- if type(text) == 'table' then
- local textMax = 0 for _, v in ipairs(text) do if type(v) == 'table' then v = v[1] end if v:len() > textMax then textMax = v:len() end end
- if my >= y and my <= y - 1 + #text and mx >= x and mx <= (x - 1 + textMax) then
- table.insert(clicked, {value.ret, key, {mx + 1 - x, my + 1 - y}})
- end
- else
- if t == 'radio' or t == 'checkbox' then custChar = value.custChar or {'O','0'} text = text:gsub('%%I%%', ut.noPat(custChar[value.value + 1])) end
- if t == 'cycler' then
- local val = value.value
- local vals = value.values
- local cyclers = value.cyclers
- text = text:gsub('%%D%%',ut.noPat(cyclers[1]))
- text = text:gsub('%%U%%',ut.noPat(cyclers[2]))
- text = text:gsub('%%V%%',ut.noPat(vals[val]))
- end
- if my == y and mx >= x and mx <= (x + text:len() - 1) then
- table.insert(clicked, {value.ret, key, {mx + 1 - x, my + 1 - y}})
- end
- end
- end
- end
- if #clicked == 0 then
- return nil
- else
- for i, v in pairs(clicked) do
- local curList = self.ui[v[2]]
- local text = curList.text
- if type(text) == 'table' then
- for i in ipairs(text) do
- text[i] = ut.aRaw(text[i])
- end
- else
- text = ut.aRaw(text)
- end
- if curList.type == 'radio' and curList.cont then
- if curList.value == 0 then
- grp = curList.cont
- for ind, val in pairs(self.ui) do
- if val['type'] == 'radio' and val.cont and val.cont == curList.cont then
- if val.value ~= 0 then val.value = 0 end
- end
- end
- curList.value = 1
- end
- if curList.exe and type(curList.exe) == 'function' then
- curList:exe(self, v[3])
- end
- elseif curList.type == 'checkbox' then
- if curList.value == 0 then
- curList.value = 1
- else
- curList.value = 0
- end
- elseif curList.type == 'button' then
- if curList.exe and type(curList.exe) == 'function' then
- curList:exe(self, v[3])
- end
- elseif curList.type == 'cycler' then
- -- '%D% %V% %U%'
- -- '< 1 >'
- -- '%D% 1 >'
- -- '< 1 %U%'
- -- '< %V% >'
- local cyclers, val, vals = curList.cyclers, curList.value, curList.values
- local doT = text:gsub('%%V%%',ut.noPat(vals[val])):gsub('%%U%%',ut.noPat(cyclers[2]))
- local doS = doT:find('%%D%%')
- local doE = doS + curList.cyclers[1]:len() - 1
- local upT = text:gsub('%%D%%',ut.noPat(cyclers[1])):gsub('%%V%%',ut.noPat(vals[val]))
- local upS = upT:find('%%U%%')
- local upE = upS + curList.cyclers[2]:len() - 1
- local teT = text:gsub('%%D%%',ut.noPat(cyclers[1])):gsub('%%U%%',ut.noPat(cyclers[2]))
- local teS = teT:find('%%V%%')
- local teE = teS + curList.values[curList.value]:len() - 1
- local curE = curList.value
- if upS and doS then
- if curE == 0 then curE = 1 end
- if v[3][1] >= upS and v[3][1] <= upE then
- if curE < #curList.values then
- curE = curE + 1
- elseif curList.loop then
- curE = 1
- end
- elseif v[3][1] >= doS and v[3][1] <= doE then
- if curE > 1 then
- curE = curE - 1
- elseif curList.loop then
- curE = #curList.values
- end
- elseif v[3][1] >= teS and v[3][1] <= teE then
- if self.ui[v[2]].exe and type(self.ui[v[2]].exe) == 'function' then self.ui[v[2]]:exe(self, v[3]) end
- end
- curList.value = curE
- end
- end
- end
- return clicked
- end
- end
- -- ################################################################################
- --
- -- Utils functions, contains the draw shapes 'n' stuff with some string utils
- function uiList:getRadio(cont) for i, v in pairs(self.ui) do if v.type == 'radio' and v.value == 1 and v.cont == cont then return v end end return nil end
- function uiList:getCheckbox(cont) local res = {} for i, v in pairs(self.ui) do if v.type == 'checkbox' and v.value == 1 and v.cont == cont then table.insert(res, v) end end if #res > 0 then return res else return nil end end
- function uiList:setCommonAttribute(regEx, attrib)
- for key, val in pairs(self.ui) do
- local validate = true
- local ut = self.utils
- for i, v in pairs(regEx) do
- if not(val[i]) or not(val[i]:find(v)) then
- validate = false
- break
- end
- end
- if validate then
- t = val.type
- for i, v in pairs(attrib) do
- if ut.inTable(self.config.pattern[t], i) > 0 then
- val[i] = v
- end
- end
- end
- end
- end
- uiList.utils.aRaw = aRaw
- uiList.utils.aWrite = aWrite
- uiList.utils.noPat = noPat
- uiList.utils.pairsByKey = pairsByKey
- uiList.utils.draw = {}
- uiList.utils.dbg = function(p) print( ' <> ',p,' <> ') sleep(2) end
- uiList.utils.draw.line = line
- uiList.utils.draw.box = box
- uiList.utils.draw.outline = outline
- uiList.utils.unpackPat = unpackPat
- uiList.utils.cur = cur
- uiList.utils.color = color
- uiList.utils.split = split
- uiList.utils.inTable = inTable
- --
- -- ################################################################################
- return uiList
- end
- --
- -- End of new() constructor
- -- ################################################################################
- function drawOutline(x1,y1, x2, y2, target, c)
- drawLine(x1,y1, x1, y2, target, c)
- drawLine(x1,y2, x2, y2, target, c)
- drawLine(x2,y1, x2, y2, target, c)
- drawLine(x1,y1, x2, y1, target, c)
- end
- function outline(self, x1,y1, x2, y2, target, c)
- self.line(x1,y1, x1, y2, target, c)
- self.line(x1,y2, x2, y2, target, c)
- self.line(x2,y1, x2, y2, target, c)
- self.line(x1,y1, x2, y1, target, c)
- end
- -- function from paintutils ported for using targets
- local function drawPixelInternal( xPos, yPos, target )
- target.setCursorPos(xPos, yPos)
- target.write(" ")
- end
- -- function to draw a filled box on target
- function drawBox(startX, startY, endX, endY, target, nColour)
- if startY <= endY then s = 1 else s= -1 end
- for index=startY, endY, s do
- drawLine( startX, index, endX, index, target, nColour )
- end
- end
- function box(self, startX, startY, endX, endY, target, nColour)
- if startY <= endY then s = 1 else s= -1 end
- for index=startY, endY, s do
- self.line( startX, index, endX, index, target, nColour )
- end
- end
- -- function to draw line using targets
- function drawLine(startX, startY, endX, endY, target, nColour)
- for i, v in pairs(target) do
- line( startX, startY, endX, endY, v, nColour )
- end
- end
- -- function from paintutils ported for using targets
- local function line(startX, startY, endX, endY, v, nColour )
- local function drawPixelInternal( xPos, yPos, target ) target.setCursorPos(xPos, yPos) target.write(" ") end
- if nColour then
- v.setBackgroundColor( nColour )
- end
- startX = math.floor(startX)
- startY = math.floor(startY)
- endX = math.floor(endX)
- endY = math.floor(endY)
- if startX == endX and startY == endY then
- drawPixelInternal( startX, startY, v )
- return
- end
- local minX = math.min( startX, endX )
- if minX == startX then
- minY = startY
- maxX = endX
- maxY = endY
- else
- minY = endY
- maxX = startX
- maxY = startY
- end
- local xDiff = maxX - minX
- local yDiff = maxY - minY
- if xDiff > math.abs(yDiff) then
- local y = minY
- local dy = yDiff / xDiff
- for x=minX,maxX do
- drawPixelInternal( x, math.floor( y + 0.5 ), v )
- y = y + dy
- end
- else
- local x = minX
- local dx = xDiff / yDiff
- if maxY >= minY then
- for y=minY,maxY do
- drawPixelInternal( math.floor( x + 0.5 ), y, v )
- x = x + dx
- end
- else
- for y=minY,maxY,-1 do
- drawPixelInternal( math.floor( x + 0.5 ), y, v )
- x = x - dx
- end
- end
- end
- end
- --
- -- ################################################################################
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement