ClientOS/LevelOS/startup/lUtils.lua
2025-10-20 00:25:07 +02:00

5381 lines
151 KiB
Lua

-- LevelOS Utils
if not unpack then
_G.unpack = table.unpack
end
local write = term.write
if _G.lUtils == nil then
_G.lUtils = {}
end
if _G.lOS == nil then
_G.lOS = {}
end
function lUtils.RGBToGrayscale( r, g, b )
local gr = (r + g + b) / 3
return gr,gr,gr
end
local function getLines(str)
local lines = {}
local w = 0
for potato in str:gmatch("([^\n]*)\n?") do
table.insert(lines,potato)
if #potato > w then
w = #potato
end
end
return lines,w
end
local function cRestore(restore)
if not restore then
return {bg=term.getBackgroundColor(),fg=term.getTextColor(),cursor={term.getCursorPos()}}
else
term.setBackgroundColor(restore.bg)
term.setTextColor(restore.fg)
term.setCursorPos(unpack(restore.cursor))
end
end
function lUtils.wordwrap(txt,width)
if not txt then
error("No text given",2)
end
local lines = {}
for line in txt:gmatch("([^\n]*)\n?") do
table.insert(lines,"")
for word in line:gmatch("%S*%s?") do
if #lines[#lines]+#word > width and #(lines[#lines]:gsub(" ","")) > 0 then
lines[#lines+1] = ""
end
if #lines[#lines]+#word > width then
local tWord = word
while #lines[#lines]+#tWord > width do
lines[#lines] = tWord:sub(1,width)
table.insert(lines,"")
tWord = tWord:sub(width+1)
end
lines[#lines] = tWord
else
lines[#lines] = lines[#lines]..word
end
end
end
if lines[#lines] == "" then
lines[#lines] = nil
end
if txt:sub(#txt) == "\n" then
table.insert(lines,"")
end
return lines
end
function lUtils.input(x1,y1,x2,y2,tOptions,sReplaceChar,tShape)
local oCursorA
local s
if not tShape then
s = {x1=x1,y1=y1,x2=x2,y2=y2,cursor={x=1,y=1,a=1},scr=0,ref={}}
else
s = tShape
s.cursor={x=1,y=1,a=1}
s.scr=0
s.ref={}
end
s.history = {}
s.rhistory = {}
s.changed = false
local opt = {}
if tOptions then
opt = tOptions
elseif s.opt then
opt = s.opt
else
opt = {}
end
if not opt.overflow and not opt.overflowX and not opt.overflowY then
opt.overflow = "scroll"
opt.overflowX = "scroll"
opt.overflowY = "none"
end
opt["overflow"] = opt.overflow or "scroll" -- none, stretch, scroll or wrap
opt["overflowX"] = opt.overflowX or opt.overflow
opt["overflowY"] = opt.overflowY or opt.overflow
if opt.overflowY == "wrap" then
opt.overflowY = "none"
end
opt["cursorColor"] = opt.cursorColor or s.txtcolor or term.getTextColor()
opt["replaceChar"] = sReplaceChar or opt.replaceChar
opt["minWidth"] = opt.minWidth or s.x2-(s.x1-1)
opt["minHeight"] = opt.minHeight or s.y2-(s.y1-1)
opt["tabSize"] = opt.tabSize or 4
opt["indentChar"] = opt.indentChar or " "
if opt.overflowX == "scroll" then
s.scrollX = 0
end
if opt.overflowY == "scroll" then
s.scrollY = 0
end
--[[opt["maxWidth"] = opt.maxWidth
opt["maxHeight"] = opt.maxHeight]]
s.opt = opt
s.color = s.color or opt.backgroundColor or term.getBackgroundColor()
s.txtcolor = s.txtcolor or opt.textColor or term.getTextColor()
local txtcolor = s.txtcolor
s.txt = opt.text or ""
--s.lines = {s.txt}
s.lines = {} -- real text input, for example password if censored
s.dLines = {} -- rendered text input, buncha asterisks if censored, spaces instead of tab
s.blit = {} -- foreground and background colors
s.state = false
local ref = s.ref
local syntaxes = {
["lua"] = {
lexer="lex",
whitespace=colors.white,
comment=colors.green,
string=colors.red,
escape=colors.orange,
keyword=colors.yellow,
value=colors.yellow,
ident=colors.white,
number=colors.purple,
symbol=colors.orange,
operator=colors.yellow,
unidentified=colors.white,
},
["lua-light"] = {
lexer="lex",
whitespace=colors.black,
comment=colors.lightGray,
string=colors.red,
escape=colors.orange,
keyword=colors.blue,
value=colors.purple,
ident=colors.black,
number=colors.lightBlue,
symbol=colors.orange,
operator=colors.gray,
unidentified=colors.black,
}
}
local syntax
local uservars = {}
local scope = 0
local function lineLn(line)
local findTab = line:find("\t")
local offset = 0
local t = s.opt.tabSize
while findTab do
local l = t-(findTab+offset-1)%t
offset = offset+(l-1)
findTab = line:find("\t",findTab+1)
end
return #line+offset
end
local function fillTable(tTable,tbl,prefix)
--local type = rtype
local rtype = rtype
if not rtype then
rtype = type
end
local docs = s.opt.complete.docs
while tTable do
for k,v in pairs(tTable) do
if type(k) == "string" and not tbl[k] then
if type(v) == "table" then
tbl[k] = {type="table",data={},name=prefix..k}
if docs and docs[tbl[k].name] then
tbl[k].docs = docs[tbl[k].name]
end
--[[scope = scope+1
fillTable(v,tbl[k].data,prefix..k..".")
scope = scope-1]]
elseif type(v) == "string" or type(v) == "number" or type(v) == "boolean" or type(v) == "nil" then
tbl[k] = {type=type(v),data=v,name=prefix..k}
if docs and docs[tbl[k].name] then
tbl[k].docs = docs[tbl[k].name]
end
elseif type(v) == "function" then
local obj = {type="function",data=v,name=prefix..k}
local args = {}
if rtype(v) == "table" then
local mt = getmetatable(v)
if rtype(mt) == "table" and rtype(mt._call) == "function" then
v = mt._call
end
end
if rtype(v) == "function" then
local info = debug.getinfo(v)
for t=1,info.nparams do
table.insert(args,debug.getlocal(v,t))
end
if info.isvararg then
table.insert(args,"...")
end
obj.args = args
obj.source = info.short_src
end
if not obj.args then obj.args = {} end
if not obj.source then obj.source = "" end
if docs and docs[obj.name] then
obj.docs = docs[obj.name]
end
tbl[k] = obj
end
end
end
local tMetatable = getmetatable(tTable)
if tMetatable and type(tMetatable.__index) == "table" then
tTable = tMetatable.__index
else
tTable = nil
end
end
end
local keywords = {"and", "break", "do", "else", "elseif", "end", "for", "function", "if", "in", "local", "not", "or", "repeat", "return", "then", "until", "while"}
local function complete(sTxt)
local docs = s.opt.complete.docs
if not sTxt then return {} end
local sSearchText
local fComplete = false
local sSearchText = sTxt:match("^[a-zA-Z0-9_%.:]+")
if not sSearchText then return end
if #sSearchText < #sTxt then
fComplete = true
end
s.opt.complete.selected = nil
local env = s.opt.complete.env
fillTable(env,uservars,"")
local nStart = 1
local nDot = string.find(sSearchText, ".", nStart, true)
local tTable = uservars
while nDot do
local sPart = string.sub(sSearchText, nStart, nDot - 1)
if not tTable[sPart] then return {} end
local value = tTable[sPart].data
if type(value) == "table" then
tTable = value
if type(env[sPart]) == "table" then
fillTable(env[sPart],tTable,"")
env = env[sPart]
end
nStart = nDot + 1
nDot = string.find(sSearchText, ".", nStart, true)
else
return {}
end
end
local nColon = string.find(sSearchText, ":", nStart, true)
if nColon then
local sPart = string.sub(sSearchText, nStart, nColon - 1)
if not tTable[sPart] then return {} end
local value = tTable[sPart].data
if type(value) == "table" then
tTable = value
if type(env[sPart]) == "table" then
fillTable(env[sPart],tTable,"")
env = env[sPart]
end
nStart = nColon + 1
else
return {}
end
end
local sPart = string.sub(sSearchText, nStart)
local prefix = string.sub(sSearchText, 1, nStart-1)
local nPartLength = #sPart
local tResults = {}
local tStrings = {}
local tSorted = {}
local tSeen = {}
if fComplete then
tResults.length = #sTxt
else
tResults.length = nPartLength
end
for k,v in pairs(tTable) do
if not tSeen[k] and type(k) == "string" and not (nColon and v.type ~= "function") then
if (string.find(k, sPart, 1, true) == 1 and not fComplete) or (fComplete and k == sPart) then
if string.match(k, "^[%a_][%a%d_]*$") then
local sResult = string.sub(k, nPartLength + 1)
local display = k
local index = sSearchText..sResult
if v.type == "function" then
local vArgs = lUtils.instantiate(v.args)
if nColon then
table.remove(vArgs,1)
end
if fComplete then
display = index.."("..table.concat(vArgs,",")..")"
else
display = k.."("..table.concat(vArgs,",")..")"
end
sResult = sResult.."("
end
tStrings[sResult] = {src=(v.source or ""),complete=sResult,type=v.type,display=display}
if fComplete and v.type ~= "function" then return nil end
if docs[index] then
local d = docs[index]
if fComplete then
tStrings[sResult].display = d.name:gsub("^_G%.","")
tStrings[sResult].description = d.summary
else
tStrings[sResult].display = d.name:gsub("^_G%.",""):gsub("^"..prefix,"")
end
end
table.insert(tSorted,sResult)
--tResults[#tResults].id = #tResults
end
end
end
tSeen[k] = true
end
table.sort(tSorted)
for t=1,#tSorted do
tResults[t] = tStrings[tSorted[t]]
tResults[t].id = t
end
--table.sort(tResults)
return tResults
end
local function renderComplete(list) -- make option to use overay like s.opt.complete.overlay = true
local c = s.opt.complete
local LevelOS = c.LevelOS
c.reverse = false
if not c.selected and not list[1].description then
c.selected = list[1]
end
local a = 0
if s.border and s.border.color ~= 0 then
a = 1
end
local scrollX = s.scrollX or 0
local scrollY = s.scrollY or 0
local x,y = s.x1+(s.cursor.x-1)+a-scrollX,s.y1+(s.cursor.y-1)+a-scrollY
x = x-list.length-4
y = y+1
local width = 10
local height = #list
for t=1,#list do
width = math.max(#list[t].display+#list[t].src+5,width)
if list[t].description then
list[t].lines = lUtils.wordwrap(list[t].description,width-4)
height = height+#list[t].lines
end
end
if c.overlay and LevelOS then
--local wX,wY = LevelOS.self.window.win.getPosition()
local wX,wY = lUtils.getWindowPos(term.current())
local w,h = lOS.oldterm.getSize()
x = x+wX-1
y = y+wY-1
if x < 1 then
x = 1
end
if y+(height-1) > h and y-1-height >= 1 then
y = y-2
c.reverse = true
end
if x+(width-1) > w then
x = w-width+1
end
else
if x < s.x1 then
x = s.x1
end
if y+(height-1) > s.y2 and y-1-height >= s.y1 then
c.reverse = true
y = y-2
end
if x+(width-1) > s.x2 then
x = s.x2-width+1
end
end
local offset = 1
if c.reverse then
offset = -1
end
local x2 = x+(width-1)
abbrevs = {table={"tbl",colors.lime},number={"num",colors.purple},boolean={"bln",colors.purple},["function"]={"fnc",colors.cyan},keyword={" ",colors.orange},["nil"]={"nil",colors.red},unknown={"???",colors.pink},string={"str",colors.yellow}}
local function theRender()
if not s.opt.complete.colors then
s.opt.complete.colors = {}
end
local col = s.opt.complete.colors
col.selectedbg = col.selectedbg or colors.lightBlue -- green
col.backgroundColor = col.backgroundColor or colors.gray -- magenta
col.txtcolor = col.txtcolor or colors.white -- white
col.typebg = col.typebg or colors.black -- light gray
col.selectedtypebg = col.selectedtypebg or colors.blue -- green
col.sourcetxtcolor = col.sourcetxtcolor or colors.lightGray -- pink
local cY = y
for t=1,#list do
if c.selected == list[t] then
term.setBackgroundColor(col.selectedtypebg)
else
term.setBackgroundColor(col.typebg)
end
local a = abbrevs[list[t].type] or abbrevs["unknown"]
term.setTextColor(a[2])
term.setCursorPos(x,cY)
term.write(a[1])
if c.selected == list[t] then
term.setBackgroundColor(col.selectedbg)
else
term.setBackgroundColor(col.backgroundColor)
end
term.setTextColor(col.txtcolor)
term.write(" "..list[t].display..string.rep(" ",width-#list[t].display-4-#list[t].src))
term.setCursorPos(x2-(#list[t].src-1),cY)
term.setTextColor(col.sourcetxtcolor)
term.write(list[t].src)
cY = cY+offset
if list[t].lines then
for i,l in ipairs(list[t].lines) do
term.setCursorPos(x,cY)
term.setTextColor(col.sourcetxtcolor)
term.write(" "..l..string.rep(" ",width-#l-4))
cY = cY+offset
end
end
end
end
local x1,y1,x2,y2
if c.overlay and LevelOS then
LevelOS.overlay = theRender
local wX,wY = LevelOS.self.window.win.getPosition()
x1 = x-wX+1
x2 = x1+width-1
y1 = y-wY+1
y2 = y1+((#list-1)*offset)
else
x1 = x
y1 = y
y2 = y1+((#list-1)*offset)
x2 = x1+width-1
theRender()
end
list.x1 = x1
list.y1 = math.min(y1,y2)
list.y2 = math.max(y1,y2)
list.x2 = x2
end
local function replaceText(txt,pos1,pos2,replace)
return txt:sub(1,pos1-1)..replace..txt:sub(pos2+1,#txt)
end
local function genLines(t)
local syn = s.opt.syntax
if type(syn) == "string" and syntaxes[syn] then
syntax = syntaxes[syn]
syntax.type = syn
elseif type(syn) == "table" and syn.lexer and type(syn.lexer) == "function" then
syntax = syn
elseif type(syn) == "table" and syntaxes[syn.type] then
syntax = syntaxes[syn.type]
for k,v in pairs(syn) do
syntax[k] = v
end
elseif type(syn) == "table" and syn.lexer and ((type(syn.lexer) == "string" and fs.exists(syn.lexer)) or type(syn.lexer) == "function") then
syntax = syn
else
syntax = nil
end
local blit = {}
if s.opt.complete and syntax then
uservars = {}
for k=1,#keywords do
uservars[keywords[k]] = {type="keyword"}
end
uservars["true"] = {type="boolean"}
uservars["false"] = {type="boolean"}
uservars["nil"] = {type="nil"}
_G.debuguservars = uservars
--[[scope = 0
local tTable = s.opt.complete.env
fillTable(tTable,uservars,"")]]
end
if syntax and ((type(syntax.lexer) == "string" and fs.exists(syntax.lexer)) or type(syntax.lexer) == "function") then
if type(syntax.lexer) ~= "function" then
syntax.lexer = dofile(syntax.lexer)
end
local lex = syntax.lexer
local sublines = lex(t)
local line = 1
local l = 0
local ref = 0
--for li in t:gmatch("([^\n]*)\n?") do -- this aint workin and dont forget to add newlines to the blit lines
blit[1] = ""
blit[2] = ""
blit[3] = ""
_G.debugsublines = sublines
for l=1,#sublines do
local elements = sublines[l]
for t=1,#elements do
local col
local col2 = lUtils.toBlit(s.color)
if type(syntax[elements[t].type]) == "table" then
col, col2 = lUtils.toBlit(syntax[elements[t].type][1] or s.txtcolor), lUtils.toBlit(syntax[elements[t].type][2] or s.color)
else
col = lUtils.toBlit(syntax[elements[t].type] or s.txtcolor)
end
blit[1] = blit[1]..elements[t].data
blit[2] = blit[2]..string.rep(col,#elements[t].data)
blit[3] = blit[3]..string.rep(col2,#elements[t].data)
if s.opt.complete and #s.txt < 40000 and syntax.type:find("^lua") then
if elements[t].type == "nfunction" then
local el = t+1
local args = {}
while true do
local e = elements[el]
if not e then
break
elseif e.type == "function" or e.type == "whitespace" or e.data == "(" or e.data == "," or e.data == "=" then
el = el+1
elseif e.type == "arg" or e.data == "..." then
table.insert(args,e.data)
el = el+1
else
break
end
end
local parent = {data=uservars}
local el = t-1
local children = {}
while true do
local e = elements[el]
if not e then
break
elseif e.data == "." then
el = el-1
elseif e.type == "ident" then
table.insert(children,1,e.data)
el = el-1
else
break
end
end
for t=1,#children do
if parent.data[children[t]] then
local child = parent.data[children[t]]
if type(child.data) ~= "table" then child.data = {} child.type = "table" end
parent = child
else
parent.data[children[t]] = {data={},type="table",source="Ln "..l}
parent = parent.data[children[t]]
end
end
parent.data[elements[t].data] = {args=args,type="function",source="Ln "..l}
elseif elements[t].type == "ident" then
local el = t+1
local foundEquals = false
local foundValue = false
local naming = true
local prefix = ""
local parent = {data=uservars}
local children = {elements[t].data}
while true do
local e = elements[el]
if not e then
break
elseif e.data == "." and naming then
el = el+1
elseif e.type == "whitespace" or (e.data == "=" and not foundEquals) then
naming = false
el = el+1
if e.data == "=" then
local stop = false
for c=1,#children-1 do
local child = parent.data[children[c]]
if not child then parent.data[children[c]] = {data={},type="table",source="Ln "..l} child = parent.data[children[c]] end
if type(child.data) ~= "table" then child.data = {} child.type = "table" end
parent = child
prefix = prefix..children[c].."."
end
if stop then break end
foundEquals = true
end
elseif e.type == "ident" then
el = el+1
if naming then
table.insert(children,e.data)
else
-- idk do later
foundValue = true
-- add table support
if uservars[e.data] then
end
end
elseif foundEquals and (e.type == "string" or e.type == "number" or (e.type == "operator" and e.data == "-") or e.type == "value" or (e.type == "symbol" and e.data:sub(1,1) == "{") or e.type == "function") then
local vType = e.type
if e.type == "operator" then
vType = "number"
elseif e.type == "value" then
if e.data == "nil" then
vType = "nil"
elseif e.data == "true" or e.data == "false" then
vType = "boolean"
else
vType = "unknown"
end
elseif e.type == "symbol" then
vType = "table"
elseif e.type == "function" then
vType = "unknown"
end
local obj = {name=prefix..children[#children],type=vType,source="Ln "..l}
if vType == "table" then
obj.data = {}
end
parent.data[children[#children]] = obj
break
else
break
end
end
end
end
end
if l < #sublines then
blit[1] = blit[1].."\n"
blit[2] = blit[2].."\n"
blit[3] = blit[3].."\n"
end
end
_G.debugBlit2 = {blit[1],blit[2],blit[3]}
end
-- check for transparency for third blit line
for a=1,#s.lines do
s.lines[a] = nil
s.dLines[a] = nil
s.blit[a] = nil
ref[a] = nil
end
s.lines[1] = ""
s.dLines[1] = ""
local width = s.x2-(s.x1-1)
local height = s.y2-(s.y1-1)
if s.border and s.border.color ~= 0 then
width = width-2
height = height-2
end
local c = 1
local l = s.lines
local dl = s.dLines
local pl = 0
if opt.overflowX == "scroll" then
--local b,e = t:find()
l[1] = ""
dl[1] = ""
while true do
-- line
local b,e = t:find("[^\n]*\n?")
if not b or e == 0 then break end
local line = t:sub(b,e)
l[c] = line
if #blit > 0 then
dl[c] = line
local findTab = dl[c]:find("\t")
local blit2 = blit[2]
local blit3 = blit[3]
if not s.opt.tabSize then
s.opt.tabSize = 4
end
local t = s.opt.tabSize
while findTab do
local l = t-(findTab-1)%t
dl[c] = replaceText(dl[c],findTab,findTab,string.sub(s.opt.indentChar..string.rep(" ",t-#s.opt.indentChar),t-l+1,t))
local col, col2
if type(syntax.whitespace) == "table" then
col = lUtils.toBlit(syntax.whitespace[1] or s.txtcolor)
col2 = lUtils.toBlit(syntax.whitespace[2] or s.color)
else
col = lUtils.toBlit(syntax.whitespace or s.txtcolor)
col2 = lUtils.toBlit(s.color)
end
blit2 = replaceText(blit2,findTab,findTab,string.rep(col,l))
blit3 = replaceText(blit3,findTab,findTab,string.rep(col2,l))
findTab = dl[c]:find("\t")
end
local tabArea = string.rep(" ",t)
local findSpace,findSpace2 = dl[c]:find(tabArea)
while findSpace do
dl[c] = replaceText(dl[c],findSpace,findSpace2,s.opt.indentChar..string.rep(" ",t-#s.opt.indentChar))
findSpace,findSpace2 = dl[c]:find(tabArea,findSpace2+1)
end
dl[c] = dl[c]:sub(1+s.scrollX,width+s.scrollX)
s.blit[c] = {dl[c],blit2:sub(b+s.scrollX,b+s.scrollX+(#dl[c]-1)),blit3:sub(b+s.scrollX,b+s.scrollX+(#dl[c]-1))}
blit[1] = blit[1]:sub(e+1,#blit[1])
blit[2] = blit[2]:sub(e+1,#blit[2])
blit[3] = blit[3]:sub(e+1,#blit[3])
else
dl[c] = line:sub(1+s.scrollX,width+s.scrollX)
s.blit[c] = {dl[c],string.rep(lUtils.toBlit(s.txtcolor),#dl[c]),string.rep(lUtils.toBlit(s.color),#dl[c])}
end
if line:sub(#line) == "\n" then
if c+1 > height then
if opt.overflowY == "stretch" then
s.y2 = s.y2+1
elseif opt.overflowY == "none" then
return false
end
end
l[c+1] = ""
dl[c+1] = ""
end
t = t:sub(e+1,#t)
c = c+1
end
else
while true do
local b,e = t:find("%S*%s?")
if not b or e < 1 then break end
local w = e-(b-1)
c = #l
if not dl[c] then dl[c] = l[c] end
if string.find(t:sub(b,e),"\n",nil,true) then
if (opt.overflowY == "stretch" or opt.overflowY == "scroll") or c+1 <= height then
if opt.overflowY == "stretch" and c+1 > height then
s.y2 = s.y2+1
end
b2,e2 = t:sub(b,e):find("\n",nil,true)
e = e2
l[c+1] = ""
dl[c+1] = ""
else
return false
end
end
local of = opt.overflowX
local tW,tH = term.getSize()
if opt.overflowX == "stretch" then
if #dl[c]+w > tW and #dl[c]+w > width then
of = "wrap"
end
end
if #dl[c]+w > width then
if opt.overflowX == "wrap" then
if (opt.overflowY == "stretch" or opt.overflowY == "scroll") or c+1 <= height then
if opt.overflowY == "stretch" and c+1 > height then
s.y2 = s.y2+1
end
if not dl[c]:find("%S") then
e = width-#l[c]
l[c] = l[c]..t:sub(b,e)
l[c+1] = ""
else
--l[c+1] = t:sub(b,e)
e = b-1
l[c+1] = ""
end
else
-- oh no, stop typing
return false
end
elseif opt.overflowX == "stretch" then
s.x2 = s.x2+1
l[c] = l[c]..t:sub(b,e)
dl[c] = dl[c]..t:sub(b,e):gsub("\9"," ")
elseif opt.overflowX == "none" then
return false
end
else
l[c] = l[c]..t:sub(b,e)
dl[c] = dl[c]..t:sub(b,e):gsub("\9"," ")
end
t = t:sub(e+1,#t)
end
end
return true
end
genLines(s.txt)
local function genText()
local txt = ""
ref[1] = 1
for l=1,#s.lines do
txt = txt..s.lines[l]
ref[l+1] = ref[l]+#s.lines[l]
if s.select and s.select[1] < ref[l+1] and s.select[2] >= ref[l] and s.blit and s.blit[l] then
local line = s.lines[l]
local c = lUtils.toBlit(s.opt.selectColor or colors.blue)
local sel1 = s.select[1]-ref[l]
local sel2 = s.select[2]-ref[l]+2
local findTab = s.lines[l]:find("\t")
local off1 = 0
local off2 = 0
local offT = 0
while findTab do
line = replaceText(line,findTab,findTab," ")
local t = s.opt.tabSize
local a = t-(findTab+offT-1)%t
if sel1 >= findTab then
off1 = off1+a-1
end
if sel2 > findTab then
off2 = off2+a-1
end
if findTab > sel1 and findTab < sel2 then
s.blit[l][1] = replaceText(s.blit[l][1],offT+findTab,offT+findTab+(a-1),string.sub(string.rep("\140",t-1).."\132",t-a+1,t))
end
offT = offT+a-1
findTab = line:find("\t")
end
sel1 = sel1+off1-s.scrollX
sel2 = sel2+off2-s.scrollX
local pos1 = math.max(0,sel1)
local pos2 = math.min(#s.blit[l][1]+1,sel2)
s.blit[l][3] = s.blit[l][3]:sub(1,pos1)..string.rep(c,pos2-(pos1+1))..s.blit[l][3]:sub(pos2,#s.blit[l][1])
if s.opt.selectTxtColor then
local c2 = lUtils.toBlit(s.opt.selectTxtColor)
s.blit[l][2] = s.blit[l][2]:sub(1,pos1)..string.rep(c2,pos2-(pos1+1))..s.blit[l][2]:sub(pos2,#s.blit[l][1])
end
s.blit[l][1] = s.blit[l][1]:sub(1,pos1)..s.blit[l][1]:sub(pos1+1,pos2-1):gsub(" ","\183")..s.blit[l][1]:sub(pos2,#s.blit[l][1])
end
end
return txt
end
genText()
local function calcCursor()
local width = s.x2-(s.x1-1)
local height = s.y2-(s.y1-1)
if s.border and s.border.color ~= 0 then
width = width-2
height = height-2
end
for r=1,#s.lines do
if ref[r+1] > s.cursor.a or r == #s.lines then
s.cursor.y = r
s.cursor.x = s.cursor.a-(ref[r]-1)
local _,tabs = s.txt:sub(ref[r],s.cursor.a-1):gsub("\9","")
s.cursor.x = s.cursor.x+((s.opt.tabSize-1)*tabs)
break
end
end
if opt.overflowX == "scroll" then
if s.cursor.x < (1+s.scrollX) then
s.scrollX = s.cursor.x-1
elseif s.cursor.x > (width+s.scrollX) then
s.scrollX = s.cursor.x-width
end
if s.scrollX > 0 and lineLn(s.lines[s.cursor.y]) < width then
s.scrollX = 0
end
end
if opt.overflowY == "scroll" then
if s.cursor.y < (1+s.scrollY) then
s.scrollY = s.cursor.y-1
elseif s.cursor.y > (height+s.scrollY) then
s.scrollY = s.cursor.y-height
end
end
end
local function rCalcCursor()
local x = s.cursor.x
local tx = 0
local offset = 0
local line = s.lines[s.cursor.y]
if s.cursor.y == #s.lines then
line = line.."\n"
end
for w in line:gmatch(".") do
tx = tx+1
if w == "\t" then
local l = s.opt.tabSize-(tx-1)%s.opt.tabSize
tx = tx+l-1
offset = offset+l-1
end
if tx >= x then
x = tx-offset
break
end
end
s.cursor.a = ref[s.cursor.y]+(x-1)
end
--s.rCalcCursor = rCalcCursor
local oTxt = ""
local function rAll(nTxt)
if nTxt then
if not genLines(nTxt) then
genLines(s.txt)
s.cursor.a = oCursorA
else
s.txt = nTxt
end
else
genLines(s.txt)
end
genText()
calcCursor()
genLines(s.txt)
genText()
oTxt = s.txt
if s.opt.complete then
s.opt.complete.list = nil
end
end
local uTimer
local function addUndo(event)
if #s.history == 0 then
table.insert(s.history,{txt=s.txt,changed=true,cursor=s.cursor.a})
end
if event == "paste" then
table.insert(s.history,{txt=s.txt,changed=true,cursor=s.cursor.a,description="Paste"})
elseif event == "key" or event == "char" then
--if s.history[#s.history] and s.history[#s.history].time and s.history[#s.history].time > os.epoch("utc")-250
if uTimer then os.cancelTimer(uTimer) end
uTimer = os.startTimer(0.3)
elseif event == "timer" then
table.insert(s.history,{txt=s.txt,changed=s.changed,cursor=s.cursor.a,description="Insert Characters"})
end
while #s.history > 80 do
table.remove(s.history,1)
end
end
local function undo()
if s.history[#s.history-1] then
local h = s.history[#s.history-1]
s.changed = h.changed
s.cursor.a = h.cursor
rAll(h.txt)
table.insert(s.rhistory,s.history[#s.history])
table.remove(s.history,#s.history)
end
end
local function redo()
if s.rhistory[#s.rhistory] then
local h = s.history
local r = s.rhistory[#s.rhistory]
s.changed = r.changed
s.cursor.a = r.cursor
rAll(r.txt)
table.insert(s.history,r)
table.remove(s.rhistory,#s.rhistory)
end
end
local function addText(txt)
local a = 0
if s.border and s.border.color ~= 0 then
a = 1
end
local ttxt = txt
if txt:find("\n") then
if s.opt.overflowY == "none" and s.cursor.y >= s.y2-s.y1+1-a*2 then
return false
else
ttxt = txt:match("(.-)\n")
end
end
if s.opt.overflowX == "none" and lineLn(s.lines[s.cursor.y]..ttxt) > s.x2-s.x1+1-a*2 then
return false
end
local pos1,pos2
if s.select then
pos1 = s.select[1]-1
pos2 = s.select[2]+1
else
pos1 = s.cursor.a-1
pos2 = s.cursor.a
end
s.changed = true
s.txt = s.txt:sub(1,pos1)..txt..s.txt:sub(pos2,#s.txt)
s.cursor.a = pos1+#txt+1
s.select = nil
rAll()
end
local function update(...) -- maybe make it so that click already selects and then the key char etc operations always sub based on select so no if statement needed
opt = s.opt
if not opt.tabSize then
opt.tabSize = 4
end
oCursorA = s.cursor.a
local e = table.pack(...)
if s.opt.complete and s.opt.complete.LevelOS and s.opt.complete.LevelOS.self.window.events == "all" and e[1]:find("mouse") and type(e[3]) == "number" and type(e[4]) == "number" then
local wX,wY = s.opt.complete.LevelOS.self.window.win.getPosition()
e[3] = e[3]-(wX-1)
e[4] = e[4]-(wY-1)
end
if e[1] == "mouse_click" and s.opt.complete and s.opt.complete.list and #s.opt.complete.list > 0 and not lUtils.isInside(e[3],e[4],s.opt.complete.list) then
s.opt.complete.list = nil
rAll()
end
if e[1] == "timer" and e[2] == uTimer then
addUndo(e[1])
end
if not s.state then
if e[1] == "mouse_click" and e[3] >= s.x1 and e[4] >= s.y1 and e[3] <= s.x2 and e[4] <= s.y2 then
s.state = true
elseif e[1] == "term_resize" then
rAll()
elseif s.txt ~= oTxt then
rAll()
end
else
if e[1] == "mouse_click" then
if (e[3] < s.x1 or e[3] > s.x2 or e[4] < s.y1 or e[4] > s.y2) and not (s.opt.complete and s.opt.complete.list and #s.opt.complete.list > 0 and lUtils.isInside(e[3],e[4],s.opt.complete.list)) then -- add support for autocomplete click
term.setCursorBlink(false)
s.state = false
end
end
if s.txt ~= oTxt then
rAll()
end
if e[1] == "char" then
if #s.history == 0 then
table.insert(s.history,{txt=s.txt,changed=false,cursor=s.cursor.a})
end
--[[s.changed = true
s.cursor.a = s.cursor.a+1
rAll(s.txt:sub(1,s.cursor.a-2)..e[2]..s.txt:sub(s.cursor.a-1,#s.txt))]]
addText(e[2])
addUndo(e[1])
if s.opt.complete then
s.opt.complete.complete = complete
s.opt.complete.render = renderComplete
s.opt.complete.list = complete(string.match(s.txt:sub(1,s.cursor.a-1), "[a-zA-Z0-9_%.:]+$"))
end
elseif e[1] == "key" then
local dirs = {
[keys.left] = true,
[keys.right] = true,
[keys.up] = true,
[keys.down] = true,
[keys["end"]] = true,
[keys.home] = true,
}
local deletes = {
[keys.delete] = true,
[keys.backspace] = true,
}
if lUtils.isHolding(keys.leftCtrl) and dirs[e[2]] then
-- nothing
elseif s.select and dirs[e[2]] then
s.select = nil
rAll()
elseif s.select and deletes[e[2]] then
addText("")
elseif e[2] == keys.left and s.cursor.a > 1 then
s.cursor.a = s.cursor.a-1
--calcCursor()
rAll()
elseif e[2] == keys.right and s.cursor.a <= #s.txt then
s.cursor.a = s.cursor.a+1
--calcCursor()
rAll()
elseif e[2] == keys.up then
local c = s.opt.complete
if c and c.list and #c.list > 0 and c.selected then
local offset = 1
if c.reverse then
offset = -1
end
local sID = c.selected.id-offset
if sID < 1 then
sID = #c.list
elseif sID > #c.list then
sID = 0
end
c.selected = c.list[sID]
elseif s.cursor.y > 1 then
s.cursor.y = s.cursor.y-1
if opt.overflowX == "scroll" then
li = s.lines
else
li = s.dLines
end
local ln = lineLn(li[s.cursor.y])
if s.cursor.x > ln then
s.cursor.x = ln
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
end
rCalcCursor()
rAll()
end
elseif e[2] == keys.down then
local c = s.opt.complete
if c and c.list and #c.list > 0 and c.selected then
local offset = 1
if c.reverse then
offset = -1
end
local sID = c.selected.id+offset
if sID < 1 then
sID = #c.list
elseif sID > #c.list then
sID = 0
end
c.selected = c.list[sID]
elseif s.cursor.y < #s.lines then
s.cursor.y = s.cursor.y+1
if opt.overflowX == "scroll" then
li = s.lines
else
li = s.dLines
end
local ln = lineLn(li[s.cursor.y])
if s.cursor.x > ln then
s.cursor.x = ln
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
end
rCalcCursor()
rAll()
end
elseif e[2] == keys.pageUp then
local h = s.y2-(s.y1-1)
s.cursor.y = math.max(s.cursor.y-h,1)
if opt.overflowX == "scroll" then
li = s.lines
else
li = s.dLines
end
local ln = lineLn(li[s.cursor.y])
if s.cursor.x > ln then
s.cursor.x = ln
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
end
rCalcCursor()
rAll()
elseif e[2] == keys.pageDown then
local h = s.y2-(s.y1-1)
s.cursor.y = math.min(s.cursor.y+h,#s.lines)
if opt.overflowX == "scroll" then
li = s.lines
else
li = s.dLines
end
local ln = lineLn(li[s.cursor.y])
if s.cursor.x > ln then
s.cursor.x = ln
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
end
rCalcCursor()
rAll()
elseif e[2] == keys["end"] then
if lUtils.isHolding(keys.leftCtrl) then
s.cursor.a = #s.txt+1
else
if opt.overflowX == "scroll" then
local ln = lineLn(s.lines[s.cursor.y])
s.cursor.x = ln
else
s.cursor.x = #s.dLines[s.cursor.y]
end
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
rCalcCursor()
end
rAll()
elseif e[2] == keys.home then
if lUtils.isHolding(keys.leftCtrl) then
s.cursor.a = 1
else
local wp = (s.lines[s.cursor.y]:match("^[\t ]+") or ""):gsub("\t",string.rep(" ",s.opt.tabSize)) -- always beginning of string so always 4 per tab
if s.cursor.x == 1+#wp then
s.cursor.x = 1
else
s.cursor.x = 1+#wp
end
rCalcCursor()
end
rAll()
elseif e[2] == keys.backspace and s.cursor.a > 1 then
s.changed = true
s.txt = s.txt:sub(1,s.cursor.a-2)..s.txt:sub(s.cursor.a,#s.txt)
s.cursor.a = s.cursor.a-1
rAll()
addUndo(e[1])
elseif e[2] == keys.delete and s.cursor.a <= #s.txt then
s.changed = true
s.txt = s.txt:sub(1,s.cursor.a-1)..s.txt:sub(s.cursor.a+1,#s.txt)
rAll()
addUndo(e[1])
elseif e[2] == keys.enter then
local wp = s.lines[s.cursor.y]:match("^[\t ]+") or ""
addText("\n"..wp)
addUndo(e[1])
elseif (lUtils.isHolding(keys.leftCtrl) and (e[2] == keys.leftBracket or e[2] == keys.rightBracket)) or (s.select and e[2] == keys.tab) then
local sLine = s.cursor.y
local eLine = s.cursor.y
if s.select then
for l=1,#s.lines do
local encountered = false
if (s.select[1] >= s.ref[l] and s.select[1] < s.ref[l+1]) then -- if begin of select is in line
sLine = math.min(sLine,l)
end
if (s.select[2] >= s.ref[l] and s.select[2] < s.ref[l+1]) then -- if end of select is in line
eLine = math.max(eLine,l)
encountered = true
elseif encountered then -- if it already encountered the select and is no longer in selected area it doesnt need to search any further
break
end
end
end
for l=sLine,eLine do
if e[2] == keys.rightBracket or e[2] == keys.tab then
s.lines[l] = "\t"..s.lines[l]
if s.select then
if s.select[1] >= s.ref[l] then
s.select[1] = s.select[1]+1
end
if s.select[2] >= s.ref[l] then
s.select[2] = s.select[2]+1
end
end
if s.cursor.a >= s.ref[l] then
s.cursor.a = s.cursor.a+1
end
calcCursor()
elseif e[2] == keys.leftBracket then
if s.lines[l]:find("^\t") then
s.lines[l] = s.lines[l]:gsub("^\t","")
if s.select then
if s.select[1] > s.ref[l] then
s.select[1] = s.select[1]-1
end
if s.select[2] > s.ref[l] then
s.select[2] = s.select[2]-1
end
end
if s.cursor.a > s.ref[l] then
s.cursor.a = s.cursor.a-1
end
elseif s.lines[l]:find("^ ") then
s.lines[l] = s.lines[l]:gsub("^ ","")
if s.select then
if s.select[1] > s.ref[l] then
s.select[1] = math.max(s.select[1]-4,s.ref[l])
end
if s.select[2] > s.ref[l] then
s.select[2] = math.max(s.select[2]-4,s.ref[l])
end
end
if s.cursor.a > s.ref[l] then
s.cursor.a = math.max(s.cursor.a-4,s.ref[l])
end
end
end
s.txt = genText()
rAll()
end
elseif e[2] == keys.tab then
local c = s.opt.complete
if c and c.list and #c.list > 0 and c.selected then
addText(c.selected.complete)
addUndo("char")
else
addText("\t")
addUndo(e[1])
end
elseif lUtils.isHolding(keys.leftCtrl) then
if e[2] == keys.z then
undo()
elseif e[2] == keys.y then
redo()
elseif e[2] == keys.a then
s.select = {1,#s.txt}
s.cursor.a = #s.txt+1
rAll()
elseif (e[2] == keys.c or e[2] == keys.x) and s.select then
local txt = s.txt:sub(s.select[1],s.select[2])
if ccemux then
ccemux.setClipboard(txt)
lOS.clipboard = txt
else
s.clipboard = txt
end
if e[2] == keys.x then
addText("")
addUndo(e[1])
end
end
end
elseif e[1] == "paste" then
if s.opt.complete then
s.opt.complete.list = nil
end
if #s.history == 0 then
table.insert(s.history,{txt=s.txt,changed=false,cursor=s.cursor.a})
end
local txt = e[2]
if s.clipboard then
txt = s.clipboard
elseif lOS.clipboard then
local nline = lOS.clipboard:find("(.)\n") or #lOS.clipboard
local checkCB = lOS.clipboard:sub(1,nline):gsub("\t","")
if txt == checkCB then
txt = lOS.clipboard
end
end
addText(txt)
addUndo(e[1])
elseif s.opt.complete and s.opt.complete.list and #s.opt.complete.list > 0 and s.opt.complete.selected and (e[1] == "mouse_click" or e[1] == "mouse_scroll" or e[1] == "mouse_up") and lUtils.isInside(e[3],e[4],s.opt.complete.list) then
local c = s.opt.complete
--local x,y = e[3]-(s.x1-1),e[4]-(s.y1-1)
local el = e[4]-(s.opt.complete.list.y1-1)
if e[1] == "mouse_click" then
c.selected = c.list[el]
elseif e[1] == "mouse_up" and c.selected.id == el then
s.changed = true
s.txt = s.txt:sub(1,s.cursor.a-1)..c.selected.complete..s.txt:sub(s.cursor.a,#s.txt)
s.cursor.a = s.cursor.a+#c.selected.complete
rAll()
addUndo("char")
end
elseif e[1] == "mouse_click" or e[1] == "mouse_scroll" then
s.select = nil
if e[3] >= s.x1 and e[4] >= s.y1 and e[3] <= s.x2 and e[4] <= s.y2 then
if e[1] == "mouse_click" then
local scrollX = s.scrollX or 0
local scrollY = s.scrollY or 0
local x,y = e[3]-(s.x1-1)+scrollX,e[4]-(s.y1-1)-s.scr+scrollY
if not s.lines[y] then
y = #s.lines
end
if x > #s.dLines[y]+scrollX then
if opt.overflowX == "scroll" then
x = lineLn(s.lines[y])
else
x = #s.dLines[y]
end
if y == #s.lines then
x = x+1
end
end
s.cursor.x,s.cursor.y = x,y
rCalcCursor()
rAll()
elseif opt.overflowY == "scroll" and e[1] == "mouse_scroll" then
local width = s.x2-(s.x1-1)
local height = s.y2-(s.y1-1)
if s.border and s.border.color ~= 0 then
width = width-2
height = height-2
end
if s.scrollY+e[2] >= 0 and s.scrollY+e[2] <= #s.lines-height then
s.scrollY = s.scrollY+e[2]
end
end
end
elseif e[1] == "mouse_drag" then
local pos1
if s.select then
pos1 = s.select[1]
else
pos1 = s.cursor.a
s.select = {pos1,pos1}
end
local scrollX = s.scrollX or 0
local scrollY = s.scrollY or 0
local x,y = e[3]-(s.x1-1)+scrollX,e[4]-(s.y1-1)-s.scr+scrollY
if not s.lines[y] then
y = #s.lines
end
if x > #s.dLines[y]+scrollX then
if opt.overflowX == "scroll" then
x = lineLn(s.lines[y])
else
x = #s.dLines[y]
end
if y == #s.lines then
x = x+1
end
end
s.cursor.x,s.cursor.y = x,y
rCalcCursor()
local pos2 = s.cursor.a
if s.select then
if pos2 < s.select[1] and not s.select.reversed then
s.select.reversed = true
s.select[2] = s.select[1]-1
elseif pos2 > s.select[2] and s.select.reversed then
s.select.reversed = false
s.select[1] = s.select[2]+1
end
if s.select.reversed then
s.select[1] = pos2
else
s.select[2] = pos2-1
end
end
rAll()
elseif e[1] == "term_resize" then
rAll()
end
end
local lChar = s.txt:sub(s.cursor.a-1,s.cursor.a-1)
if s.opt.complete and (e[1] == "key" or e[1] == "char") and (lChar == "(" or lChar == ",") then
s.opt.complete.list = complete(string.match(s.txt:sub(1,s.cursor.a-1), "[a-zA-Z0-9_%.:%(]+[^%)%(]*$"))
end
end
local function render()
if s.color == 0 then s.color = colors.white end
local restore = cRestore()
term.setBackgroundColor(s.color or term.getBackgroundColor())
local a = 0
if s.border and s.border.color ~= 0 then
a = 1
end
lOS.boxClear(s.x1+a,s.y1+a,s.x2-a,s.y2-a)
local scrollX = s.scrollX or 0
local scrollY = s.scrollY or 0
for y=s.y1+a,s.y2-a do
local l = y-(s.y1-1+a)+s.scr + scrollY
if s.lines[l] then
term.setCursorPos(s.x1+a,y)
term.setBackgroundColor(s.color or term.getBackgroundColor())
term.setTextColor(s.txtcolor or txtcolor)
local line
if s.blit[l] then
line = s.blit[l][1]
else
line = s.dLines[l]
end
if type(opt.replaceChar) == "string" and #opt.replaceChar > 0 then
local pattern = "."
for t=1,#opt.replaceChar-1 do
pattern = pattern..".?"
end
local nLine = line:gsub(pattern,opt.replaceChar)
line = nLine:sub(1,#line)
end
if s.blit[l] then
--s.blit[l][3] = string.rep(lUtils.toBlit(s.color or term.getBackgroundColor()),#s.blit[l][1])
term.blit(line,s.blit[l][2],s.blit[l][3])
else
term.write(line)
end
end
end
if s.opt.complete and s.opt.complete.list and #s.opt.complete.list > 0 then
if s.opt.complete.overlay and s.opt.complete.LevelOS then
s.opt.complete.LevelOS.self.window.events = "all"
lOS.noEvents = 2
end
lOS.noEvents = 2
local ok,err = pcall(renderComplete,s.opt.complete.list)
if not ok then
_G.theterribleerrorrighthere = err
end
elseif s.opt.complete and s.opt.complete.LevelOS and s.opt.complete.LevelOS.self.window.events == "all" then
if s.opt.complete.overlay then
s.opt.complete.LevelOS.self.window.events = nil
lOS.noEvents = false
end
s.opt.complete.LevelOS.overlay = nil
end
if s.state then
local x,y = s.x1+(s.cursor.x-1)+a-scrollX,s.y1+(s.cursor.y-1)+a-scrollY
if lUtils.isInside(x,y,s) then
term.setTextColor(opt.cursorColor or txtcolor)
term.setCursorPos(x,y)
term.setCursorBlink(true)
else
term.setCursorBlink(false)
end
else
cRestore(restore)
end
end
if not tShape then
s.update = update
s.render = render
else
s.fUpdate = update
s.fRender = render
end
return s
end
function lUtils.read(_sReplaceChar)
local x1,y1 = term.getCursorPos()
local x2,_ = term.getSize()
local box = lUtils.input(x1,y1,x2,y1,{overflowX="scroll",overflowY="none",replaceChar=_sReplaceChar})
box.state = true
while true do
local e = {os.pullEvent()}
if e[1] == "key" and e[2] == keys.enter then
box.state = false
print("")
return box.txt
else
box.update(unpack(e))
box.state = true
box.render()
end
end
end
local animationTbl = {}
lUtils.debugAnimationTbl = animationTbl
function lUtils.renderImg(spr,x,y,format,transparency)
local format
local img = spr
local cterm = term.current()
if not format then
if type(spr) == "string" then
format = "nfp"
elseif type(spr) == "table" then
format = "bImg"
if type(spr[1][1]) == "table" then
if #spr == 1 or not spr.animated then
spr = spr[1]
else
if not animationTbl[spr] then
animationTbl[spr] = {cFrame=0,oTime=os.epoch("utc")/1000}
else
local cFrame = animationTbl[spr].cFrame+0.0001
local dur = spr[math.ceil(cFrame)].duration or spr.secondsPerFrame or 0.05
local oTime = animationTbl[spr].oTime
local cTime = os.epoch("utc")/1000
local delta = (cTime-oTime)
animationTbl[spr].cFrame = (cFrame + delta/dur) % #spr
animationTbl[spr].oTime = cTime
end
spr = spr[math.ceil(animationTbl[spr].cFrame+0.0001)]
end
end
end
end
if format == "bImg" or format == "lImg" then
if not spr[1] or not spr[1][1] or type(spr[1][1]) ~= "string" then
error("Unrecognized format",2)
end
local sW,sH = #spr[1][1],#spr
local w,h = term.getSize()
for l=1,#spr do
--[[if not y then
term.setCursorPos(math.ceil(w/2)-math.floor(sW/2),(math.ceil(h/2)-math.floor(sH/2)+(l-1)))
else
term.setCursorPos(x,y+(l-1))
end]]
local line
if y then
line = y+(l-1)
end
local cX,cY = x or math.ceil(w/2)-math.floor(sW/2), line or (math.ceil(h/2)-math.floor(sH/2)+(l-1))
term.setCursorPos(cX,cY)
local bl = {}
bl[1] = spr[l][1]
if transparency then
bl[2] = spr[l][2]
bl[3] = spr[l][3]
-- thing
local line
if cY >= 1 and cY <= h then
line = {cterm.getLine(cY)}
for t=1,3 do
line[t] = line[t]:sub(cX,cX+(#spr[l][1]-1))
end
else
line = {string.rep("f",#spr[l][1])}
line[2] = line[1]
line[3] = line[2]
end
if #line[2] < #spr[l][1] then
line[1] = line[1]..string.rep(" ",#spr[l][1]-#line[1])
line[2] = line[2]..string.rep("f",#spr[l][1]-#line[2])
line[3] = line[3]..string.rep("f",#spr[l][1]-#line[3])
end
local start,final = 0,0
while true do
start,final = bl[2]:gsub(" ","T"):find("T")
if start then
bl[2] = bl[2]:sub(1,start-1)..line[3]:sub(start,final)..bl[2]:sub(final+1,#bl[2])
else
break
end
end
while true do
start,final = bl[3]:gsub(" ","T"):find("T")
if start then
if bl[1]:sub(start,final) == " " or bl[1]:sub(start,final) == "\128" then
bl[1] = bl[1]:sub(1,start-1)..line[1]:sub(start,final)..bl[1]:sub(final+1,#bl[1])
bl[2] = bl[2]:sub(1,start-1)..line[2]:sub(start,final)..bl[2]:sub(final+1,#bl[2])
end
bl[3] = bl[3]:sub(1,start-1)..line[3]:sub(start,final)..bl[3]:sub(final+1,#bl[3])
else
break
end
end
else
bl[2] = string.gsub(spr[l][2]:gsub(" ","T"),"T",lUtils.toBlit(term.getBackgroundColor()))
bl[3] = string.gsub(spr[l][3]:gsub(" ","T"),"T",lUtils.toBlit(term.getBackgroundColor()))
end
if #bl[1] ~= #bl[2] or #bl[2] ~= #bl[3] then
_G.debugblitthingy = bl
if #bl[2] > #bl[1] then
bl[2] = bl[2]:sub(1, #bl[1])
elseif #bl[2] < #bl[1] then
bl[2] = bl[2]..string.rep(lUtils.toBlit(term.getBackgroundColor()), #bl[1]-#bl[2])
end
if #bl[3] > #bl[2] then
bl[3] = bl[3]:sub(1, #bl[2])
elseif #bl[3] < #bl[2] then
bl[3] = bl[3]..string.rep(lUtils.toBlit(term.getBackgroundColor()), #bl[2]-#bl[3])
end
end
term.blit(unpack(bl))
end
elseif format == "nfp" or format == "nfg" then
local b,e = string.find(spr,"\n")
local sW,sH
local w,h = term.getSize()
local lines,sW = getLines(spr)
sH = #lines
for l=1,sH do
local line
if y then
line = y+(l-1)
end
term.setCursorPos(x or math.ceil(w/2)-math.floor(sW/2), line or math.ceil(h/2)-math.floor(sH/2)+(l-1))
for lX=1,sW do
local ch = lines[l]:sub(lX,lX)
if ch ~= " " and ch ~= "" then
term.blit(" ","f",ch)
else
local cx,cy = term.getCursorPos()
term.setCursorPos(cx+1,cy)
end
end
end
end
end
function lUtils.getDrawingCharacter(...)
local e=0
local arg = {...}
for t=1,5 do
if arg[t] then
e=e+2^(t-1)
end
end
return {char = string.char(arg[6] and 159-e or 128+e), inverted = not not arg[6]}
end
lUtils.asset = {}
function lUtils.asset.load(filename)
if filename and type(filename) == "string" and fs.exists(filename) and not fs.isDir(filename) then
local ft = lUtils.getFileType(filename)
if ft == ".limg" or ft == ".bimg" or ft == ".lconf" then
local contents = lUtils.fread(filename, true)
local decodedText = ""
for _, codepoint in utf8.codes(contents) do
decodedText = decodedText .. string.char(codepoint)
end
local data = textutils.unserialize(decodedText)
local oterm = term.current()
local trashwin = window.create(term.current(), 1, 1, 51, 19, false)
term.redirect(trashwin)
local ok, _ = pcall(lUtils.renderImg, data)
term.redirect(oterm)
if ((ft == ".limg" or ft == ".bimg") and not ok) then
return textutils.unserialize(contents)
else
return data
end
end
return textutils.unserialize(lUtils.fread(filename))
else
return false
end
end
function lUtils.asset.save(asset,filename,compact)
if compact == nil then compact = true end
if type(asset) == "table" and not fs.isDir(filename) then
local ok,output = pcall(textutils.serialize,asset,{compact=compact})
if not ok then
error(output,2)
end
return lUtils.fwrite(filename,output)
else
return false
end
end
function lUtils.HSVToRGB( hue, saturation, value )
-- Returns the RGB equivalent of the given HSV-defined color
-- (adapted from some code found around the web)
-- If it's achromatic, just return the value
hue = hue%360
if saturation == 0 then
return value,value,value
end
-- Get the hue sector
local hue_sector = math.floor( hue / 60 )
local hue_sector_offset = ( hue / 60 ) - hue_sector
local p = value * ( 1 - saturation )
local q = value * ( 1 - saturation * hue_sector_offset )
local t = value * ( 1 - saturation * ( 1 - hue_sector_offset ) )
if hue_sector == 0 then
return value, t, p
elseif hue_sector == 1 then
return q, value, p
elseif hue_sector == 2 then
return p, value, t
elseif hue_sector == 3 then
return p, q, value
elseif hue_sector == 4 then
return t, p, value
elseif hue_sector == 5 then
return value, p, q
end
end
function lUtils.RGBToHSV( red, green, blue )
local hue, saturation, value
local min_value = math.min( red, green, blue )
local max_value = math.max( red, green, blue )
value = max_value
local value_delta = max_value - min_value
-- If the color is not black
if max_value ~= 0 then
saturation = value_delta / max_value
-- If the color is purely black
else
saturation = 0
hue = -1
return hue, saturation, value
end
if red == max_value then
hue = ( green - blue ) / value_delta
elseif green == max_value then
hue = 2 + ( blue - red ) / value_delta
else
hue = 4 + ( red - green ) / value_delta
end
hue = hue * 60
if hue < 0 then
hue = hue + 360
end
return hue, saturation, value
end
lUtils.graphics = {}
local g = lUtils.graphics
function g.button(txt,x1,y1,x2,y2,func,border,fg1,bg1,fg2,bg2,bcolor)
if not y2 then
return nil
end
if not bg1 then
bg1 = colors.black
end
if not fg1 then
fg1 = colors.white
end
if not bcolor then
bcolor = colors.gray
end
if not fg2 then
fg2 = fg1
end
if not bg2 then
bg2 = colors.gray
end
if not border then
border = true
end
local btn = {txt=txt,x1=x1,y1=y1,x2=x2,y2=y2,func=func,border=border,selected=false,colors={bg1=bg1,fg1=fg1,bg2=bg2,fg2=fg2,border=bcolor}}
function btn.render(...)
local e = {...}
local selected = false
--term.setTextColor(colors.fg1)
--term.setBackgroundColor(colors.bg1)
local abc = {}
if e[1] == "mouse_click" or e[1] == "mouse_up" then
if e[3] >= btn.x1 and e[4] >= btn.y1 and e[3] <= btn.x2 and e[4] <= btn.y2 then
if e[1] == "mouse_click" then
btn.selected = true
elseif e[1] == "mouse_up" then
if not btn.func then
abc[1] = true
else
abc = {btn.func()}
end
end
end
end
if e[1] == "mouse_up" then
btn.selected = false
end
if btn.selected then
term.setBackgroundColor(btn.colors.bg2)
else
term.setBackgroundColor(btn.colors.bg1)
end
term.setTextColor(btn.colors.border)
if btn.border == true then
lUtils.border(btn.x1,btn.y1,btn.x2,btn.y2)
else
lOS.boxClear(btn.x1,btn.y1,btn.x2,btn.y2)
end
if btn.selected then
term.setTextColor(btn.colors.fg2)
else
term.setTextColor(btn.colors.fg1)
end
lUtils.textbox(btn.txt,btn.x1+1,btn.y1+1,btn.x2-1,btn.y2-1)
return unpack(abc)
end
return btn
end
function lOS.boxClear(tx1,ty1,tx2,ty2)
local x1,y1,x2,y2 = tx1,ty1,tx2,ty2
if x1 > x2 then
x2,x1 = x1,x2
end
if y1 > y2 then
y2,y1 = y1,y2
end
clearline = ""
for t=x1,x2 do
clearline = clearline.." "
end
for l=y1,y2 do
term.setCursorPos(x1,l)
term.write(clearline)
end
end
local function run2( _tEnv, _sPath, ... )
local tArgs = table.pack( ... )
local tEnv = _tEnv
setmetatable( tEnv, { __index = _G } )
local fnFile,err
if fs.exists("window") then
fnFile, err = loadfile( _sPath, tEnv )
else
fnFile, err = loadfile( _sPath, nil, tEnv )
end
if fnFile then
local returned = {pcall( function()
return table.unpack({fnFile( table.unpack( tArgs, 1, tArgs.n ) )})
end )}
local ok, err = returned[1],returned[2]
table.remove(returned,1)
if not ok then
table.remove(returned,1)
if err and err ~= "" then
--printError( err )
end
return false,err,table.unpack(returned)
end
return true,table.unpack(returned)
end
if err and err ~= "" then
--printError( err )
end
return false,err
end
local function createShellEnv( sDir , tWindow , sPath)
local tEnv = {}
tEnv[ "shell" ] = lUtils.instantiate(shell)
if sPath then
tEnv.shell.getRunningProgram = function() return sPath end
end
tEnv[ "multishell" ] = multishell
local package = {}
package.loaded = {
_G = _G,
bit32 = bit32,
coroutine = coroutine,
math = math,
package = package,
string = string,
table = table,
}
package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua;/LevelOS/modules/?.lua;/LevelOS/modules/?/init.lua"
if turtle then
package.path = package.path..";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua"
elseif command then
package.path = package.path..";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua"
end
package.config = "/\n;\n?\n!\n-"
package.preload = {}
package.loaders = {
function( name )
if package.preload[name] then
return package.preload[name]
else
return nil, "no field package.preload['" .. name .. "']"
end
end,
function( name )
local fname = string.gsub(name, "%.", "/")
local sError = ""
for pattern in string.gmatch(package.path, "[^;]+") do
local sPath = string.gsub(pattern, "%?", fname)
if sPath:sub(1,1) ~= "/" then
sPath = fs.combine(sDir, sPath)
end
if fs.exists(sPath) and not fs.isDir(sPath) then
local fnFile, sError = loadfile( sPath, tEnv )
if fnFile then
return fnFile, sPath
else
return nil, sError
end
else
if #sError > 0 then
sError = sError .. "\n"
end
sError = sError .. "no file '" .. sPath .. "'"
end
end
return nil, sError
end
}
local sentinel = {}
local function require( name )
if type( name ) ~= "string" then
error( "bad argument #1 (expected string, got " .. type( name ) .. ")", 2 )
end
if package.loaded[name] == sentinel then
error("Loop detected requiring '" .. name .. "'", 0)
end
if package.loaded[name] then
return package.loaded[name]
end
local sError = "Error loading module '" .. name .. "':"
for n,searcher in ipairs(package.loaders) do
local loader, err = searcher(name)
if loader then
package.loaded[name] = sentinel
local result = loader( err )
if result ~= nil then
package.loaded[name] = result
return result
else
package.loaded[name] = true
return true
end
else
sError = sError .. "\n" .. err
end
end
error(sError, 2)
end
tEnv["package"] = package
tEnv["require"] = require
tEnv["LevelOS"] = {self={window=tWindow}}
local lAPI = tEnv["LevelOS"]
--local s = tEnv["shell"]
--local ms = tEnv["multishell"]
--TEMP DISABLED
local s = {}
local ms = {}
if not lOS.oldterm then
lOS.oldterm = oldterm
if not oldterm then
lOS.oldterm = term.native() -- DANGEROUS
end
end
local w,h = lOS.oldterm.getSize()
if tWindow ~= nil and lOS.wins ~= nil then
local win = tWindow
local function setWin( x, y, width, height, mode )
local x,y,width,height,mode = x, y, width, height, mode
if type(x) == "string" then
mode = x
x,y = win.win.getPosition()
width,height = win.win.getSize()
elseif type(width) ~= "number" then
width,height,mode = x,y,width
x,y = win.win.getPosition()
end
win.win.reposition(x,y,width,height)
os.queueEvent("term_resize")
if mode then
if mode ~= "background" and win.winMode == "background" then
local pID
for p=1,#lOS.processes do
if lOS.processes[p] == win then
pID = p
break
end
end
if pID then
os.queueEvent("window_open",pID,tostring(win))
end
elseif mode == "background" and win.winMode ~= "background" then
local wID
for i=1,#lOS.wins do
if lOS.wins[i] == win then
wID = i
break
end
end
if wID then
os.queueEvent("window_close",wID,tostring(win))
end
end
win.winMode = mode
end
end
local function pullEvent()
win.events = "all"
local e = {os.pullEventRaw()}
win.events = "default"
return table.unpack(e)
end
local function setTitle(title)
if type(title) == "string" and title ~= "" then
win.title = title
return true
else
return false
end
end
local function maximize()
if win.win ~= nil then
local w,h = lOS.wAll.getSize()
local off = 0
if win.winMode == "windowed" then
off = 1
end
win.snap = {x=true,y=true,oPos={win.win.getPosition()},oSize={win.win.getSize()}}
win.win.reposition(1,1+off,w,h-lOS.tbSize-off)
return true
end
return false
end
local function minimize()
os.queueEvent("window_minimize",lOS.cWin,tostring(tWindow))
end
local function focus()
for k,v in pairs(lOS.processes) do
if v == win then
os.queueEvent("window_focus",k,tostring(v))
break
end
end
end
lAPI.pullEvent = pullEvent
lAPI.setWin = setWin
lAPI.setTitle = setTitle
lAPI.maximize = maximize
lAPI.minimize = minimize
lAPI.focus = focus
tWindow.env = tEnv
local srun = tEnv.shell.run
function ms.launch(env, path, ...)
local args = {...}
local function func(win)
local oEnv = createShellEnv(fs.getDir(path),win)
env["LevelOS"] = oEnv["LevelOS"]
return run2(env, path, table.unpack(args))
end
lOS.newWin(func,path)
end
function ms.getCount()
return #lOS.wins
end
function ms.setTitle(n, title)
lOS.wins[n].title = title
end
function ms.setFocus(n)
if n >= 1 and n <= #lOS.wins then
local w = lOS.wins[n]
table.remove(lOS.wins,n)
table.insert(lOS.wins,w)
return true
else
return false
end
end
function ms.getTitle(n)
if n >= 1 and n <= #lOS.wins then
return lOS.wins[n].title
end
end
function s.execute(command, ...)
local sPath = s.resolveProgram(command)
if sPath ~= nil then
local sTitle = lUtils.getFileName(sPath)
sTitle = (sTitle:gsub("^%l", string.upper))
lAPI.setTitle(sTitle)
local sDir = fs.getDir(sPath)
local env = createShellEnv(sDir,lAPI.self.window)
env.arg = { [0] = command, ... }
local result = run2(env, sPath, ...)
return result
else
printError("No such program")
return false
end
end
--function tEnv.shell.run(...)
--if ({...})[2] and fs.exists(({...})[2]) then
--setTitle(lUtils.getFileName(({...})[1]).." - "..lUtils.getFileName(({...})[2]))
--else
--setTitle(lUtils.getFileName(({...})[1]))
--end
--srun(...)
--end
-- do the above once we port everything to lOS.run
end
return tEnv
end
lOS.createShellEnv = createShellEnv
lUtils.shapescape = {}
if true then
local shape = lUtils.shapescape
function shape.createRectangle(x1,y1,x2,y2,fillColor,borderColor)
local sCol = {fillColor or term.getBackgroundColor(),borderColor or 0}
local tObj = {event={mouse_click={function() end,-1},mouse_up={function() end,-1},focus={function() end,-1},update={function() end,-1},render={function() end,-1},Coroutine={function() end,-1}},type="rect",color=sCol[1],border={type=1,color=sCol[2]},x1=x1,y1=y1,x2=x2,y2=y2}
--slide.objs[#slide.objs+1] = tObj
local w,h = term.getSize()
lUtils.shapescape.renderSlide({win=window.create(term.current(),1,1,w,h,false),objs={tObj}})
return tObj
end
function shape.createText(txt,x1,y1,x2,y2,fillColor,borderColor,textColor)
local tObj = shape.createRectangle(x1,y1,x2,y2,fillColor or 0,borderColor or 0)
tObj.type = "text"
tObj.txtcolor = textColor or term.getTextColor()
tObj.txt = txt
return tObj
end
function shape.createWindow(x1,y1,x2,y2)
local tObj = shape.createRectangle(x1,y1,x2,y2)
tObj.type = "window"
tObj.color = colors.black
tObj.border.color = 0
tObj.render = nil
tObj.update = nil
local w,h = term.getSize()
lUtils.shapescape.renderSlide({win=window.create(term.current(),1,1,w,h,false),objs={tObj}})
return tObj
end
function shape.createInputbox(x1,y1,x2,y2,fillColor,borderColor,textColor)
local tObj = shape.createText("",x1,y1,x2,y2,fillColor,0,textColor)
tObj.type = "input"
return tObj
end
end
local align = {}
function align.left(slide,offset)
local w,h = term.getSize()
return offset
end
function align.right(slide,offset)
local w,h = term.getSize()
return w-offset
end
function align.top(slide,offset)
local w,h = term.getSize()
return offset
end
function align.bottom(slide,offset)
local w,h = term.getSize()
return h-offset
end
function align.center(slide,offset,vert)
local w,h = term.getSize()
if vert then
return math.ceil(h/2)-offset
else
return math.ceil(w/2)-offset
end
end
local generic = {}
function generic.align(obj)
--if obj.ox1 == nil or obj.oy1 == nil then
--obj.ox1,obj.oy1,obj.ox2,obj.oy2 = obj.x1,obj.y1,obj.x2,obj.y2
--end
local w,h = term.getSize()
if obj.snap.Left == "Snap right" then
if not obj.ox1 then
obj.ox1 = w-obj.x1
end
obj.x1 = align.right(slide,obj.ox1)
elseif obj.snap.Left == "Snap center" then
if not obj.ox1 then
obj.ox1 = math.ceil(w/2)-obj.x1
end
obj.x1 = align.center(slide,obj.ox1)
else
obj.ox1 = nil
end
if obj.snap.Right == "Snap right" then
if not obj.ox2 then
obj.ox2 = w-obj.x2
end
obj.x2 = align.right(slide,obj.ox2)
elseif obj.snap.Right == "Snap center" then
if not obj.ox2 then
obj.ox2 = math.ceil(w/2)-obj.x2
end
obj.x2 = align.center(slide,obj.ox2)
else
obj.ox2 = nil
end
if obj.snap.Top == "Snap bottom" then
if not obj.oy1 then
obj.oy1 = h-obj.y1
end
obj.y1 = align.bottom(slide,obj.oy1)
elseif obj.snap.Top == "Snap center" then
if not obj.oy1 then
obj.oy1 = math.ceil(h/2)-obj.y1
end
obj.y1 = align.center(slide,obj.oy1,true)
else
obj.oy1 = nil
end
if obj.snap.Bottom == "Snap bottom" then
if not obj.oy2 then
obj.oy2 = h-obj.y2
end
obj.y2 = align.bottom(slide,obj.oy2)
elseif obj.snap.Bottom == "Snap center" then
if not obj.oy2 then
obj.oy2 = math.ceil(h/2)-obj.y2
end
obj.y2 = align.center(slide,obj.oy2,true)
else
obj.oy2 = nil
end
end
function lUtils.shapescape.addScript(tShp,id,ev,assets,LevelOS,slides)
if not tShp.event then
tShp.event = {}
end
-- run script with environment
local function getEnv(tShape)
local tempWin
if LevelOS then
tempWin = LevelOS.self.window
end
local tEnv = createShellEnv("",tempWin)
if LevelOS then
tEnv.LevelOS = LevelOS
end
setmetatable(tEnv,{__index=_G})
tEnv.self = tShape
return tEnv
end
local function getSlide()
return slides[slides.cSlide]
end
local function getSlides()
return slides
end
local function setSlide(n)
if slides[n] then
slides.cSlide = n
os.queueEvent("shapescape_change_slide")
os.pullEvent()
return true
else
return false
end
end
local function exit(...)
slides.stop = true
slides["return"] = {...}
end
local getEvent = function() return end
local tEnv = getEnv(tShp)
tEnv.shapescape = {getEvent=getEvent,getSlide=getSlide,getSlides=getSlides,setSlide=setSlide,exit=exit}
tShp.tEnv = tEnv
local sFunc,err = load(assets[id].content,"@"..assets[id].name,"bt",tEnv)
if not sFunc then
function sFunc() printError(err) end
end
if ev == "Coroutine" and tShp.type == "window" then
tShp.event[ev] = {function(tShape,e,...) tShape.tEnv.shapescape.getEvent = function() return unpack(e) end local ok,err = pcall(sFunc,...) if not ok then print(err) end end,id}
else
tShp.event[ev] = {function(tShape,e,...) tShape.tEnv.shapescape.getEvent = function() return unpack(e) end local ok,err = pcall(sFunc,...) if not ok then lUtils.popup("Error",err,31,11,{"OK"}) end end,id}
end
end
function lUtils.shapescape.renderSlide(slide,static,args)
local oterm = term.current()
term.redirect(slide.win)
term.setBackgroundColor(colors.white)
term.clear()
local cursor
for o=1,#slide.objs do
--[[if slide.objs[o].snap then
generic.align(slide.objs[o])
end]]
if slide.objs[o].type == "rect" or slide.objs[o].type == "text" or slide.objs[o].type == "window" or slide.objs[o].type == "input" then
local s = slide.objs[o]
if not slide.objs[o].render then
local self = slide.objs[o]
if slide.objs[o].type == "window" then
if not static then
function self.render()
local restore = cRestore()
if self.snap then
generic.align(self)
end
if self.color ~= 0 then
if not self.window then
self.window = window.create(term.current(),self.x1,self.y1,(self.x2-self.x1)+1,(self.y2-self.y1)+1,false)
end
local x,y = self.window.getPosition()
local w,h = self.window.getSize()
if x ~= self.x1 or y ~= self.y1 or w ~= (self.x2-self.x1)+1 or h ~= (self.y2-self.y1)+1 then
self.window.reposition(self.x1,self.y1,(self.x2-self.x1)+1,(self.y2-self.y1)+1)
end
for l=1,(self.y2-self.y1)+1 do
term.setCursorPos(self.x1,self.y1+(l-1))
term.blit(self.window.getLine(l))
end
end
cRestore(restore)
end
else
local lines
local function genLines()
lines = {}
--[[local fg = lUtils.toBlit(self.color or colors.white)
local bg = lUtils.toBlit(self.color or colors.white)]]
local fg,bg
if self.color ~= colors.black then
fg = lUtils.toBlit(self.color)
bg = lUtils.toBlit(self.border.color ~= nil and self.border.color or colors.black)
if bg == nil then
bg = lUtils.toBlit(colors.black)
end
else
fg = lUtils.toBlit(self.color)
bg = lUtils.toBlit(self.border.color ~= nil and self.border.color or colors.white)
if bg == nil then
bg = lUtils.toBlit(colors.white)
end
end
for y=self.y1,self.y2 do
lines[#lines+1] = {"","",""}
for x=self.x1,self.x2 do
lines[#lines][1] = lines[#lines][1]..string.char(math.random(129,159))
if math.random(1,2) == 2 then
lines[#lines][2] = lines[#lines][2]..bg
lines[#lines][3] = lines[#lines][3]..fg
else
lines[#lines][2] = lines[#lines][2]..fg
lines[#lines][3] = lines[#lines][3]..bg
end
end
end
end
genLines()
local x1,y1,x2,y2 = self.x1,self.y1,self.x2,self.y2
local c1,c2 = self.color,self.border.color
function self.render()
if self.snap then
generic.align(self)
end
if x1 ~= self.x1 or y1 ~= self.y1 or x2 ~= self.x2 or y2 ~= self.y2 or c1 ~= self.color or c2 ~= self.border.color then
x1,y1,x2,y2 = self.x1,self.y1,self.x2,self.y2
c1,c2 = self.color,self.border.color
genLines()
end
for l=1,#lines do
term.setCursorPos(self.x1,self.y1+(l-1))
term.blit(unpack(lines[l]))
end
end
end
else
function self.render()
local restore = cRestore()
if self.snap then
generic.align(self)
end
if self.color ~= 0 then
term.setBackgroundColor(self.color)
--term.setCursorPos(self.x1,self.y1)
for y=self.y1,self.y2 do
term.setCursorPos(self.x1,y)
term.write(string.rep(" ",self.x2-(self.x1-1)))
end
--lOS.boxClear(self.x1,self.y1,self.x2,self.y2)
end
if self.border and self.border.color ~= 0 then
term.setTextColor(self.border.color)
lUtils.border(self.x1,self.y1,self.x2,self.y2,"transparent")
end
if self.image then
if self.border and self.border.color ~= 0 then
lUtils.renderImg(self.image,self.x1+1,self.y1+1,nil,true)
else
lUtils.renderImg(self.image,self.x1,self.y1,nil,true)
end
end
if self.type == "input" then
-- gen shape and make it provide shape
if not self.fRender then
lUtils.input(self.x1,self.y1,self.x2,self.y2,nil,nil,self)
end
self.fRender()
elseif self.txt then
if self.color == 0 then
term.setBackgroundColor(colors.white)
else
term.setBackgroundColor(self.color)
end
term.setTextColor(self.txtcolor)
term.setCursorPos(1,1)
if self.border and self.border.color ~= 0 and self.y2 >= self.y1+2 then
lUtils.textbox(self.txt,self.x1+1,self.y1+1,self.x2-1,self.y2-1,true)
else
if static and self.txt == "" and not self.input then
lUtils.textbox("...",self.x1,self.y1,self.x2,self.y2,true)
else
lUtils.textbox(self.txt,self.x1,self.y1,self.x2,self.y2,true)
end
end
end
if not self.state then
cRestore(restore)
end
end
end
end
s.render()
if not static and s.event and s.event.render and s.event.render[1] then
s.event.render[1](s,nil,args)
end
if not slide.objs[o].update then
local self = slide.objs[o]
function self.update(args,...)
-- continue this
local e = {...}
if e[1] == "mouse_click" or e[1] == "mouse_up" then
if e[3] >= self.x1 and e[4] >= self.y1 and e[3] <= self.x2 and e[4] <= self.y2 then
if self.event[e[1]] and self.event[e[1]][1] then
self.event[e[1]][1](self,e,table.unpack(args))
end
end
if e[1] == "mouse_up" and self.selected then
self.selected = false
end
end
if self.event.update then
-- dt when i can
self.event.update[1](self,e,table.pack(args))
end
if not self.coroutine and self.event.Coroutine[2] >= 0 then
self.coroutine = coroutine.create(function() self.event.Coroutine[1](self,{},table.unpack(args)) end)
end
if self.coroutine then
local oterm = term.current()
if self.window then
if self.snap then
generic.align(self)
end
local x,y = self.window.getPosition()
local w,h = self.window.getSize()
if x ~= self.x1 or y ~= self.y1 or w ~= (self.x2-self.x1)+1 or h ~= (self.y2-self.y1)+1 then
self.window.reposition(self.x1,self.y1,(self.x2-self.x1)+1,(self.y2-self.y1)+1)
end
term.redirect(self.window)
if string.find(e[1],"mouse") and e[3] and e[4] and not (self.tEnv and self.tEnv.LevelOS and self.tEnv.LevelOS.self and self.tEnv.LevelOS.self.window and self.tEnv.LevelOS.self.window.events == "all") then
e[3] = e[3]-(self.x1-1)
e[4] = e[4]-(self.y1-1)
end
end
coroutine.resume(self.coroutine,unpack(e))
-- if blink was enabled set cursor back to this window after everything blablabla
term.redirect(oterm)
end
if self.type == "input" then
if not self.fUpdate then
lUtils.input(self.x1,self.y1,self.x2,self.y2,nil,nil,nil,self)
end
self.fUpdate(...)
end
end
end
elseif slide.objs[o].type == "triangle" then
end
if not slide.objs[o].remove then
local self = slide.objs[o]
local sl = slide
slide.objs[o].remove = function()
for s=1,#sl.objs do
if sl.objs[s] == self then
sl.objs[s] = nil
return true
end
end
end
end
end
term.redirect(oterm)
end
function lUtils.shapescape.run(slides,...)
local args = table.pack(...)
local oterm = term.current()
if not slides.cSlide then
slides.cSlide = 1
end
term.setBackgroundColor(colors.white)
local cTerm = term.current()
local sWin = window.create(cTerm,1,1,term.getSize())
sWin.setVisible(false)
--sWin.setVisible(false)
sWin.setBackgroundColor(colors.white)
sWin.setTextColor(colors.black)
sWin.clear()
slides[slides.cSlide].win = sWin
--lUtils.shapescape.renderSlide(slides[slides.cSlide])
local started = 0
os.queueEvent("term_resize")
while not slides.stop do
local cursor
local s = slides[slides.cSlide]
local e
if started < 2 then
e = {"shapescape_start"}
started = started+1
else
e = {os.pullEvent()}
end
sWin.reposition(1,1,term.getSize())
local cSlide = slides.cSlide
local oterm = term.current()
local function ssUpdateFunc()
for t=1,#s.objs do
local o = s.objs[t]
if o and o.update then
local ok,err = pcall(function() o.update(args,unpack(e)) end)
if not ok then
error(err,0)
end
if o.window and o.window.getCursorBlink() == true then
cursor = {pos={o.window.getCursorPos()},color=o.window.getTextColor()}
cursor.pos[1] = o.x1+(cursor.pos[1]-1)
cursor.pos[2] = o.y1+(cursor.pos[2]-1)
end
end
end
end
local ssUpdate = coroutine.create(ssUpdateFunc)
term.redirect(sWin)
local ok,err = coroutine.resume(ssUpdate)
if not ok then
error(err,0)
end
while coroutine.status(ssUpdate) ~= "dead" do
term.redirect(oterm)
sWin.setVisible(true)
sWin.setVisible(false)
local e = {os.pullEvent()}
term.redirect(sWin)
coroutine.resume(ssUpdate,table.unpack(e))
end
if cSlide ~= slides.cSlide then
-- fuck you bitchass cunt
if not slides[slides.cSlide].win then
local w,h = term.getSize()
slides[slides.cSlide].win = window.create(cTerm,1,1,w,h,false)
end
sWin = slides[slides.cSlide].win
term.redirect(sWin)
lUtils.shapescape.renderSlide(slides[slides.cSlide])
end
term.setCursorBlink(false)
lUtils.shapescape.renderSlide(s)
if cursor then
term.setCursorPos(unpack(cursor.pos))
term.setTextColor(cursor.color)
term.setCursorBlink(true)
end
-- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa it doesnt WORK
term.redirect(oterm)
sWin.setVisible(true)
sWin.setVisible(false)
-- invis rendering during update broke eberythinf so now its notv invis
--for t=1,({sWin.getSize()})[2] do
--term.setCursorPos(1,t)
--term.blit(sWin.getLine(t))
--end
end
if not LevelOS then
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
term.setCursorPos(1,1)
end
return unpack(slides["return"])
end
function lUtils.getArgs(fn)
local args = {}
local info = debug.getinfo(fn)
for i=1, info.nparams do
args[i] = debug.getlocal(fn,i) or "?"
end
if info.vararg then
args[#args + 1] = "..."
end
return args
end
function lUtils.randStr(keyLength,num,symb)
local upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local lowerCase = "abcdefghijklmnopqrstuvwxyz"
local numbers = "0123456789"
local symbols = "!@#$%&()*+-,./\\:;<=>?^[]{}"
local characterSet = upperCase .. lowerCase
if num then
characterSet = characterSet..numbers
end
if symb then
characterSet = characterSet..symbols
end
local output = ""
for i = 1, keyLength do
local rand = math.random(#characterSet)
output = output .. string.sub(characterSet, rand, rand)
end
return output
end
function lUtils.logout()
lOS.userID = nil
lOS.username = nil
if fs.exists("LevelOS/data/account2.txt") then
fs.delete("LevelOS/data/account2.txt")
end
lOS.notification("You are now logged out of Leveloper Services.")
end
function lUtils.login(username,password,isToken,rememberme)
local res,err
local xtra = ""
if isToken then
res,err = http.post("https://os.leveloper.cc/auth.php","username="..textutils.urlEncode(username).."&token="..textutils.urlEncode(password))
else
if rememberme then
xtra = "&rememberme=true"
end
res,err = http.post("https://os.leveloper.cc/auth.php","username="..textutils.urlEncode(username).."&password="..textutils.urlEncode(password)..xtra)
end
if res then
local str = res.readAll()
local oldstr = str
str = lUtils.getField(str,"msg")
if str:find("Welcome") then
lUtils.fwrite("LevelOS/data/account.txt",username)
if rememberme then
local token = lUtils.getField(oldstr,"token")
lUtils.fwrite("LevelOS/data/account2.txt",token)
end
local userID = res.getResponseHeaders()["Set-Cookie"]
return userID,str
else
return nil,str
end
else
return nil,err
end
end
function lUtils.getField(thing,fieldname)
if string.find(thing,"<"..fieldname..">",1,true) ~= nil and string.find(thing,"</"..fieldname..">",1,true) ~= nil then
begin = nil
ending = nil
trash,begin = string.find(thing,"<"..fieldname..">",1,true)
ending,ending2 = string.find(thing,"</"..fieldname..">",begin+1,true)
if begin ~= nil and ending ~= nil then
return string.sub(thing,begin+1,ending-1),string.sub(thing,1,trash-1)..string.sub(thing,ending2+1,string.len(thing))
end
end
return nil,thing
end
function lUtils.compare(a, b)
for k,v in pairs(a) do
if (type(v) == "table" and type(b[k]) == "table" and not lUtils.compare(b[k], v)) or b[k] ~= v then return false end
end
for k,v in pairs(b) do
if (type(v) == "table" and type(a[k]) == "table" and not lUtils.compare(a[k], v)) or a[k] ~= v then return false end
end
return true
end
function lUtils.centerText(text)
local x,y = term.getSize()
local x2,y2 = term.getCursorPos()
term.setCursorPos(math.floor(x / 2 - text:len() / 2) + 1, y2)
term.write(text)
end
function lUtils.outline(x1,y1,x2,y2)
local c1 = term.getTextColor()
local c2 = term.getBackgroundColor()
term.setCursorPos(x1,y1)
term.setBackgroundColor(c1)
term.setTextColor(c2)
local a
for a=x1,x2 do
term.write("\143")
end
for a=y1,y2 do
term.setCursorPos(x1,y1-1+a)
term.write("\149")
end
term.setBackgroundColor(c2)
term.setTextColor(c1)
for a=y1,y2 do
term.setCursorPos(x2,y1-1+a)
term.write("\149")
end
term.setCursorPos(x1,y2)
for a=x1,x2 do
term.write("\131")
end
end
function lUtils.border(x1,y1,x2,y2,mode,layer)
term.setCursorPos(x1,y1)
local l = layer or 3
local w,h = x2-(x1-1),y2-(y1-1)
local bg,fg = term.getBackgroundColor(),term.getTextColor()
local inved = false
local function inv()
if not inved then
term.setBackgroundColor(fg)
term.setTextColor(bg)
inved = true
else
term.setBackgroundColor(bg)
term.setTextColor(fg)
inved = false
end
end
local function setBG()
if mode and mode == "transparent" then
if not inved then
term.setBackgroundColor(lUtils.toColor(({lUtils.getPixel(term.current(),term.getCursorPos())})[3]))
else
term.setTextColor(lUtils.toColor(({lUtils.getPixel(term.current(),term.getCursorPos())})[3]))
end
end
end
--[[local function setFG()
if mode and mode == "transparent" then
term.setTextColor(lUtils.toColor(({lUtils.getPixel(term.current(),term.getCursorPos())})[3]))
end
end]]
if l == 1 then
inv()
setBG()
else
setBG()
end
if l == 2 then
term.write("\156")
elseif l == 3 then
term.write("\151")
elseif l == 1 then
term.write("\159")
end
if mode and mode == "transparent" then
for x=1,w-2 do
if l == 3 then
setBG()
term.write("\131")
elseif l == 2 then
setBG()
term.write("\140")
elseif l == 1 then
setBG()
term.write("\143")
end
end
else
if l == 3 then
term.write(string.rep("\131",w-2))
elseif l == 2 then
term.write(string.rep("\140",w-2))
elseif l == 1 then
term.write(string.rep("\143",w-2))
end
end
inv()
if l == 1 then
setBG()
else
setBG()
end
if l == 2 then
term.write("\147")
elseif l == 3 then
term.write("\148")
elseif l == 1 then
term.write("\144")
end
for y=y1+1,y2-1 do
term.setCursorPos(x2,y)
setBG()
term.write("\149")
inv()
term.setCursorPos(x1,y)
setBG()
term.write("\149")
if mode and mode == "fill" then
term.write(string.rep(" ",w-2))
end
inv()
end
term.setCursorPos(x1,y2)
if l == 3 then
setBG()
term.write("\138")
elseif l == 2 then
inv()
setBG()
term.write("\141")
elseif l == 1 then
setBG()
term.write("\130")
end
if mode and mode == "transparent" then
for x=1,w-2 do
if l == 3 then
setBG()
term.write("\143")
elseif l == 2 then
setBG()
term.write("\140")
elseif l == 1 then
setBG()
term.write("\131")
end
end
else
if l == 3 then
term.write(string.rep("\143",w-2))
elseif l == 2 then
term.write(string.rep("\140",w-2))
elseif l == 1 then
term.write(string.rep("\131",w-2))
end
end
if l == 3 then
setBG()
term.write("\133")
inv()
elseif l == 2 then
setBG()
term.write("\142")
elseif l == 1 then
setBG()
term.write("\129")
end
end
function lUtils.spairs(t, order)
-- collect the keys
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
-- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
table.sort(keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
end
function lUtils.splitStr(str,pat)
local t = {} -- NOTE: use {n = 0} in Lua-5.0
local fpat = "(.-)" .. pat
local last_end = 1
local s, e, cap = str:find(fpat, 1)
while s do
if s ~= 1 or cap ~= "" then
table.insert(t,cap)
end
last_end = e+1
s, e, cap = str:find(fpat, last_end)
end
if last_end <= #str then
cap = str:sub(last_end)
table.insert(t, cap)
end
return t
end
function lUtils.explorer(path,mode)
if path == nil or path == "" then
path = "/"
end
local title = "Explorer"
if mode == "SelFile" then
title = "Select File"
elseif mode == "SelFolder" then
title = "Select Folder"
end
local w,h = term.getSize()
local eW,eH = math.ceil(math.min(w*0.75,w-3)),math.ceil(math.min(h*0.75,h-1))
local a = {lUtils.openWin(title,"Program_Files/LevelOS/Explorer/main.lua "..path.." "..mode,math.ceil(w/2-eW/2),math.ceil(h/2-eH/2),eW,eH,true,false)}
if a[1] == false then return false end
table.remove(a,1)
if type(a[1]) == "table" then
a = a[1]
end
return table.unpack(a)
end
local to_colors, to_blit = {}, {}
for i = 1, 16 do
to_blit[2^(i-1)] = ("0123456789abcdef"):sub(i, i)
to_colors[("0123456789abcdef"):sub(i, i)] = 2^(i-1)
end
function lUtils.toColor(theblit)
return to_colors[theblit] or nil
end
function lUtils.toBlit(thecolor)
return to_blit[thecolor] or nil
end
function lUtils.instantiate(orig)
local function deepCopy(o, seen) -- so that "seen" doesn't appear in autocomplete
seen = seen or {}
if seen[o] then
return seen[o]
end
local copy
if type(o) == 'table' then
copy = {}
seen[o] = copy
for k,v in pairs(o) do
copy[deepCopy(k, seen)] = deepCopy(v, seen)
end
setmetatable(copy, deepCopy(getmetatable(o), seen))
else
copy = o
end
return copy
end
return deepCopy(orig)
end
local function tokenise( ... )
local sLine = table.concat( { ... }, " " )
local tWords = {}
local bQuoted = false
for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
if bQuoted then
table.insert( tWords, match )
else
for m in string.gmatch( match, "[^ \t]+" ) do
table.insert( tWords, m )
end
end
bQuoted = not bQuoted
end
return tWords
end
to_colors, to_blit = {}, {}
for i = 1, 16 do
to_blit[2^(i-1)] = ("0123456789abcdef"):sub(i, i)
to_colors[("0123456789abcdef"):sub(i, i)] = 2^(i-1)
end
function lOS.eventdelay()
local oldtime = os.time()
local timer = os.startTimer(0)
while true do
event,id = os.pullEvent("timer")
if id == timer then
local newtime = os.time()
return (newtime-oldtime)*50000
end
end
end
function lUtils.getFileType(filename)
if string.find(filename,"%.%w+$") == nil then
return ""
else
return string.sub(filename,string.find(filename,"%.%w+$"))
end
end
function lUtils.getFileName(filename,ext)
local f = filename
if string.find(filename," ") then
f = string.sub(filename,1,({string.find(filename," ")})[1]-1)
end
f = fs.getName(f)
if not ext or lUtils.getFileType(f) == ".llnk" then
f = string.sub(f,1,string.len(f)-string.len(lUtils.getFileType(f)))
end
return string.gsub(f,"_"," ")
end
function lUtils.getFilePath(filename)
local f = filename
if string.find(filename," ") then
f = string.sub(filename,1,({string.find(filename," ")})[1]-1)
end
return f
end
local strength
function lOS.checkinternet()
strenth = 0
local oldtime = os.time()
a = http.get("https://os.leveloper.cc/ping.php")
if a == nil or a == false then
strength = 0
else
newtime = os.time()
if newtime-oldtime < 0.005 then
strength = 3
elseif newtime-oldtime < 0.01 then
strength = 2
elseif newtime-oldtime >= 0.01 then
strength = 1
end
return strength,newtime-oldtime
end
return 0,0
end
function lOS.getInternet()
return strength or 0
end
function lOS.run( _sCommand, ... )
local sPath = shell.resolveProgram( _sCommand )
if sPath ~= nil then
local tWindow = {}
local tArgs = {...}
--term.write(textutils.serialize(tArgs))
if type(tArgs[1]) == "table" then
tWindow = tArgs[1]
table.remove(tArgs,1)
else
tWindow = nil
end
-- set window title
local sDir = fs.getDir( sPath )
local result = {run2( createShellEnv( sDir, tWindow, sPath ), sPath, table.unpack(tArgs) )}
return table.unpack(result)
else
return false,"No such program"
end
end
function lOS.newWin(func,rPath)
-- fuck me
end
function lOS.searchfor(arg,path)
local files = fs.list(path)
local folders = {}
local result = {}
local p = ""
if path ~= "" then
p = path.."/"
else
p = ""
end
for t=1,#files do
if fs.isDir(p..files[t]) then
folders[#folders+1] = p..files[t]
elseif string.find(string.lower(files[t]),string.lower(arg)) then
result[#result+1] = p..files[t]
end
end
for t=1,#folders do
local a = lOS.searchfor(arg,folders[t])
for b=1,#a do
result[#result+1] = a[b]
end
end
return result
end
function lOS.search(keyword,x,y,w,h,searchfile,animation)
local lines = {}
local slp = function() os.sleep(0) end
for t=1,h do
lines[t] = {"","",""}
end
lines[h] = {"\138","0","8"}
lines[h-1] = {"\149","8","0"}
lines[h-2] = {"\151","8","0"}
lines[1] = {"\151","8","7"}
lines[2] = {"\149","8","7"}
for t=1,w-2 do
if t < math.floor(w/2) then
lines[1][1] = lines[1][1].."\131"
lines[1][2] = lines[1][2].."8"
lines[1][3] = lines[1][3].."7"
lines[2][1] = lines[2][1].." "
lines[2][2] = lines[2][2].."0"
lines[2][3] = lines[2][3].."7"
else
lines[1][1] = lines[1][1].."\131"
lines[1][2] = lines[1][2].."8"
lines[1][3] = lines[1][3].."0"
lines[2][1] = lines[2][1].." "
lines[2][2] = lines[2][2].."0"
lines[2][3] = lines[2][3].."0"
end
lines[h][1] = lines[h][1].."\143"
lines[h][2] = lines[h][2].."0"
lines[h][3] = lines[h][3].."8"
lines[h-1][1] = lines[h-1][1].." "
lines[h-1][2] = lines[h-1][2].."f"
lines[h-1][3] = lines[h-1][3].."0"
lines[h-2][1] = lines[h-2][1].."\131"
lines[h-2][2] = lines[h-2][2].."8"
lines[h-2][3] = lines[h-2][3].."0"
end
lines[1][1] = lines[1][1].."\148"
lines[1][2] = lines[1][2].."0"
lines[1][3] = lines[1][3].."8"
lines[2][1] = lines[2][1].."\149"
lines[2][2] = lines[2][2].."0"
lines[2][3] = lines[2][3].."8"
lines[h][1] = lines[h][1].."\133"
lines[h][2] = lines[h][2].."0"
lines[h][3] = lines[h][3].."8"
lines[h-1][1] = lines[h-1][1].."\149"
lines[h-1][2] = lines[h-1][2].."0"
lines[h-1][3] = lines[h-1][3].."8"
lines[h-2][1] = lines[h-2][1].."\148"
lines[h-2][2] = lines[h-2][2].."0"
lines[h-2][3] = lines[h-2][3].."8"
term.setCursorPos(x,y+(h-1))
term.blit(table.unpack(lines[h]))
term.setCursorPos(x,y+(h-1)-1)
term.blit(table.unpack(lines[h-2]))
slp()
term.setCursorPos(x,y+(h-1)-1)
term.blit(table.unpack(lines[h-1]))
term.setCursorPos(x,y+(h-1)-2)
term.blit(table.unpack(lines[h-2]))
slp()
for t=4,h,3 do
term.setCursorPos(x,y+(h-1)-(t-1))
term.blit(table.unpack(lines[1]))
for a=1,t-4 do
term.setCursorPos(x,y+(h-1)-(t-1)+(a))
term.blit(table.unpack(lines[2]))
end
slp()
end
term.setCursorPos(x,y)
term.blit(table.unpack(lines[1]))
for a=1,h-4 do
term.setCursorPos(x,y+a)
term.blit(table.unpack(lines[2]))
end
search = lUtils.makeEditBox("Search",w-3,1)
search.lines = {""}
local function searchy()
while true do
lUtils.drawEditBox(search,x+2,y+(h-2),0,0,string.len(search.lines[1])+1,1,true,false)
end
end
local scrl = -1
local sel = 1
local btns = {}
local function rendersearch()
for a=1,h-4 do
term.setCursorPos(x,y+a)
term.blit(table.unpack(lines[2]))
end
local ox,oy = term.getCursorPos()
local txtcolor = term.getTextColor()
term.setCursorPos(x+(w/2),y+1)
term.setBackgroundColor(colors.white)
term.setTextColor(colors.black)
-- write("lastkey = "..lastkey.." and os.time() = "..os.time())
-- selected box
term.setBackgroundColor(colors.white)
lOS.boxClear(x+math.ceil(w/2),y+1,x+(w-2),y+(h-4))
if result[sel] ~= nil then
local tPath = ""
if string.gsub(result[sel],fs.getName(result[sel]),"") ~= "" then
tPath = string.gsub(result[sel],fs.getName(result[sel]),"")
else
tPath = "root"
end
term.setCursorPos(x+(w/2)+1,y+2)
local ay = y+2
local ax = x+(math.floor(w/2))+6
local aw = (math.ceil(w/2-1)-6)
term.setTextColor(colors.lightGray)
write("Name")
term.setTextColor(colors.black)
for t=1,math.ceil(string.len(fs.getName(result[sel]))/aw) do
term.setCursorPos(ax,ay)
write(string.sub(fs.getName(result[sel]),1+((t-1)*aw),t*aw))
ay = ay+1
end
term.setCursorPos(x+(w/2)+1,ay)
term.setTextColor(colors.lightGray)
write("Path")
term.setTextColor(colors.black)
for t=1,math.ceil(string.len(tPath)/aw) do
term.setCursorPos(ax,ay)
write(string.sub(tPath,1+((t-1)*aw),t*aw))
ay = ay+1
end
if fs.exists(result[sel].."updater") then
term.setTextColor(colors.gray)
term.setCursorPos(x+(w/2)+1,ay)
term.write("(Auto updates)")
ay = ay+1
end
ay = ay+1
local aline = ""
for t=ax-5,x+(w-2) do
aline = aline.." "
end
function filetype(filename)
if string.find(filename,"%.%w+$") == nil then
return ""
else
return string.sub(filename,string.find(filename,"%.%w+$"))
end
end
if ay+1 <= y+(h-4) then
if filetype(fs.getName(result[sel])) == ".lua" then
btns = {}
btns[1] = {" Execute",ax-5,ay}
btns[2] = {" Edit",ax-5,ay+2}
btns[3] = {" Create Shortcut",ax-5,ay+4}
elseif filetype(fs.getName(result[sel])) == ".txt" then
btns = {}
btns[1] = {" Edit",ax-5,ay}
btns[2] = {" Create Shortcut",ax-5,ay+2}
else
btns = {}
btns[1] = {" Execute",ax-5,ay}
btns[2] = {" Edit",ax-5,ay+2}
btns[3] = {" Create Shortcut",ax-5,ay+4}
end
end
for t=1,#btns do
if t==1 then
term.setCursorPos(ax-5,btns[t][3]-1)
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.white)
term.write(string.gsub(aline,"%s","\143"))
end
term.setCursorPos(btns[t][2],btns[t][3])
term.setTextColor(colors.black)
term.write(btns[t][1]..string.sub(aline,string.len(btns[t][1])+1,string.len(aline)))
if t < #btns then
term.setCursorPos(ax-5,btns[t][3]+1)
term.setTextColor(colors.white)
term.write(string.gsub(aline,"%s","\140"))
else
term.setCursorPos(ax-5,btns[t][3]+1)
term.setTextColor(colors.lightGray)
term.setBackgroundColor(colors.white)
term.write(string.gsub(aline,"%s","\131"))
end
end
end
for t=1,#result do
if result[t] ~= nil then
if sel == t then
term.setBackgroundColor(colors.lightGray)
else
term.setBackgroundColor(colors.gray)
end
if fs.exists(result[t].."updater") ~= nil then
for i=1,#result do
if result[i] ~= nil and result[i] == result[t].."updater" then
table.remove(result,i)
end
end
end
if y+((t-1)*2)-scrl > y and y+((t-1)*2)-scrl <= y+(h-4) then
term.setCursorPos(x,y+((t-1)*2)-scrl)
term.setTextColor(colors.lightGray)
term.write("\149")
for i=1,(w/2)-2 do
write(" ")
end
term.setCursorPos(x+1,y+((t-1)*2)-scrl)
term.setTextColor(colors.white)
if string.len(fs.getName(result[t])) > (w/2)-2 then
term.write(string.sub(fs.getName(result[t]),1,(w/2)-5).."...")
else
term.write(fs.getName(result[t]))
end
end
if 1+y+((t-1)*2)-scrl > y and 1+y+((t-1)*2)-scrl <= y+(h-4) then
term.setCursorPos(x,1+y+((t-1)*2)-scrl)
term.setTextColor(colors.lightGray)
term.write("\149")
for i=1,(w/2)-2 do
write(" ")
end
term.setCursorPos(x+1,1+y+((t-1)*2)-scrl)
if sel == t then
term.setTextColor(colors.gray)
end
if string.gsub(result[t],fs.getName(result[t]),"") ~= "" then
if string.len(string.gsub(result[t],fs.getName(result[t]),"")) > (w/2)-2 then
term.write(string.sub(string.gsub(result[t],fs.getName(result[t]),""),1,(w/2)-5).."...")
else
term.write(string.gsub(result[t],fs.getName(result[t]),""))
end
else
term.write("root")
end
end
end
end
term.setCursorPos(ox,oy)
term.setTextColor(txtcolor)
end
lastkey = os.time()
lastsearch = ""
result = {}
term.setBackgroundColor(colors.white)
-- lOS.boxClear(x+math.ceil(w/2),y+1,x+(w-2),y+(h-2))
local function regevents()
local atimer = os.startTimer(0.5)
while true do
local a = {os.pullEvent()}
if a[1] == "mouse_click" then
if a[3] < x or a[3] > x+(w-1) or a[4] < y then -- maybe check underneath search box too but i dont think thats necessary for now
return false,""
else
for t=1,#btns do
if a[4] == btns[t][3] and a[3] >= btns[t][2] and result[sel] ~= nil then
if string.gsub(btns[t][1],"%s","") == "Edit" and fs.exists(result[sel].."updater") == true then
result[sel] = result[sel].."updater"
end
return true,result[sel],string.gsub(btns[t][1],"%s","")
end
end
end
elseif a[1] == "key" then
lastkey = os.time()
if a[2] == keys.enter and search.lines[1] ~= "" and result[sel] ~= nil then
return true,result[sel],"Execute"
elseif a[2] == keys.down then
if result[sel+1] ~= nil then
sel = sel+1
while y+((sel-1)*2)-scrl <= y do
scrl = scrl-1
end
while 1+y+((sel-1)*2)-scrl > y+(h-4) do
scrl = scrl+1
end
rendersearch()
end
elseif a[2] == keys.up then
if sel > 1 then
sel = sel-1
while y+((sel-1)*2)-scrl <= y do
scrl = scrl-1
end
while 1+y+((sel-1)*2)-scrl > y+(h-4) do
scrl = scrl+1
end
rendersearch()
end
end
elseif a[1] == "timer" and a[2] == atimer then
atimer = os.startTimer(0.5)
if os.time() > lastkey+0.01 and search.lines[1] ~= lastsearch then
lastkey = os.time()
lastsearch = search.lines[1]
result = lOS.searchfor(search.lines[1],"")
sel = 1
rendersearch()
end
elseif a[1] == "mouse_scroll" and a[3] >= x and a[3] <= x+((w/2)-1) and a[4] >= y and a[4] <= y+(h-4) then
if scrl+a[2] >= -1 then
scrl = scrl+a[2]
rendersearch()
end
end
end
end
i = {}
parallel.waitForAny(searchy,function() i={regevents()} end)
term.setCursorBlink(false)
return table.unpack(i)
end
function lUtils.littlewin(oriwin,w,h)
local oW,oH = oriwin.getSize()
local oldwin = {lines={}}
for y=1,oH do
oldwin.lines[y] = {oriwin.getLine(y)}
end
local newwin = {lines={}}
local ystep = oH/h
local xstep = oW/w
for y=1,h do
local xstart = math.floor(xstep/2)
if xstart < 1 then
xstart = 1
end
local ystart = math.floor(ystep/2)
if ystart < 1 then
ystart = 1
end
local curLine = math.floor(ystart+ystep*(y-1))
if curLine > oH or curLine <= 0 then
curLine = 1
end
local templine = {oriwin.getLine(curLine)}
newwin.lines[y] = {"","",""}
for x=1,w do
newwin.lines[y][1] = newwin.lines[y][1]..string.sub(templine[1],math.floor(xstart+xstep*(x-1)+0.5),math.floor(xstart+xstep*(x-1)+0.5))
newwin.lines[y][2] = newwin.lines[y][2]..string.sub(templine[2],math.floor(xstart+xstep*(x-1)+0.5),math.floor(xstart+xstep*(x-1)+0.5))
newwin.lines[y][3] = newwin.lines[y][3]..string.sub(templine[3],math.floor(xstart+xstep*(x-1)+0.5),math.floor(xstart+xstep*(x-1)+0.5))
end
end
function newwin.render(x,y,noLetters)
for l=1,#newwin.lines do
term.setCursorPos(x,y+(l-1))
if noLetters then
newwin.lines[l][1] = newwin.lines[l][1]:gsub("[^\127-\160 ]",".")
end
term.blit(table.unpack(newwin.lines[l]))
end
end
return newwin
end
function lUtils.fread(filepath, binary)
local mode = binary and "rb" or "r"
local fread = fs.open(filepath,mode)
local thing = fread.readAll()
fread.close()
return thing
end
function lUtils.fwrite(filepath,content, binary)
local mode = binary and "wb" or "w"
local fwrite = fs.open(filepath,mode)
fwrite.write(content)
fwrite.close()
return true
end
wPattern = "%S+"
function lUtils.makeEditBox(filepath,width,height,sTable)
if filepath == nil then
return false
end
if width == nil then
width = getWidth()
end
if height == nil then
height = getHeight()
end
if sTable == nil then
--sTable = {background={colors.white},text={colors.black},keywords={colors.yellow},notes={colors.green},strings={colors.red},menu={colors.yellow,colors.lightGray}}
sTable = {background={colors.white},text={colors.black},cursor={colors.red}}
end
lines = {""}
return {width=width,height=height,sTable=sTable,lines=lines,filepath=filepath,changed=false}
end
function lUtils.drawEditBox(box,expos,eypos,spx,spy,cpx,cpy,active,enterkey,rChar,changesAllowed)
if changesAllowed == nil then
changesAllowed = true
end
-- term.setCursorPos(expos,eypos)
-- print(tostring(changesAllowed))
-- os.sleep(1)
if enterkey == nil then
enterkey = true
end
if spx == nil then
spx = 0
end
if spy == nil then
spy = 0
end
if cpx == nil then
cpx = 1
end
if cpy == nil then
cpy = 1
end
if expos == nil then
expos = box.x
else
box.x = expos
end
if eypos == nil then
eypos = box.y
else
box.y = eypos
end
-- As these abbrevs are pretty unclear, I will now specify them respectively.
-- editorboxtable,editorxposition,editoryposition,scrollpositionx,scrollpositiony,usercursorpositionx,usercurserpositiony
local keywords = {["and"]=true,["break"]=true,["do"]=true,["else"]=true,["elseif"]=true,["end"]=true,["false"]=true,["for"]=true,["function"]=true,["if"]=true,["in"]=true,["local"]=true,["nil"]=true,["not"]=true,["or"]=true,["repeat"]=true,["return"]=true,["then"]=true,["true"]=true,["until"]=true,["while"]=true}
if box.width == nil or box.height == nil or box.sTable == nil or box.lines == nil or box.filepath == nil then
return false
end
if active == nil then
active = true
end
if active == false then
if box.sTable.background == nil then
term.setBackgroundColor(colors.black)
else
term.setBackgroundColor(box.sTable.background[1])
end
lOS.boxClear(expos,eypos,expos+(box.width-1),eypos+(box.height-1))
ypos = eypos
for l=1+spy,box.height+spy do
if box.lines[l] ~= nil then
if rChar ~= nil then
term.setTextColor(colors.lightGray)
term.setCursorPos(expos,ypos)
for rc=1,string.len(string.sub(box.lines[l],1+spx,box.width+spx)) do
write(rChar)
end
else
term.setTextColor(colors.lightGray)
term.setCursorPos(expos,ypos)
write(string.sub(box.lines[l],1+spx,box.width+spx))
end
end
ypos = ypos+1
end
return box,spx,spy,cpx,cpy,false,{}
end
while true do
expos,eypos = box.x,box.y
if box.sTable.background == nil then
term.setBackgroundColor(colors.black)
else
term.setBackgroundColor(box.sTable.background[1])
end
lOS.boxClear(expos,eypos,expos+(box.width-1),eypos+(box.height-1))
ypos = eypos
for l=1+spy,box.height+spy do
instring = false
innote = false
if box.lines[l] ~= nil then
if box.sTable.text == nil then
term.setTextColor(colors.white)
else
term.setTextColor(box.sTable.text[1])
end
term.setCursorPos(expos,ypos)
if rChar ~= nil then
for rc=1,string.len(string.sub(box.lines[l],1+spx,box.width+spx)) do
write(rChar)
end
else
wordcount = 1
if string.find(string.sub(box.lines[l],1+spx,box.width+spx),"%s+") ~= nil then
if string.find(string.sub(box.lines[l],1+spx,box.width+spx),"%s+") == 1 then
write(string.match(string.sub(box.lines[l],1+spx,box.width+spx),"%s+"))
end
end
for word in string.gmatch(string.sub(box.lines[l],1+spx,box.width+spx),wPattern) do
somearg = string.find(word,"--",nil,true)
if tonumber(somearg) ~= nil and instring == false then
innote = true
end
somearg = nil
if innote == true and box.sTable.notes ~= nil then
term.setTextColor(box.sTable.notes[1])
elseif keywords[word] ~= nil and box.sTable.keywords ~= nil then
term.setTextColor(box.sTable.keywords[1])
elseif tonumber(word) ~= nil and box.sTable.numbers ~= nil then
term.setTextColor(box.sTable.numbers[1])
else
term.setTextColor(box.sTable.text[1])
end
if wordcount == 1 then
write(word)
else
write(" "..word)
end
wordcount = wordcount+1
end
end
end
ypos = ypos+1
end
if cpy > spy and cpy <= spy+(box.height) and cpx > spx and cpx <= spx+(box.width) then
if box.sTable.cursor ~= nil then
term.setTextColor(box.sTable.cursor[1])
else
term.setTextColor(colors.black)
end
term.setCursorPos(cpx-spx-1+expos,cpy-spy-1+eypos)
term.setCursorBlink(true)
end
while cpx > string.len(box.lines[cpy])+1 do
cpx = cpx-1
end
while cpx > spx+(box.width) do
spx = spx+1
end
while cpx <= spx do
spx = spx-1
end
event,button,x,y = os.pullEvent()
term.setCursorBlink(false)
if (event == "mouse_click" and (x < expos or x > expos+box.width-1 or y < eypos or y > eypos+box.height-1)) or (event == "key" and button == keys.enter and enterkey == false) then
if box.sTable.background == nil then
term.setBackgroundColor(colors.black)
else
term.setBackgroundColor(box.sTable.background[1])
end
lOS.boxClear(expos,eypos,expos+(box.width-1),eypos+(box.height-1))
ypos = eypos
for l=1+spy,box.height+spy do
if box.lines[l] ~= nil then
if rChar ~= nil then
for rc=1,string.len(string.sub(box.lines[l],1+spx,box.width+spx)) do
write(rChar)
end
else
term.setTextColor(colors.lightGray)
term.setCursorPos(expos,ypos)
write(string.sub(box.lines[l],1+spx,box.width+spx))
end
end
ypos = ypos+1
end
return box,spx,spy,cpx,cpy,false,{event,button,x,y}
elseif event == "mouse_scroll" and enterkey == true then
if button == -1 and spy > 0 then
spy = spy-1
elseif button == 1 then
spy = spy+1
end
elseif event == "key" then
if button == keys.right and cpx < string.len(box.lines[cpy])+1 then
cpx = cpx+1
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height do
spy = spy+1
end
elseif button == keys.left and cpx > 1 then
cpx = cpx-1
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height do
spy = spy+1
end
elseif button == keys.down and cpy < #box.lines then
cpy = cpy+1
while cpy > spy + (box.height) do
spy = spy+1
end
while cpy <= spy do
spy = spy-1
end
elseif button == keys.up and cpy > 1 then
cpy = cpy-1
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height do
spy = spy+1
end
elseif button == keys.home then
cpx = 1
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height do
spy = spy+1
end
elseif button == keys["end"] then
cpx = string.len(box.lines[cpy])+1
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height do
spy = spy+1
end
elseif button == keys.tab and changesAllowed == true then
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height do
spy = spy+1
end
if box.changed == false then
box.changed = true
end
box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-1).." "..string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy]))
cpx = cpx+2
elseif button == keys.backspace and changesAllowed == true then
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height do
spy = spy+1
end
if box.changed == false then
box.changed = true
end
if cpx == 1 then
if cpy > 1 then
cpx = string.len(box.lines[cpy-1])+1
box.lines[cpy-1] = box.lines[cpy-1]..box.lines[cpy]
table.remove(box.lines,cpy)
cpy = cpy-1
end
else
box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-2)..string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy]))
cpx = cpx-1
end
elseif button == keys.enter and enterkey ~= false and changesAllowed == true then
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height-1 do
spy = spy+1
end
if box.changed == false then
box.changed = true
end
table.insert(box.lines,cpy+1,string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy])))
box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-1)
cpy = cpy+1
cpx = 1
end
elseif event == "char" and changesAllowed == true then
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height do
spy = spy+1
end
if box.changed == false then
box.changed = true
end
box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-1)..button..string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy]))
cpx = cpx+1
elseif event == "paste" and changesAllowed == true then
while cpy <= spy do
spy = spy-1
end
while cpy > spy+box.height do
spy = spy+1
end
if box.changed == false then
box.changed = true
end
box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-1)..button..string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy]))
cpx = cpx+string.len(button)
elseif event == "mouse_click" and y <= eypos+box.height-1 then
cpx = x - expos + spx+1
cpy = y - eypos + spy+1
if cpy > #box.lines then
cpy = #box.lines
end
end
end
end
function lUtils.textbox(txt,x1,y1,x2,y2,trans,syntax,overflowX,overflowY,scrollX,scrollY,align)
--[[term.setCursorPos(1,1)
local bbox = lUtils.makeBox(txt,x1,y1,x2,y2,{background={term.getBackgroundColor()},text={term.getTextColor()},name={colors.cyan},code={colors.red,colors.lightGray},notification={colors.green},title={colors.orange,align="center"}})
_G.bbox = bbox
lUtils.printBox(bbox,0,true,trans)]]
local al = align or "left"
local oX = overflowX or "wrap"
local oY = overflowY or "none"
local w,h = x2-(x1-1),y2-(y1-1)
local lines
local blit = {}
if trans then
for y=y1,y2 do
local bl = term.getLine(y)
table.insert(blit,{"","",bl[3]:sub(x1,x2)})
end
else
local col = lUtils.toBlit(term.getBackgroundColor())
for y=y1,y2 do
table.insert(blit,{"","",string.rep(col,w)})
end
end
if oX == "wrap" then
lines = lUtils.wordwrap(txt,w)
else
lines = {}
for line in txt:gmatch("([^\n]*)\n?") do
table.insert(lines,line)
end
if txt:sub(#txt) == "\n" then
table.insert(lines,"")
end
end
local tLines = {{lines[1],ref=1}}
for t=2,#lines do
tLines[t] = {lines[t],ref=tLines[t-1].ref+#tLines[t-1][1]}
end
local syntaxes = {
["lua"] = {
whitespace=colors.white,
comment=colors.green,
string=colors.red,
escape=colors.orange,
keyword=colors.yellow,
value=colors.yellow,
ident=colors.cyan,
number=colors.purple,
symbol=colors.orange,
operator=colors.yellow,
unidentified=colors.white,
}
}
local sType
if type(syntax) == "string" then
sType = syntax
syntax = {type=sType}
elseif type(syntax) == "table" then
sType = syntax.type
end
if sType and syntaxes[sType] then
if sType == "lua" and fs.exists("lex") then
local lex = dofile("lex")
local elements = lex.lex(txt)
local line = 1
for t=1,#elements do
while tLines[line+1] and elements[t].posFirst >= tLines[line+1].ref do
line = line+1
end
--term.setCursorPos(x+elements[t].posFirst-tLines[line].ref)
--elements[t].data = elements[t].data:gsub("\t"," ")
local col = lUtils.toBlit(syntaxes[sType][elements[t].type] or colors.white)
blit[line][1] = blit[line][1]..elements[t].data
blit[line][2] = blit[line][2]..string.rep(col,#elements[t].data)
end
end
end
_G.debugBlit = blit
for t=1,#blit do
local x
local line = lines[t]
if align == "center" then
x = (x1+math.ceil(w/2)-1)-(math.ceil(#blit[t][1]/2)-1)
elseif align == "right" then
x = x2-(#blit[t][1]-1)
else
x = x1
end
if #blit[t][3] > #blit[t][1] then
blit[t][3] = blit[t][3]:sub(x1-(x-1),x1-(x-1)+(#blit[t][1]-1))
end
term.setCursorPos(x,y1+(t-1))
term.blit(unpack(blit[t]))
end
end
function lUtils.textbox(txt,x1,y1,x2,y2,trans)
term.setCursorPos(1,1)
local bbox = lUtils.makeBox(txt,x1,y1,x2,y2,{background={term.getBackgroundColor()},text={term.getTextColor()},name={colors.cyan},code={colors.red,colors.lightGray},notification={colors.green},title={colors.orange,align="center"}})
_G.bbox = bbox
lUtils.printBox(bbox,0,true,trans)
end
function lUtils.makeBox(boxTxt,x1,y1,x2,y2,sTable,buttons)
-- boxTxt = {{{"Introduction",type="title"}},{{"Welcome to ",type="text"},{"LuaCraft",type="name"},{"!",type="text"}}}
paragraphs = {}
if boxTxt == nil then
return false
end
if x1 == nil then
x1 = 1
end
if y1 == nil then
y1 = 1
end
if x2 == nil then
x2 = getWidth()
end
if y2 == nil then
y2 = getHeight()
end
if sTable == nil then
sTable = {background={colors.white},text={colors.black},name={colors.cyan},code={colors.red,colors.lightGray},notification={colors.green},title={colors.orange,align="center"}}
end
if _G.type(boxTxt) == "string" then
while true do
par1,par2 = string.find(boxTxt,"\n")
if par2 ~= nil then
paragraphs[#paragraphs+1] = {{string.sub(boxTxt,1,par1-1),type="text"}}
if string.len(boxTxt) > par2 then
boxTxt = string.sub(boxTxt,par2+1,string.len(boxTxt))
else
boxTxt = ""
break
end
else
paragraphs[#paragraphs+1] = {{boxTxt,type="text"}}
break
end
end
elseif _G.type(boxTxt) == "table" then
paragraphs = boxTxt
end
lines = {{{},tt=""}}
cline = 1
for p=1,#paragraphs do
for t=1,#paragraphs[p] do
-- paragraphs[1] = {{"Introduction",type="title"}}
-- paragraphs[1][1] = {"Introduction",type="title"}
lines[cline][1][#lines[cline][1]+1] = {"",type=paragraphs[p][t].type}
for word in string.gmatch(paragraphs[p][t][1],"%S+") do
-- write(word)
if string.len(lines[cline].tt) == 0 then
if string.len(lines[cline].tt..word) > x2-(x1-1) then
-- This means a single word takes up MORE than an ENTIRE line. I'll have to write some proper code for that later.
end
-- First word in the line, so obviously no space before the word.
-- wow old me is so stupid I completely forgot about indentation
-- HAHAHA dumbass old me messed the fuck up
local indent = ""
if string.sub(paragraphs[p][t][1],1,1) == " " then
indent = string.gmatch(paragraphs[p][t][1],"%s+")()
end
lines[cline][1][#lines[cline][1]][1] = lines[cline][1][#lines[cline][1]][1]..indent..word
lines[cline].tt = lines[cline].tt..indent..word
else
if string.len(lines[cline].tt.." "..word) > x2-(x1-1) then
cline = cline+1
lines[cline] = {{},tt=""}
lines[cline][1][#lines[cline][1]+1] = {"",type=paragraphs[p][t].type}
lines[cline][1][#lines[cline][1]][1] = lines[cline][1][#lines[cline][1]][1]..word
lines[cline].tt = lines[cline].tt..word
else
lines[cline][1][#lines[cline][1]][1] = lines[cline][1][#lines[cline][1]][1].." "..word
lines[cline].tt = lines[cline].tt.." "..word
end
end
end
print("")
end
cline = cline+1
lines[cline] = {{},tt=""}
end
if buttons == nil then
buttons = {}
end
return {x1=x1,y1=y1,x2=x2,y2=y2,sTable=sTable,lines=lines,buttons=buttons}
end
function lUtils.transWrite(txt,autoTextColor)
local txtcolor = term.getTextColor()
local dark = {colors.blue,colors.red,colors.purple,colors.green,colors.black,colors.gray,colors.brown,colors.cyan,colors.magenta}
local light = {colors.white,colors.orange,colors.lightBlue}
for k,v in ipairs(dark) do
dark[v] = true
end
for k,v in ipairs(light) do
light[v] = true
end
for t=1,string.len(txt) do
local col = lUtils.toColor(({lUtils.getPixel(term.current(),term.getCursorPos())})[3])
term.setBackgroundColor(col)
if autoTextColor then
if dark[col] and not light[col] then
term.setTextColor(colors.white)
else
term.setTextColor(colors.black)
end
end
term.write(string.sub(txt,t,t))
end
term.setTextColor(txtcolor)
end
function lUtils.printBox(box,scrollpos,simple,trans)
if type(box) ~= "table" then
error("Invalid argument #1: Expected table",2)
end
someabsolutelyrandomthing = true
while someabsolutelyrandomthing == true do
term.setTextColor(colors.black)
if box.x1 == nil or box.y1 == nil or box.x2 == nil or box.y2 == nil or box.sTable == nil or box.lines == nil then
return false,"Invalid Box."
end
if box.sTable.background ~= nil then
term.setBackgroundColor(box.sTable.background[1])
else
term.setBackgroundColor(colors.white)
end
if not trans then
lOS.boxClear(box.x1,box.y1,box.x2,box.y2)
end
ypos = box.y1
for l=1+scrollpos,box.y2-(box.y1-1)+scrollpos do
if box.lines[l] ~= nil then
term.setCursorPos(box.x1,ypos)
for p=1,#box.lines[l][1] do
if box.sTable[box.lines[l][1][p].type] ~= nil then
term.setTextColor(box.sTable[box.lines[l][1][p].type][1])
if box.sTable[box.lines[l][1][p].type][2] ~= nil then
term.setBackgroundColor(box.sTable[box.lines[l][1][p].type][2])
end
if box.sTable[box.lines[l][1][p].type].align ~= nil then
if box.sTable[box.lines[l][1][p].type].align == "center" then
cposx,cposy=term.getCursorPos()
term.setCursorPos(box.x1+math.floor((box.x2-(box.x1-1))/2)-math.floor(string.len(box.lines[l][1][p][1])/2),cposy)
end
end
lUtils.transWrite(box.lines[l][1][p][1])
end
end
end
ypos = ypos+1
end
if box.buttons ~= nil then
for b=1,#box.buttons do
if box.buttons[b].type == "button" or box.buttons[b].type == "editor" then
term.setBackgroundColor(box.buttons[b].bg)
term.setTextColor(box.buttons[b].txt)
for t=1,box.buttons[b].y2-(box.buttons[b].y1-1) do
blankline = ""
for bl=1,box.buttons[b].x2-(box.buttons[b].x1-1) do
blankline = blankline.." "
end
if box.buttons[b].y1+t-1 >= 1+scrollpos and box.buttons[b].y1+t-1 <= box.y2-(box.y1-1)+scrollpos then
term.setCursorPos(box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos)
term.write(blankline)
term.setCursorPos(box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos)
if box.buttons[b].text[t] ~= nil then
term.write(box.buttons[b].text[t])
end
end
end
end
if box.buttons[b].type == "editor" then
if box.buttons[b].y1 >= 1+scrollpos and box.buttons[b].y2 <= box.y2-(box.y1-1)+scrollpos then
if drawEditBox ~= nil and box.buttons[b].quit == false then
term.setCursorPos(1,1)
term.write(box.buttons[b].spx..","..box.buttons[b].spy.." | "..box.buttons[b].cpx..","..box.buttons[b].cpy)
drawEditBox(box.buttons[b].box,box.buttons[b].x1+box.x1-1,(box.buttons[b].y1-1)+box.y1-scrollpos,box.buttons[b].spx,box.buttons[b].spy,box.buttons[b].cpx,box.buttons[b].cpy,false)
end
end
end
end
end
if simple == true then
someabsolutelyrandomthing = false
else
event,button,x,y = os.pullEvent()
if event == "mouse_click" and (x < box.x1 or x > box.x2 or y < box.y1 or y > box.y2) then
term.setBackgroundColor(colors.white)
ypos = box.y1
for l=1+scrollpos,box.y2-(box.y1-1)+scrollpos do
if box.lines[l] ~= nil then
term.setCursorPos(box.x1,ypos)
for p=1,#box.lines[l][1] do
if box.sTable[box.lines[l][1][p].type] ~= nil then
term.setTextColor(colors.lightGray)
if box.sTable[box.lines[l][1][p].type][2] ~= nil then
term.setBackgroundColor(box.sTable[box.lines[l][1][p].type][2])
end
if box.sTable[box.lines[l][1][p].type].align ~= nil then
if box.sTable[box.lines[l][1][p].type].align == "center" then
cposx,cposy=term.getCursorPos()
term.setCursorPos(box.x1+math.floor((box.x2-(box.x1-1))/2)-math.floor(string.len(box.lines[l][1][p][1])/2),cposy)
end
end
term.write(box.lines[l][1][p][1])
end
end
end
ypos = ypos+1
end
if box.buttons ~= nil then
for b=1,#box.buttons do
term.setBackgroundColor(box.buttons[b].bg)
term.setTextColor(colors.lightGray)
for t=1,box.buttons[b].y2-(box.buttons[b].y1-1) do
blankline = ""
for bl=1,box.buttons[b].x2-(box.buttons[b].x1-1) do
blankline = blankline.." "
end
if box.buttons[b].y1+t-1 >= 1+scrollpos and box.buttons[b].y1+t-1 <= box.y2-(box.y1-1)+scrollpos then
term.setCursorPos(box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos)
term.write(blankline)
term.setCursorPos(box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos)
if box.buttons[b].text[t] ~= nil then
term.write(box.buttons[b].text[t])
end
end
end
end
end
return scrollpos
elseif event == "mouse_click" then
for b=1,#box.buttons do
-- box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos
if x >= box.buttons[b].x1+box.x1-1 and x <= box.buttons[b].x2+box.x1-1 and y >= (box.buttons[b].y1-1)+box.y1-scrollpos and y <= (box.buttons[b].y2-1)+box.y1-scrollpos then
newbox,newbutton,newscrollpos = box.buttons[b].func(box,box.buttons[b],scrollpos)
if newbox ~= nil then
box = newbox
end
if newbutton ~= nil then
box.buttons[b] = newbutton
end
if newscrollpos ~= nil then
scrollpos = newscrollpos
end
end
end
elseif event == "mouse_scroll" then
if button == -1 and scrollpos > 0 then
scrollpos = scrollpos-1
elseif button == 1 then
scrollpos = scrollpos+1
end
end
end
end
end
function lUtils.getPixel(win,x,y)
local w,h = win.getSize()
if x < 1 or x > w or y < 1 or y > h then
return "0","0","0"
else
theline = {win.getLine(y)}
return string.sub(theline[1],x,x),string.sub(theline[2],x,x),string.sub(theline[3],x,x)
end
end
local function setParent(win)
if not win.reposition then return false end
local varName,value = debug.getupvalue(win.reposition,5)
local i = 1
while varName and varName ~= "parent" do
varName,value = debug.getupvalue(win.reposition,i)
i = i+1
end
if varName == "parent" then
win.parent = value
if win.parent.reposition and not win.parent.parent then
setParent(win.parent)
end
end
end
function lUtils.getWindowPos(win)
setParent(win)
local tWin = win
local x,y = tWin.getPosition()
while tWin.parent and tWin.parent.getPosition do
tWin = tWin.parent
local ox,oy = tWin.getPosition()
x,y = x+ox-1,y+oy-1
end
return x,y
end
function lUtils.openWin(title,filepath,x,y,width,height,canresize,canmaximize)
if canmaximize == nil then
canmaximize = true
end
to_colors, to_blit = {}, {}
for i = 1, 16 do
to_blit[2^(i-1)] = ("0123456789abcdef"):sub(i, i)
to_colors[("0123456789abcdef"):sub(i, i)] = 2^(i-1)
end
local OGterm = term.current()
local OGwin = {lines={}}
local OGtxt = term.getTextColor()
local OGbg = term.getBackgroundColor()
local w,h = term.getSize()
for t=1,h do
OGwin.lines[t] = {OGterm.getLine(t)}
end
local function getPixel(win,x,y)
theline = {win.getLine(y)}
return string.sub(theline[1],x,x),string.sub(theline[2],x,x),string.sub(theline[3],x,x)
end
function OGwin.render()
for l=1,#OGwin.lines do
term.setCursorPos(1,l)
term.blit(table.unpack(OGwin.lines[l]))
end
end
local progWin = window.create(term.current(),x+1,y+1,width-2,height-2)
dragging = false
dragX = false
dragY = false
dragSide = 1
local function redrawbox()
term.setBackgroundColor(colors.gray)
term.setCursorPos(x,y)
for t=1,width-6 do
term.write(" ")
end
term.setTextColor(colors.white)
if canmaximize == false then
term.write(" × ")
else
term.write(" + × ")
end
term.setCursorPos(x+1,y)
term.write(title)
term.setTextColor(colors.gray)
for i=1,height-2 do
term.setCursorPos(x,y+i)
term.blit(string.char(149),to_blit[colors.gray],({getPixel(progWin,1,i)})[3])
term.blit(progWin.getLine(i))
term.blit(string.char(149),({getPixel(progWin,width-2,i)})[3],to_blit[colors.gray])
end
local bottomline = {string.char(138),({getPixel(progWin,1,height-2)})[3],to_blit[colors.gray]}
for i=1,width-2 do
bottomline[1] = bottomline[1]..string.char(143)
bottomline[2] = bottomline[2]..({getPixel(progWin,i,height-2)})[3]
bottomline[3] = bottomline[3]..to_blit[colors.gray]
end
bottomline[1] = bottomline[1]..string.char(133)
bottomline[2] = bottomline[2]..({getPixel(progWin,width-2,height-2)})[3]
bottomline[3] = bottomline[3]..to_blit[colors.gray]
term.setCursorPos(x,y+(height-1))
term.blit(table.unpack(bottomline))
end
local endresult = {}
local function regevents()
local progCor
if type(filepath) == "string" then
local tPath = filepath
local rPath = string.sub(filepath,string.find(filepath,"%S+"))
local b,e = string.find(filepath,"%S+")
local tPath = string.sub(filepath,e+2,string.len(filepath))
local thingy = {}
for i in string.gmatch(tPath,"%S+") do
if i ~= nil and i ~= "" then
thingy[#thingy+1] = i
end
end
progCor = coroutine.create(function() endresult = {lOS.run(rPath,table.unpack(thingy))} end)
elseif type(filepath) == "function" then
progCor = coroutine.create(function() endresult = {filepath()} end)
else
error("Invalid filepath",3)
end
local stop = false
e = {}
while stop == false do
if coroutine.status(progCor) == "dead" then return end
term.redirect(progWin)
progWin.redraw()
progWin.restoreCursor()
if (e[1] == "mouse_click" or e[1] == "mouse_drag" or e[1] == "mouse_up" or e[1] == "mouse_scroll" or e[1] == "mouse_move") and lOS.wins[lOS.cWin].events ~= "all" then
e[3] = e[3]-x
e[4] = e[4]-y
if e[3] >= 1 and e[3] <= width-2 and e[4] >= 1 and e[4] <= height-2 then
coroutine.resume(progCor,table.unpack(e))
end
else
coroutine.resume(progCor,table.unpack(e))
end
term.redirect(OGterm)
redrawbox()
term.setTextColor(progWin.getTextColor())
term.setCursorPos(({progWin.getPosition()})[1]+({progWin.getCursorPos()})[1]-1,({progWin.getPosition()})[2]+({progWin.getCursorPos()})[2]-1)
term.setCursorBlink(progWin.getCursorBlink())
e = {os.pullEvent()}
if e[1] == "mouse_drag" then
if dragX == true then
if dragSide == 1 then
width = OGw+(DRx-e[3])
x = OGx-(DRx-e[3])
else
width = OGw-(DRx-e[3])
end
end
if dragY == true then
height = OGh-(DRy-e[4])
end
progWin.reposition(x+1,y+1,width-2,height-2)
progWin.setVisible(false)
term.redirect(progWin)
progWin.redraw()
progWin.restoreCursor()
coroutine.resume(progCor,"term_resize")
term.redirect(OGterm)
progWin.setVisible(true)
OGwin.render()
redrawbox()
end
if e[1] == "mouse_drag" and dragging == true then
x = OGx-(DRx-e[3])
y = OGy-(DRy-e[4])
progWin.reposition(x+1,y+1)
progWin.setVisible(false)
term.redirect(progWin)
progWin.redraw()
progWin.restoreCursor()
coroutine.resume(progCor,"term_resize")
term.redirect(OGterm)
progWin.setVisible(true)
OGwin.render()
redrawbox()
elseif e[1] == "mouse_click" or e[1] == "mouse_up" then
if e[1] == "mouse_up" then
dragging = false
dragX = false
dragY = false
OGx,OGy = x,y
OGw,OGh = width,height
DRx,DRy = nil,nil
end
if e[4] == y and e[3] >= x+(width-3) and e[3] <= x+(width-1) then
if e[1] == "mouse_click" then
term.setTextColor(colors.white)
term.setBackgroundColor(colors.red)
term.setCursorPos(x+(width-3),y)
term.write(" × ")
elseif e[1] == "mouse_up" then
stop = true
return false
end
elseif e[4] == y and e[3] >= x+(width-6) and e[3] <= x+(width-4) and canmaximize ~= false then
if e[1] == "mouse_click" then
term.setTextColor(colors.white)
term.setBackgroundColor(colors.lightGray)
term.setCursorPos(x+(width-6),y)
term.write(" + ")
elseif e[1] == "mouse_up" then
--lOS.oWins[#lOS.oWins+1] = {window.create(oldterm,1,2,({oldterm.getSize()})[1],({oldterm.getSize()})[2]-3),progCor,fullscreen=false,minimized=false,filepath="'..rPath..'",icon={string.sub(fs.getName(rPath),1,3),string.sub(fs.getName(rPath),4,6)},ran=false}
--os.startTimer(0.1)
--return true
end
elseif e[4] == y and e[1] == "mouse_click" then
dragging = true
DRx,DRy = e[3],e[4]
OGx,OGy = x,y
end
if e[4] > y and e[4] <= y+(height-1) and (e[3] == x or e[3] == x+(width-1)) and e[1] == "mouse_click" and canresize then
DRx = e[3]
OGw = width
OGx = x
dragX = true
if e[3] == x then
dragSide = 1
else
dragSide = 2
end
end
if e[4] == y+(height-1) and e[3] >= x and e[3] <= x+(width-1) and e[1] == "mouse_click" and canresize then
dragY = true
DRy = e[4]
OGh = height
end
end
end
end
regevents()
OGwin.render()
term.setBackgroundColor(OGbg)
term.setTextColor(OGtxt)
return table.unpack(endresult)
end
function lUtils.popup(title,msg,width,height,buttons,redrawscreen,colorScheme)
colorScheme = colorScheme or {}
local tbtxt = colorScheme.topbarText or colors.white
local tbtxt2 = colorScheme.topbarTextHighlight or colors.black
local tbbg = colorScheme.topbarFill or colors.gray
local tbbg2 = colorScheme.topbarFillHighlight or colors.white
local txtcol = colorScheme.text or colors.blue
local bg = colorScheme.fill or colors.white
local fg = colorScheme.border or colors.lightGray
local btnbg = colorScheme.buttonFill or colors.white
local btnbg2 = colorScheme.buttonFillHighlight or colors.lightBlue
local btntxt = colorScheme.buttonText or colors.black
local btntxt2 = colorScheme.buttonTextHighlight or colors.white
buttons = buttons or {"Continue"}
local oblink = term.getCursorBlink()
term.setCursorBlink(false)
if msg == nil then
error("No text given",2)
end
local OGterm = term.current()
local OGwin = {lines={}}
local w,h = term.getSize()
for t=1,h do
OGwin.lines[t] = {OGterm.getLine(t)}
end
function OGwin.render()
for l=1,#OGwin.lines do
term.setCursorPos(1,l)
term.blit(table.unpack(OGwin.lines[l]))
end
end
local dragging = false
--[[if width == nil or height == nil then
width = 21
height = 7
end]]
if not height and not width then
width = 21
end
if height and not width then
width = 21
local lines = lUtils.wordwrap(msg, width-2)
while #lines > height-6 do
width = width+2
lines = lUtils.wordwrap(msg, width-2)
end
elseif width and not height then
local lines = lUtils.wordwrap(msg, width-2)
height = #lines+6
end
if height < 6 then height = 6 end
local popupline = string.rep(" ",width)
local lines = lUtils.wordwrap(msg, width-2)
local write = term.write
local function redrawbox()
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
term.setBackgroundColor(tbbg)
term.setTextColor(tbtxt)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
write(" "..title)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
write(" × ")
-- The line below is unreadable now but it just makes the text box for the popup message and then prints it
term.setBackgroundColor(bg)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+1)
term.write(string.rep(" ", width-2))
term.setTextColor(txtcol)
for y=1, height-4 do
local line = ""
if lines[y] then
line = lines[y]
end
line = line..string.rep(" ", width-2 - #line)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+1+y)
term.write(line)
end
for t=1,height-4 do
term.setBackgroundColor(bg)
term.setTextColor(fg)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+t)
write(string.char(149))
term.setBackgroundColor(fg)
term.setTextColor(bg)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-1,math.ceil(h/2)-math.floor(height/2)+t)
write(string.char(149))
end
term.setBackgroundColor(fg)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-2)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
term.setTextColor(btntxt)
for b=1,#buttons do
term.setBackgroundColor(fg)
write(" ")
term.setBackgroundColor(btnbg)
write(" "..buttons[b].." ")
end
end
redrawbox()
while true do
event,button,x,y = os.pullEvent()
if event == "mouse_drag" and dragging == true then
w = OGw-(OGx-x)*2
h = OGh-(OGy-y)*2
OGwin.render()
redrawbox()
elseif event == "mouse_click" or event == "mouse_up" then
if event == "mouse_up" and dragging == true then
dragging = false
end
if y == math.ceil(h/2)-math.floor(height/2)+height-2 then
curX = math.ceil(w/2)-math.floor(width/2)
for b=1,#buttons do
if x >= curX+1 and x <= curX+string.len(" "..buttons[b].." ") then
if event == "mouse_up" or event == "monitor_touch" then
if redrawscreen ~= nil and redrawscreen == true then
OGwin.render()
end
term.setCursorBlink(oblink)
return true,b,buttons[b]
elseif event == "mouse_click" then
term.setCursorPos(curX+1,math.ceil(h/2)-math.floor(height/2)+height-2)
term.setBackgroundColor(btnbg2)
term.setTextColor(btntxt2)
write(" "..buttons[b].." ")
end
end
curX = curX+string.len(" "..buttons[b].." ")+1
end
elseif y == math.ceil(h/2)-math.floor(height/2) and x >= math.ceil(w/2)-math.floor(width/2)+width-3 and x <= math.ceil(w/2)-math.floor(width/2)+width-1 then
if event == "mouse_click" then
term.setTextColor(colors.white)
term.setBackgroundColor(colors.red)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
write(" × ")
elseif event == "mouse_up" then
if redrawscreen ~= nil and redrawscreen == true then
OGwin.render()
end
term.setCursorBlink(oblink)
return false,0,""
end
elseif y == math.ceil(h/2)-math.floor(height/2) and event == "mouse_click" then
dragging = true
OGx,OGy = x,y
OGw = w
OGh = h
end
if (x < math.ceil(w/2)-math.floor(width/2) or x > math.ceil(w/2)-math.floor(width/2)+width-1 or y < math.ceil(h/2)-math.floor(height/2) or y > math.ceil(h/2)-math.floor(height/2)+height-1) and event == "mouse_click" then
for t=1,5 do
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
term.setBackgroundColor(tbbg2)
term.setTextColor(tbtxt2)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
write(" "..title)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
write(" × ")
os.sleep(0.1)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
term.setBackgroundColor(tbbg)
term.setTextColor(tbtxt)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
write(" "..title)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
write(" × ")
os.sleep(0.1)
end
end
end
end
end
function lUtils.inputbox(title,msg,width,height,buttons)
local OGterm = term.current()
local OGwin = {lines={}}
local w,h = term.getSize()
for t=1,h do
OGwin.lines[t] = {OGterm.getLine(t)}
end
function OGwin.render()
for l=1,#OGwin.lines do
term.setCursorPos(1,l)
term.blit(table.unpack(OGwin.lines[l]))
end
end
dragging = false
local input = lUtils.makeEditBox("input",width-4,1)
if width == nil or height == nil then
width = 21
height = 7
end
if height < 6 then height = 6 end
popupline = ""
for pl=1,width do
popupline = popupline.." "
end
local function redrawbox()
ocursorx,ocursory = term.getCursorPos()
otext = term.getTextColor()
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
term.setBackgroundColor(colors.gray)
term.setTextColor(colors.white)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
write(" "..title)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
write(" × ")
-- The line below is unreadable now but it just makes the text box for the popup message and then prints it
term.setBackgroundColor(colors.white)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+1)
term.write(string.sub(popupline,2,string.len(popupline)-1))
term.setTextColor(colors.black)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+1)
lUtils.printBox(lUtils.makeBox(msg,(math.ceil(w/2)-math.floor(width/2))+1,math.ceil(h/2)-math.floor(height/2)+2,(math.ceil(w/2)-math.floor(width/2)+width-1)-1,math.ceil(h/2)-math.floor(height/2)+height-1-3-3,{background={colors.white},text={colors.blue}}),0,true)
for t=1,height-4 do
term.setBackgroundColor(colors.white)
term.setTextColor(colors.lightGray)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+t)
write(string.char(149))
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.white)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-1,math.ceil(h/2)-math.floor(height/2)+t)
write(string.char(149))
end
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+height-1-3-2)
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.white)
write("\159")
for t=1,width-4 do
write("\143")
end
term.setBackgroundColor(colors.white)
term.setTextColor(colors.lightGray)
write("\144")
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+height-1-3-1)
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.white)
write("\149")
term.setTextColor(colors.lightGray)
term.setBackgroundColor(colors.white)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1+width-3,math.ceil(h/2)-math.floor(height/2)+height-1-3-1)
write("\149")
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+height-1-3)
write("\130")
for t=1,width-4 do
write("\131")
end
write("\129")
term.setBackgroundColor(colors.lightGray)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-2)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
term.setTextColor(colors.black)
for b=1,#buttons do
term.setBackgroundColor(colors.lightGray)
write(" ")
term.setBackgroundColor(colors.white)
write(" "..buttons[b].." ")
end
term.setTextColor(otext)
term.setCursorPos(ocursorx,ocursory)
end
redrawbox()
local function regevents()
while true do
event,button,x,y = os.pullEvent()
if event == "mouse_drag" and dragging == true then
w = OGw-(OGx-x)*2
h = OGh-(OGy-y)*2
OGwin.render()
redrawbox()
elseif event == "mouse_click" or event == "mouse_up" then
if event == "mouse_up" and dragging == true then
dragging = false
end
if y == math.ceil(h/2)-math.floor(height/2)+height-2 then
curX = math.ceil(w/2)-math.floor(width/2)
for b=1,#buttons do
if x >= curX+1 and x <= curX+string.len(" "..buttons[b].." ") then
if event == "mouse_up" or event == "monitor_touch" then
return true,b,buttons[b]
elseif event == "mouse_click" then
ocursorx,ocursory = term.getCursorPos()
otext = term.getTextColor()
term.setCursorPos(curX+1,math.ceil(h/2)-math.floor(height/2)+height-2)
term.setBackgroundColor(colors.lightBlue)
term.setTextColor(colors.black)
write(" "..buttons[b].." ")
term.setCursorPos(ocursorx,ocursory)
term.setTextColor(otext)
end
end
curX = curX+string.len(" "..buttons[b].." ")+1
end
elseif y == math.ceil(h/2)-math.floor(height/2) and x >= math.ceil(w/2)-math.floor(width/2)+width-3 and x <= math.ceil(w/2)-math.floor(width/2)+width-1 then
if event == "mouse_click" then
term.setTextColor(colors.white)
term.setBackgroundColor(colors.red)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
write(" × ")
elseif event == "mouse_up" then
return false,0,""
end
elseif y == math.ceil(h/2)-math.floor(height/2) then
dragging = true
OGx,OGy = x,y
OGw = w
OGh = h
else
redrawbox()
end
if (x < math.ceil(w/2)-math.floor(width/2) or x > math.ceil(w/2)-math.floor(width/2)+width-1 or y < math.ceil(h/2)-math.floor(height/2) or y > math.ceil(h/2)-math.floor(height/2)+height-1) and event == "mouse_click" then
for t=1,5 do
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
term.setBackgroundColor(colors.white)
term.setTextColor(colors.black)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
write(" "..title)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
write(" × ")
os.sleep(0.1)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
term.setBackgroundColor(colors.gray)
term.setTextColor(colors.white)
write(popupline)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
write(" "..title)
term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
write(" × ")
os.sleep(0.1)
end
end
end
end
end
function readbox()
c = {"mouse_click",1,math.ceil(w/2)-math.floor(width/2)+2,math.ceil(h/2)-math.floor(height/2)+height-1-3-1}
while true do
if c[1] == "mouse_click" and c[3] >= math.ceil(w/2)-math.floor(width/2)+2 and c[3] <= math.ceil(w/2)-math.floor(width/2)+2+(input.width-1) and c[4] == math.ceil(h/2)-math.floor(height/2)+height-1-3-1 then
if input.lines[1] ~= nil then
lUtils.drawEditBox(input,math.ceil(w/2)-math.floor(width/2)+2,math.ceil(h/2)-math.floor(height/2)+height-1-3-1,0,0,string.len(input.lines[1])+1,1,true,false)
else
lUtils.drawEditBox(input,math.ceil(w/2)-math.floor(width/2)+2,math.ceil(h/2)-math.floor(height/2)+height-1-3-1,0,0,1,1,true,false)
end
end
c = {os.pullEvent("mouse_click")}
end
end
local returnthis = {}
parallel.waitForAny(readbox,function() returnthis = {regevents()} end)
term.setCursorBlink(false)
return input.lines[1],table.unpack(returnthis)
end
function lUtils.createButton(x,y,w,h,lines,func,colrs)
local btn = {x1=x,y1=y,x2=x+(w-1),y2=y+(h-1),func=func,colors=colrs,selected=false}
if not btn.colors then
btn.colors = {}
end
local c = btn.colors
if not c.txt then
c.txt = colors.white
end
if not c.fg then
c.fg = c.bg or colors.lightGray
end
if not c.bg then
c.bg = colors.gray
end
if not c.clicked then
c.clicked = {}
end
local cc = c.clicked
if not cc.fg then
cc.fg = cc.bg or colors.lightGray
end
if not cc.bg then
cc.bg = c.bg2 or colors.lightGray
end
if not cc.txt then
cc.txt = c.txt2 or colors.white
end
function btn.render()
local ofg,obg = term.getTextColor(),term.getBackgroundColor()
if btn.selected then
term.setBackgroundColor(btn.colors.clicked.bg)
term.setTextColor(btn.colors.clicked.fg)
else
term.setBackgroundColor(btn.colors.bg)
term.setTextColor(btn.colors.fg)
end
lUtils.border(btn.x1,btn.y1,btn.x2,btn.y2,"fill")
if type(lines) == "string" then
if btn.selected then
term.setTextColor(btn.colors.clicked.txt)
else
term.setTextColor(btn.colors.txt)
end
if btn.y1 == btn.y2 then
term.setCursorPos(btn.x1,btn.y1)
else
term.setCursorPos(btn.x1+1,btn.y1+1)
end
term.write(lines)
end
term.setTextColor(ofg)
term.setBackgroundColor(obg)
end
function btn.update(...)
local e = {...}
if (e[1] == "mouse_click" or e[1] == "mouse_up") and e[2] == 1 then
if e[3] >= btn.x1 and e[4] >= btn.y1 and e[3] <= btn.x2 and e[4] <= btn.y2 then
if e[1] == "mouse_click" then
btn.selected = true
btn.render()
elseif e[1] == "mouse_up" and btn.selected then
btn.selected = false
btn.render()
return btn.func()
end
end
end
if e[1] == "mouse_up" then
btn.selected = false
end
end
return btn
end
function lUtils.createCanvas(x,y,w,h)
cvs = {}
if not (x or y) then
cvs.x1,cvs.y1 = 1,1
else
cvs.x1,cvs.y1 = x,y
end
if not (w or h) then
cvs.x2,cvs.y2 = term.getSize()
else
cvs.x2,cvs.y2 = x+(w-1),y+(h-1)
end
cvs.objs = {}
function cvs.clear()
cvs.objs = {}
end
function cvs.update(...)
for o=1,#cvs.objs do
cvs.objs[o].update(...)
end
end
function cvs.render()
for o=1,#cvs.objs do
cvs.objs[o].render()
end
end
function cvs.createButton(...)
local b = lUtils.createButton(...)
if b then
cvs.objs[#cvs.objs+1] = b
return b
else
return false
end
end
return cvs
end
function lUtils.contextmenu(x,y,width,options,colorScheme,dividers)
-- elements are either string OR table, table contains txt and optionally: disabled (bool), action (function or table (for nested context menu)), color (number)
local mColors = colorScheme or {}
mColors.fg = mColors.fg or colors.lightGray
mColors.bg = mColors.bg or colors.gray
mColors.txt = mColors.txt or colors.white
mColors.disabled = mColors.disabled or colors.lightGray
mColors.divider = mColors.divider or mColors.fg
mColors.selected = mColors.selected or mColors.select or mColors.fg
local w = 0
if not width then
width = "auto"
end
if type(width) == "number" then
w = width
end
local function genContextMenu(opt,x,y,w)
local h = 2
local objects = {}
local cY = 1
local function addObject(obj,parent)
obj.y = cY
obj.parent = opt
obj.parentObj = parent
table.insert(objects,obj)
cY = cY+1
h = h+1
if not obj.action then obj.action = "output" end
if obj.txt then
local nW
if type(obj.txt) == "table" then
nW = #obj.txt[1]+2
else
nW = #obj.txt+2
end
if type(obj.action) == "table" then
nW = nW+2
end
if nW > w then
w = nW
end
end
end
local acceptable = {["string"]=true,["table"]=true}
local lID = 1
for i,o in ipairs(opt) do
if i > 1 and dividers then
addObject({action="divider"})
end
if type(o) == "string" then
addObject({action="output",txt=o,id=lID})
lID = lID+1
elseif type(o) == "table" and acceptable[type(o.txt)] then
addObject(o)
o.id = lID
lID = lID+1
elseif type(o) == "table" then
for k,v in ipairs(o) do
local lID2 = 1
if type(v) == "string" then
addObject({action="output",txt=v,id=lID2},o)
lID2 = lID2+1
elseif type(v) == "table" and acceptable[type(v.txt)] then
addObject(v,o)
v.id = lID2
lID2 = lID2+1
end
end
o.id = lID
lID = lID+1
o.parent = opt
end
end
objects.x,objects.y,objects.w,objects.h = x,y,w,h
objects.x1,objects.y1 = x,y
objects.x2,objects.y2 = x+w-1,y+h-1
objects.rObjs = {}
objects.scroll = 0
return objects
end
local objects,h
objects = genContextMenu(options,x,y,w)
objects.status = "idle"
local function positionMenu(menu)
local tW,tH = term.getSize()
if menu.y+menu.h-1 > tH then
if menu.y-menu.h+1 >= 1 then
menu.y = menu.y-menu.h+1
elseif menu.h <= tH then
while menu.y+menu.h-1 > tH do
menu.y = menu.y-1
end
else
menu.y = 1
end
end
-- repeat for X axis
if menu.x+menu.w-1 > tW then
if menu.x-menu.w+1 >= 1 then
menu.x = menu.x-menu.w+1
elseif menu.w <= tW then
while menu.x+menu.w-1 > tW do
menu.x = menu.x-1
end
else
menu.x = 1
-- turn on scroll
end
end
end
positionMenu(objects)
local menus = {objects}
local function renderMenu(menu)
local tW,tH = term.getSize()
while menu.y+menu.h-1 > tH do
menu.h = menu.h-1
end
menu.x1,menu.y1 = menu.x,menu.y
menu.x2,menu.y2 = menu.x+menu.w-1,menu.y+menu.h-1
menu.bg = menu.bg or mColors.bg
menu.fg = menu.fg or mColors.fg
menu.txt = menu.txt or mColors.txt
menu.divider = menu.divider or mColors.divider
menu.disabled = menu.disabled or mColors.disabled
menu.selColor = menu.selColor or mColors.selected
dividerline = string.rep("\140",menu.w-2)
term.setBackgroundColor(menu.bg)
term.setTextColor(menu.fg)
lUtils.border(menu.x,menu.y,menu.x+menu.w-1,menu.y+menu.h-1,"fill")
for k=1+menu.scroll,(menu.h-2)+menu.scroll do
local v = menu[k]
term.setCursorPos(menu.x+1,menu.y+v.y-menu.scroll)
if menu.scroll > 0 and k == 1+menu.scroll then
term.setBackgroundColor(menu.bg)
term.setTextColor(menu.txt)
term.setCursorPos(menu.x+math.ceil(menu.w/2)-1,menu.y+1)
term.write("\30")
elseif k == (menu.h-2)+menu.scroll and k < #menu then
term.setBackgroundColor(menu.bg)
term.setTextColor(menu.txt)
term.setCursorPos(menu.x+math.ceil(menu.w/2)-1,menu.y2-1)
term.write("\31")
else
if menu.selected == v then
term.setBackgroundColor(menu.selColor)
else
term.setBackgroundColor(v.bg or menu.bg)
end
if v.action == "divider" then
term.setTextColor(menu.divider)
term.write(dividerline)
else
if v.disabled then
term.setTextColor(v.color or menu.disabled)
else
term.setTextColor(v.color or menu.txt)
end
if type(v.txt) == "table" then
if not v.txt[2] then v.txt[2] = "" end
if not v.txt[3] then v.txt[3] = "" end
local bl = {
v.txt[1]..string.rep(" ",(menu.w-2)-#v.txt[1]),
v.txt[2]..string.rep(lUtils.toBlit(term.getTextColor()),(menu.w-2)-#v.txt[2]),
v.txt[3]..string.rep(lUtils.toBlit(term.getBackgroundColor()),(menu.w-2)-#v.txt[3]),
}
term.blit(unpack(bl))
else
term.write(v.txt..string.rep(" ",(menu.w-2)-#v.txt))
end
if type(v.action) == "table" then
term.setCursorPos(menu.x+menu.w-2,menu.y+v.y-menu.scroll)
term.write(">")
end
end
end
end
end
function objects.render()
for m=1,#menus do
renderMenu(menus[m])
end
end
function objects.update(...)
if objects.status == "dead" then return false end
objects.status = "running"
local e = {...}
if e[1]:find("mouse") and e[3] and e[4] then
for m=#menus,1,-1 do
local menu = menus[m]
local oselected = menu.selected
if e[1] == "mouse_click" or e[1] == "mouse_move" then
menu.selected = nil
end
if not lUtils.isInside(e[3],e[4],menu) then
if e[1] == "mouse_click" then
table.remove(menus,m)
if m == 1 then
objects.status = "dead"
return
end
end
else
if e[1] == "mouse_scroll" then
if e[2] == -1 and menu.scroll > 0 then
menu.scroll = menu.scroll-1
elseif e[2] == 1 and (menu.h-2)+menu.scroll < #menu then
menu.scroll = menu.scroll+1
end
elseif e[3] > menu.x and e[3] < menu.x+menu.w-1 then
if e[4] == menu.y+1 and menu.scroll > 0 then
if e[1] == "mouse_click" then
menu.scroll = menu.scroll-1
end
elseif e[4] == menu.y2-1 and (menu.h-2)+menu.scroll < #menu then
if e[1] == "mouse_click" then
menu.scroll = menu.scroll+1
end
else
for i=1+menu.scroll,(menu.h-2)+menu.scroll do
local o = menu[i]
if e[4] == o.y+menu.y-menu.scroll then
if o.action == "divider" or o.disabled then
break
end
-- check action
if e[1] == "mouse_up" and menu.selected == o then
if type(o.action) == "function" then
o.action(o)
objects.clicked = o
objects.status = "dead"
return o
elseif type(o.action) == "table" then
local submenu = genContextMenu(o.action,menu.x+menu.w-1,menu.y+o.y-menu.scroll-1,o.action.w or 0)
o.action.parent = o
local tW,tH = term.getSize()
if submenu.y+submenu.h-1 > tH and (submenu.y+2)-submenu.h+1 >= 1 then
submenu.y = (submenu.y+2)-submenu.h+1
else
positionMenu(submenu)
end
table.insert(menus,submenu)
elseif type(o.action) == "string" then
-- idk what to return
objects.clicked = o
objects.status = "dead"
return o
end
elseif e[1] == "mouse_up" then
menu.selected = nil
elseif e[1] == "mouse_click" or e[1] == "mouse_move" then
menu.selected = o
if e[1] == "mouse_click" then
if menu.oselected == menu.selected then
menu.selected = nil
menu.oselected = nil
else
menu.oselected = o
end
end
end
break
end
end
end
end
break
end
end
end
objects.status = "idle"
end
function objects.run()
objects.render()
while objects.status ~= "dead" do
objects.update(os.pullEvent())
objects.render()
end
return objects.clicked
end
return objects
end
function lUtils.clickmenu(x,y,w,options,redrawscreen,disabled,preferredColors)
local function disable(opt)
if opt.txt and disabled[opt.txt] then
opt.disabled = true
end
for o=1,#opt do
if type(opt[o]) == "table" then
disable(opt[o])
elseif type(opt[o]) == "string" and disabled[opt[o]] then
opt[o] = {txt=opt[o],action="output",disabled=true}
end
end
end
if disabled then
disable(options)
end
local menu = lUtils.contextmenu(x,y,w,options,preferredColors,true)
local clicked = menu.run()
if not clicked then
return false,0
else
if clicked.parentObj then
return true,clicked.parentObj.id,clicked.txt,clicked.id
else
return true,clicked.id,clicked.txt
end
end
end
function lUtils.isInside(x,y,object)
local function n(var)
return type(var) == "number"
end
local x1,y1,x2,y2
if n(object.x1) and n(object.y1) and n(object.x2) and n(object.y2) then
x1,y1,x2,y2 = object.x1,object.y1,object.x2,object.y2
elseif n(object.x) and n(object.y) then
x1,y1 = object.x,object.y
if n(object.w) and n(object.h) then
x2,y2 = x1+(object.w-1),y1+(object.h-1)
else
x2,y2 = x1,y1
end
else
error("Invalid input: "..textutils.serialize(object,{compact=true}),2)
end
if x2 < x1 then
x1,x2 = x2,x1
end
if y2 < y1 then
y1,y2 = y2,y1
end
if x >= x1 and y >= y1 and x <= x2 and y <= y2 then
return true
else
return false
end
end