-- 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,"",1,true) ~= nil then begin = nil ending = nil trash,begin = string.find(thing,"<"..fieldname..">",1,true) ending,ending2 = string.find(thing,"",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