2025-10-21 00:40:14 +02:00

2228 lines
56 KiB
Lua

-- shapescape
-- somehow you need to detect whether the inputted argument is the path from require or a path from shell to execute a .scape file
-- i guess the best way to do that is to just check if the file extension is .scape
-- lol yea
-- also if the file extension IS .scape, that means the API path is actually shell.getRunningProgram()
local tArgs = {...}
local shapescape = {utils={},slide={},scape={},generic={}}
local scapeRef = {}
local slideRef = {}
local corFilterCache = {}
local doExecute
if type(tArgs[1]) == "string" and string.find(tArgs[1],"%.%w+$") and string.sub(tArgs[1],string.find(tArgs[1],"%.%w+$")) == ".scape" then
-- execute file
-- somehow
doExecute = tArgs[1]
shapescape.path = shell.getRunningProgram()
else
shapescape.path = tArgs[2] or tArgs[1]
end
if shapescape.path then
shapescape.path = "/"..fs.getDir(shapescape.path)
end
do -- imports
local function doImport(t,k)
local n
for k,v in pairs(shapescape) do
if v == t then
n = k
break
end
end
if not n then
error("Module not found",2)
end
shapescape[n] = require("/"..fs.combine(shapescape.path,n))
if shapescape[n].init then
shapescape[n].init(shapescape)
end
for k,v in pairs(shapescape[n]) do
t[k] = v
end
return shapescape[n][k]
end
local function getFileType(filename)
if string.find(filename,"%.%w+$") == nil then
return ""
else
return string.sub(filename,string.find(filename,"%.%w+$"))
end
end
local function 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 getFileType(f) == ".llnk" then
f = string.sub(f,1,string.len(f)-string.len(getFileType(f)))
end
return string.gsub(f,"_"," ")
end
local ls = fs.list(shapescape.path)
for i,f in ipairs(ls) do
if f ~= "init.lua" then
local n = getFileName(f)
shapescape[n] = {import=function() doImport(shapescape[n]) end}
setmetatable(shapescape[n],{__index=doImport})
end
end
end
-- [[ UTILITIES API ]]
local utils = shapescape.utils
utils.wordwrap = function(txt,tWidth)
local lines = {}
for line in txt:gmatch("([^\n]*)\n?") do
table.insert(lines,"")
for word in line:gmatch("%S*%s?") do
local width
if type(tWidth) == "table" then
if not tWidth[#lines] then
lines[#lines] = nil
return lines
else
width = tWidth[#lines]
end
else
width = tWidth
end
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
while type(tWidth) == "table" and #lines > #tWidth do
lines[#lines] = nil
end
return lines
end
local animationTbl = {}
utils.renderImage = function(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",utils.toBlit(term.getBackgroundColor()))
bl[3] = string.gsub(spr[l][3]:gsub(" ","T"),"T",utils.toBlit(term.getBackgroundColor()))
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
utils.instantiate = function(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 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
utils.toColor = function(theblit)
return to_colors[theblit] or nil
end
utils.toBlit = function(thecolor)
return to_blit[thecolor] or nil
end
utils.fread = function(filepath)
local fread = fs.open(filepath,"r")
local thing = fread.readAll()
fread.close()
return thing
end
utils.locateEntry = function(tbl,value)
for k,v in pairs(tbl) do
if v == value then
return k
end
end
end
utils.genStatelessIterator = function(...)
local tbls = {...}
local function iter(tbl)
local temp = {}
for i=#tbls,1 do
for k,v in pairs(tbls[i]) do
temp[k] = v
end
end
return next,temp
end
return iter
end
-- [[ LOCAL API ]]
shapescape.debug = {scapeRef=scapeRef,slideRef=slideRef}
if not lOS.pDebug then
lOS.pDebug = {}
end
if not lOS.pDebug.scapes then
lOS.pDebug.scapes = {}
end
shapescape.debug.scapes = lOS.pDebug.scapes
local dLocal = shapescape.debug
local function runListener(listeners,scape)
for k=#listeners,1,-1 do
local func = listeners[k]
local ok,err = pcall(func,unpack(scape.args))
if not ok then
if not scape.blacklist then
scape.blacklist = {}
end
if scape.blacklist[func] then
table.remove(listeners,k)
else
scape.blacklist[func] = true
scape:warn(err)
end
end
end
end
dLocal.runListener = runListener
local pointerCache = {}
dLocal.pointerCache = {}
local function runPointer(pointerValue,scape,self)
local pointer = self.pointers[pointerValue]
local ret
if type(pointer) == "string" or pointer.type == "formula" then
local formula
if type(pointer) == "string" then
formula = pointer
else
formula = pointer.content
end
local env = {
self = self,
-- load shapescape api or something too
math = math,
}
env.width,env.height = scape.slides[scape.currentSlide]:getSize()
setmetatable(env,{__index=scape.variables})
local id = self.name or self.id
local func,err = load("return "..formula,"@"..id.."."..pointerValue, nil, env)
if not func then
func,err = load(formula,"@"..id.."."..pointerValue, nil, env)
end
if not func then
scape:warn(err)
else
local ok,output = pcall(func)
if ok then
if pointerValue == "text" and type(output) == "string" then
output = {content=output, color=self.textColor or colors.white}
elseif (pointerValue == "fill" or pointerValue == "border") and type(output) == "number" then
output = {color=output}
end
ret = output
else
scape:warn(output)
ret = 0
end
end
elseif pointer.type == "variable" then
-- parse dots
ret = scape.variables[pointer.content]
end
if not pointerCache[self] then
pointerCache[self] = {}
end
if pointerCache[self][pointerValue] ~= ret then
pointerCache[self][pointerValue] = ret
self:markAsChanged()
end
return ret
end
dLocal.runPointer = runPointer
local function deleted(t,k)
if k == "deleted" or k == "destroyed" then
return t
else
error("This object no longer exists.",2)
end
end
dLocal.deleted = deleted
local function captureCursor()
local cursor = {}
cursor.x,cursor.y = term.getCursorPos()
cursor.color = term.getTextColor()
cursor.blink = term.getCursorBlink()
return cursor
end
local function restoreCursor(cursor)
term.setCursorPos(cursor.x,cursor.y)
term.setTextColor(cursor.color)
term.setCursorBlink(cursor.blink)
end
local function resumeCoroutines(self,...)
local ev = {...}
local scape = self:getScape()
for i=#self.coroutines,1,-1 do
local cor = self.coroutines[i]
if coroutine.status(cor) == "dead" then
table.remove(self.coroutines,i)
elseif not (corFilterCache[cor] and ev[1] ~= corFilterCache[cor]) then
local oterm = term.current()
if self.window then
term.redirect(self.window)
end
local ok, filter = coroutine.resume(cor,...)
if not ok then
scape:warn(filter)
table.remove(self.coroutines,i)
else
corFilterCache[cor] = filter
end
term.redirect(oterm)
end
end
end
dLocal.resumeCoroutines = resumeCoroutines
local function fixChildren(o)
for i,obj in ipairs(o.children) do
scapeRef[obj] = scapeRef[o]
slideRef[obj] = slideRef[o]
if obj.children then
fixChildren(obj)
end
end
end
local changeCache = {}
local renderCache = {}
dLocal.renderCache = renderCache
dLocal.changeCache = changeCache
-- [[ GENERIC API ]]
local generic = shapescape.generic
generic.getAbsolutePosition = function(self,includePadding)
local x1,y1,x2,y2 = self.x1,self.y1,self.x2,self.y2
local o = self
while o.parent do
--local p = o.parent.padding or 0
--local pL,pR,pT,pB = (o.parent.paddingLeft or p), (o.parent.paddingRight or p), (o.parent.paddingTop or p), (o.parent.paddingBottom or p)
local off = 0
if o.parent.border then
off = 1
end
x1 = x1+o.parent.x1-1+off
x2 = x2+o.parent.x1-1+off
y1 = y1+o.parent.y1-1+off
y2 = y2+o.parent.y1-1+off
o = o.parent
end
if includePadding then
local p = self.padding or 0
local pL,pR,pT,pB = (self.paddingLeft or p), (self.paddingRight or p), (self.paddingTop or p), (self.paddingBottom or p)
x1, y1, x2, y2 = x1-pL, y1-pT, x2+pR, y2+pB
end
return x1,y1,x2,y2
end
generic.render = function(self, baseX, baseY, full)
-- for variable parameters use custom objects using metamethods; indexing self with that field would return the value of the variable it refers to
-- the metamethods would be applied on the parent of the parameter and be defined during object initialization
local oCursor = captureCursor()
baseX = baseX or 1
baseY = baseY or 1
local off = 0
if self.border then
off = 1
end
local p = self.padding or 0
local pL,pR,pT,pB = (self.paddingLeft or p), (self.paddingRight or p), (self.paddingTop or p), (self.paddingBottom or p)
local x1, y1, x2, y2 = self.x1+baseX-1-pL, self.y1+baseY-1-pT, self.x2+baseX-1+pR, self.y2+baseY-1+pB
if self.type == "circle" then
-- different rendering
elseif self.border or self.fill or self.text then -- literally all other rendering
local mode = "fill"
if not self.fill then
mode = "transparent"
end
-- no lUtils allowed
-- center, top, bottom, left, right, corTL, corTR, corBL, corBR
local p
local t = " "
local fg = " "
local bg = " "
if self.border and self.border.color then
fg = utils.toBlit(self.border.color)
elseif self.fill and self.fill.color and self.fill.color ~= 0 then
fg = utils.toBlit(self.fill.color)
end
if self.fill and self.fill.color and self.fill.color ~= 0 and not (self.window and self.active) then
bg = utils.toBlit(self.fill.color)
end
--print("fg="..fg.."\nbg="..bg)
--os.sleep(0.5)
local pc = {" ",bg,bg}
if self.border then
local weight = self.border.weight or 1
local dist = self.border.padding or 2
local ctbl = {
[1] = { -- weight
[0] = { -- distance
top = {"\143",t,fg},
bottom = {"\131",fg,t},
left = {"\149",t,fg},
right = {"\149",fg,t},
corTL = {"\159",t,fg},
corTR = {"\144",fg,t},
corBL = {"\130",fg,t},
corBR = {"\129",fg,t},
},
[1] = {
top = {"\140",fg,bg},
bottom = {"\140",fg,bg},
left = {"\149",fg,bg},
right = {"\149",bg,fg},
corTL = {"\156",fg,bg},
corTR = {"\147",bg,fg},
corBL = {"\141",fg,bg},
corBR = {"\142",fg,bg},
},
[2] = {
top = {"\131",fg,bg},
bottom = {"\143",bg,fg},
left = {"\149",fg,bg},
right = {"\149",bg,fg},
corTL = {"\151",fg,bg},
corTR = {"\148",bg,fg},
corBL = {"\138",bg,fg},
corBR = {"\133",bg,fg},
}, --maybe make more efficient in future using the strings "bg" and "fg" so grid doesn't have to regenerate on color change
}
}
p = utils.instantiate(ctbl[weight][dist])
p.center = pc
else
p = {center=pc, top=pc, bottom=pc, left=pc, right=pc, corTL=pc, corTR=pc, corBL=pc, corBR=pc}
end
local txt
local lines = {}
local txtlines = {}
if self.text and self.text.color and self.text.content then
txt = self.text.content or self.textContent
-- if self.fill.image or self.fill.pattern then replace center lines with that etc etc
-- add handling for blit lines
txtlines = utils.wordwrap(txt, (x2-pR-off)-(x1+pL+off)+1)
end
--_G.debuglines = {}
local winW,winH
if self.window then
winW,winH = self.window.getSize()
end
for y=y1,y2 do
local winLine
local absY = y-y1+1 - off-pT
if self.window then
if absY < 1 then
winLine = {self.window.getLine(1)}
elseif absY <= winH then
winLine = {self.window.getLine(absY)}
else
winLine = {self.window.getLine(winH)}
end
end
local txtline = txtlines[y-y1+1-pT-off]
term.setCursorPos(x1,y)
local line = {{},{},{}}
--table.insert(debuglines,line)
local function addPix(pix,amount)
amount = amount or 1
for i=1,amount do
table.insert(line[1],pix[1])
table.insert(line[2],pix[2])
table.insert(line[3],pix[3])
end
end
if not self.border then
addPix(p.center,x2-x1+1)
elseif y == y1 then
addPix(p.corTL)
addPix(p.top,x2-x1-1)
addPix(p.corTR)
elseif y == y2 then
addPix(p.corBL)
addPix(p.bottom,x2-x1-1)
addPix(p.corBR)
else
addPix(p.left)
addPix(p.center,x2-x1-1)
addPix(p.right)
end
local wLine
if self.window then
wLine = {
string.rep(" ",off+pL)..winLine[1]..string.rep(" ",off+pR),
string.rep(winLine[2]:sub(1,1),off+pL)..winLine[2]..string.rep(winLine[2]:sub(winW,winW),off+pR),
string.rep(winLine[3]:sub(1,1),off+pL)..winLine[3]..string.rep(winLine[3]:sub(winW,winW),off+pR),
}
elseif full then
local tw,th = term.getSize()
if y >= 1 and y <= th then
wLine = {term.current().getLine(y)}
else
wLine = {"","",""}
end
end
--_G.mydebugwline = wLine
if wLine then
for c=1,#line[1] do
local p = c+x1-1
if self.window then
p = c
end
local c1,c2,c3 = wLine[1]:sub(p,p),wLine[2]:sub(p,p),wLine[3]:sub(p,p)
if c1 == "" then c1 = " " end
if c2 == "" then c2 = "f" end
if c3 == "" then c3 = "f" end
if line[2][c] == " " and line[3][c] == " " then
line[1][c] = c1
line[2][c] = c2
line[3][c] = c3
elseif line[2][c] == " " then
line[2][c] = c3
elseif line[3][c] == " " then
line[3][c] = c3
end
end
end
for l=1,3 do
line[l] = table.concat(line[l])
end
if txtline then
line[1] = line[1]:sub(1,off+pL)..txtline..line[1]:sub(off+pL+#txtline+1)
--[[for s=off+pL+1,off+pL+#txtline do
local c = s-(off+pL)
line[1][s] = txtline:sub(c,c)]]
line[2] = line[2]:sub(1,off+pL)..string.rep(utils.toBlit(self.text.color or self.textColor),#txtline)..line[2]:sub(off+pL+#txtline+1)
end
if full then
term.blit(unpack(line))
else
table.insert(lines,line)
end
end
if not full then
renderCache[self] = lines
end
--[[if self.fill and self.fill.image then -- temporary, make it stretch n shit and put it in the line fill
if self.fill.color then
term.setBackgroundColor(self.fill.color)
end
utils.renderImage(self.fill.image,x1+pL+off,y1+pT+off,nil,(not self.fill.color))
end]]
end
if self.window and self.window.getCursorBlink() then
local x,y = self:getAbsolutePosition()
local col = self.window.getTextColor()
local cX,cY = self.window.getCursorPos()
term.setCursorPos(x+cX+off-1,y+cY+off-1)
term.setCursorBlink(true)
term.setTextColor(col)
else
restoreCursor(oCursor)
end
if self.children then
-- add base coords AND self coords (u become new parent but m aybe u already have parent)
for k,v in ipairs(self.children) do
if generic.isVisible(v) then
generic.render(v,x1+pL+off, y1+pT+off, full) --x1 and y1 are already base plus self stoopid
end
end
end
end
generic.update = function(self, ...)
if not self.active then
if not scapeRef[slideRef[self]] then
error("No scape attached",2)
end
self:initialize()
end
if self.window then
local sw,sh = self:getSize()
local ww,wh = self.window.getSize()
if sw ~= ww or sh ~= wh then
self:resize(sw,sh)
end
end
local fEv = "self.mouse"
if not self:getScape() then
fEv = "mouse"
end
local e = {...}
local runCor = true
local runChildren = true
if e[1]:find(fEv) == 1 then
--e[3] = e[3]-self.x1+1
--e[4] = e[4]-self.y1+1
-- completely wrong, get absolute position
if e[3] and e[4] then
local x1,y1,x2,y2 = self:getAbsolutePosition()
local off = 0
if self.border then
off = 1
end
e[3] = e[3]-(x1+off)+1
e[4] = e[4]-(y1+off)+1 -- test
end
local cE = table.pack(table.unpack(e))
cE[1] = cE[1]:gsub("self.","")
if self.coroutines then
resumeCoroutines(self,unpack(cE))
end
runCor = false
runChildren = false
elseif e[1]:find("mouse") == 1 then
runCor = false
end
local oterm = term.current()
if self.window then
term.redirect(self.window)
end
--[=[if self.eventListeners[e[1]] then
runListener(self.eventListeners[e[1]],self:getScape())
end]=]
local sTbl = {}
for i=1,#e do
sTbl[i] = tostring(e[i])
local l = table.concat(sTbl,".",1,i)
if self.eventListeners[l] then
runListener(self.eventListeners[l],self:getScape())
end
end
if self.coroutines and runCor then
resumeCoroutines(self,unpack(e))
end
if not self.destroyed then
if self.children and runChildren then
for i,child in ipairs(self.children) do
child:update(...)
end
end
end
term.redirect(oterm)
end
local privRef = {}
generic.initialize = function(self) -- provide the scape table to it for variables
local slide = slideRef[self]
local scape = scapeRef[slide]
if not scape then
error("No scape attached",2)
end
-- NOTE: CLASSES MUST NOT BE INITIALIZED, ONLY THEIR INSTANCES
-- except they should, just using custom class initialize
-- oh yeah i didnt make classes yet hm
-- figure out how to handle variables
-- maybe a table in an object with names of properties with variable values and then this table isn't looked at until initialize is ran and the properties are replaced with custom variable shit stuff
-- load the scripts into functions
-- etc
-- do everything that would make it not serializable
-- add scape log to log errors and shit and warnings
-- generate env
if not self.parent or not self.parent.env then
self.env = {
shell = scape.env.shell,
multishell = scape.env.multishell,
package = scape.env.package,
require = scape.env.require,
LevelOS = scape.env.LevelOS,
shapescape = scape.env.shapescape, --no. add as __index.
_SCAPE = scape,
_SLIDE = slide,
}
else
self.env = {}
for k,v in pairs(self.parent.env) do
self.env[k] = v
end
end
-- make local sahpesacpe api
self.env._ENV = self.env
self.env.self = self
setmetatable(self.env,{__index=scape.variables,__newindex=scape.variables}) -- scape.variables index is _G
if self.type == "window" then
local p = self.padding or 0
local pL,pR,pT,pB = (self.paddingLeft or p), (self.paddingRight or p), (self.paddingTop or p), (self.paddingBottom or p)
local off = 0
if self.border then
off = 1
end
local w,h = (self.x2-off)-(self.x1+off)+1, (self.y2-off)-(self.y1+off)+1
self.window = window.create(term.current(),self.x1+off,self.y1+off,w,h,false)
if self.fill and self.fill.color then
self.window.setBackgroundColor(self.fill.color)
self.window.clear()
self.window.setCursorPos(1,1)
end
if self.textColor then
self.window.setTextColor(self.textColor)
end
end
for i,e in pairs(self.eventListeners) do
if type(e) ~= "table" then
e = {e}
end
local newE = {}
for k,v in ipairs(e) do
local asset
if type(v) == "number" then
asset = scape:getAssetByID(v)
elseif type(v) == "string" then
asset = scape:getAssetByName(v)
else
asset = v
end
id, name = asset.id, asset.name
if asset then
local func,err = load(asset.content,"@"..(asset.name or asset.id),nil,self.env)
if not func then
scape:warn(err)
else
table.insert(newE,func)
end
-- put erorr if not loaded correctly idk
end
end
self.eventListeners[i] = newE
end
-- generate coroutines, run once with scape.args
for k,v in pairs(self.pointers) do
self[k] = nil
end
local oldindex = getmetatable(self).__index
local private = {}
privRef[self] = private
for k,v in pairs(self) do
private[k] = v
self[k] = nil
end
local mt = {
__index=function(tbl,k)
if private[k] then
return private[k]
elseif self.pointers[k] then
return runPointer(k,scape,self)
elseif k == "width" then
return self.x2-self.x1+1
elseif k == "height" then
return self.y2-self.y1+1
elseif k == "x" then
return self.x1
elseif k == "y" then
return self.y1
else
return oldindex[k]
end
end,
__newindex=function(tbl,k,v)
if tbl[k] ~= v then
if k == "width" then
self:resize(v,self.height)
elseif k == "height" then
self:resize(self.width,v)
elseif k == "x" then
self:move(v,self.y1)
elseif k == "y" then
self:move(self.x1,v)
elseif k == "x1" then
self:reposition(v)
elseif k == "y1" then
self:reposition(self.x1,v)
elseif k == "x2" then
self:reposition(self.x1,self.y1,v,self.y2)
elseif k == "y2" then
self:reposition(self.x1,self.y1,self.x2,v)
else
private[k] = v
self:markAsChanged()
end
end
end
}
if self.type == "group" then
mt.__pairs = utils.genStatelessIterator(private, group, generic)
else
mt.__pairs = utils.genStatelessIterator(private, generic)
end
setmetatable(self, mt)
self.active = true
if self.children then
for k,v in ipairs(self.children) do
if v.name then
if not self[v.name] then
self[v.name] = v
end
if not self.env[v.name] then
self.env[v.name] = v
end
end
v:initialize()
end
end
if self.coroutines then
local args = scape.args or {}
for k=#self.coroutines,1,-1 do
local v = self.coroutines[k]
local asset = scape.assets[v]
if asset then
local func,err = load(asset.content,"@"..(asset.name or asset.id),nil,self.env)
if not func then
scape:warn(err)
table.remove(self.coroutines,k)
else
self.coroutines[k] = coroutine.create(func)
end
end
end
resumeCoroutines(self,unpack(args))
end
if self.eventListeners["initialize"] then
runListener(self.eventListeners["initialize"],scape)
end
return true
end
generic.reposition = function(self,x1,y1,x2,y2)
-- take care of aligning n shit idk
x1,y1,x2,y2 = x1 or self.x1, y1 or self.y1, x2 or self.x2, y2 or self.y2
if self.active then
if not self.pointers.x1 then
privRef[self].x1 = x1
end
if not self.pointers.y1 then
privRef[self].y1 = y1
end
if not self.pointers.x2 then
privRef[self].x2 = x2
end
if not self.pointers.y2 then
privRef[self].y2 = y2
end
--self:markAsChanged()
renderCache[self:getSlide()] = nil
else
self.x1,self.y1,self.x2,self.y2 = x1,y1,x2,y2
end
if self.window then-- position dont matter shit, just size (window is invisible) thats wrong motherfucker it absolutely does
local p = self.padding or 0
local pL,pR,pT,pB = (self.paddingLeft or p), (self.paddingRight or p), (self.paddingTop or p), (self.paddingBottom or p)
local off = 0
if self.border then
off = 1
end
local w,h = (self.x2-off)-(self.x1+off)+1, (self.y2-off)-(self.y1+off)+1
local x,y = self:getAbsolutePosition()
self.window.reposition(x+off,y+off,w,h)
end
end
generic.move = function(self,x,y)
x = x or self.x1
y = y or self.y1
local offX,offY = x-self.x1,y-self.y1
self:reposition(x,y,self.x2+offX,self.y2+offY)
end
generic.resize = function(self,width,height)
self:reposition(self.x1,self.y1,((width and (self.x1+width-1)) or self.x2),((height and (self.y1+height-1)) or self.y2))
end
generic.duplicate = function(self,properties)
if self.active then
error("Cannot duplicate active objects",2)
end
return self:getSlide():addObject(utils.instantiate(self))
end
generic.destroy = function(self,full)
if full == nil then
full = true
end
if self.parent then
for k,v in pairs(self.parent.children) do
if v == self then
table.remove(self.parent.children,k)
break
end
end
for k,v in pairs(self.parent) do
if v == self then
self.parent[k] = nil
end
end
local sl = self:getSlide()
elseif not self.isClass then
local sl = self:getSlide()
for k,v in pairs(sl.objects) do
if v == self then
table.remove(sl.objects,k)
end
end
for id,obj in ipairs(sl.objects) do
obj.id = id -- so ID is not static hm
-- then again it doesn't really need to be because string IDs *are* static
end
end
if full then
for k,v in pairs(self) do
self[k] = nil
end
setmetatable(self,{__index=deleted,__newindex=deleted})
end
if slideRef[self] then
renderCache[slideRef[self]] = nil
slideRef[self] = nil
end
end
generic.markAsChanged = function(self)
local slide = self:getSlide()
if not slide then
return false
else
if not changeCache[slide] then
changeCache[slide] = {}
end
if changeCache[self] then
for k,v in pairs(changeCache[self]) do
if k ~= "old" and self[k] ~= v then
changeCache[slide][self] = 2
break
end
end
end
local x1,y1,x2,y2 = self:getAbsolutePosition(true)
changeCache[self] = {x1=x1,y1=y1,x2=x2,y2=y2,old=changeCache[self]}
changeCache[slide][self] = changeCache[slide][self] or 1
if self.children then
for k,v in pairs(self.children) do
v:markAsChanged()
end
end
end
end
generic.getSize = function(self)
return self.x2-self.x1+1,self.y2-self.y1+1
end
generic.getPosition = function(self)
return self.x1,self.y1
end
generic.getBackgroundColor = function(self)
if not self.fill then
return 0
else
return self.fill.color or 0
end
end
generic.getBorderColor = function(self)
if not self.border then
return 0
else
return self.border.color or 0
end
end
generic.getTextColor = function(self)
if not self.text then
return 0
else
return self.text.color or 0
end
end
generic.getText = function(self)
if not self.text then
return nil
else
return self.text.content
end
end
generic.setBackgroundColor = function(self,color)
if color == nil or color == 0 then
self.fill = nil
else
if not self.fill then self.fill = {} end
self.fill.color = color
end
self:markAsChanged()
end
generic.setTextColor = function(self,color)
if not self.text then self.text = {content=""} end
self.text.color = color
self:markAsChanged()
end
generic.setText = function(self,content,color)
if not self.text then self.text = {} end
self.text.color = color or self.text.color or self.txtcolor or self.textColor or colors.black
self.text.content = content
self:markAsChanged()
end
generic.setBorderColor = function(self,color)
if color == nil or color == 0 then
self.border = nil
else
if not self.border then self.border = {weight=1,padding=2} end
self.border.color = color
end
self:markAsChanged()
end
generic.setPointer = function(self, key, value)
if not self.pointers then self.pointers = {} end
if self.active then
privRef[self][key] = nil
end
self.pointers[key] = value
end
generic.removePointer = function(self, key)
if self.active then
self[key] = self[key]
end
self.pointers[key] = nil
end
generic.addListener = function(self, sEvent, sAsset)
local scape = scapeRef[self]
if not self.eventListeners then
self.eventListeners = {}
end
if sEvent == "coroutine" then
if not self.coroutines then
self.coroutines = {}
end
elseif not self.eventListeners[sEvent] then
self.eventListeners[sEvent] = {}
end
if self.active and (type(sAsset) == "string" or type(sAsset) == "number") and scape then
local asset
if type(sAsset) == "string" then
asset = scape:getAssetByName(sAsset)
else
asset = scape:getAssetByID(sAsset)
end
if asset then
local func,err = load(asset.content,"@"..(asset.name or asset.id),nil,self.env)
if not func then
scape:warn(err)
elseif sEvent == "coroutine" then
local args = scape.args or {}
local k = #self.coroutines+1
self.coroutines[k] = coroutine.create(func)
local oterm = term.current()
if self.window then
term.redirect(self.window)
end
local ok,filter = coroutine.resume(self.coroutines[k],unpack(args))
if not ok then
scape:warn(filter)
table.remove(self.coroutines,k)
else
corFilterCache[self.coroutines[k]] = filter
end
term.redirect(oterm)
else
table.insert(self.eventListeners[sEvent],func)
end
end
elseif self.active and sEvent == "coroutine" then
local func = sAsset
local args = scape.args or {}
local k = #self.coroutines+1
self.coroutines[k] = coroutine.create(func)
local oterm = term.current()
if self.window then
term.redirect(self.window)
end
local ok,filter = coroutine.resume(self.coroutines[k],unpack(args))
if not ok then
scape:warn(filter)
table.remove(self.coroutines,k)
else
corFilterCache[self.coroutines[k]] = filter
end
term.redirect(oterm)
elseif sEvent == "coroutine" then
table.insert(self.coroutines,sAsset)
else
table.insert(self.eventListeners[sEvent],sAsset)
end
end
generic.getSlide = function(self)
return slideRef[self]
end
generic.getScape = function(self)
return scapeRef[self]
end
generic.isInside = function(self,x,y)
local x1,y1,x2,y2 = self:getAbsolutePosition(true)
if x >= x1 and y >= y1 and x <= x2 and y <= y2 then
return true
end
end
generic.arrange = function(self,newPositionID)
local slide = self:getSlide()
if not slide then
error("No slide attached to object",2)
end
local parent = slide.objects
if self.parent then
parent = self.parent.children
end
if newPositionID < 0 then
newPositionID = #parent+newPositionID+2
end
newPositionID = math.min(#parent+1,newPositionID)
table.remove(parent,self.id)
table.insert(parent,newPositionID,self)
for id,iObj in ipairs(parent) do
iObj.id = id
end
end
generic.moveToFront = function(self)
self:arrange(-1)
end
generic.moveToBack = function(self)
self:arrange(1)
end
generic.moveForward = function(self)
self:arrange(self.id+1)
end
generic.moveBackward = function(self)
self:arrange(math.max(1,self.id-1))
end
-- [[ GROUP API ]]
shapescape.group = {}
local group = shapescape.group
setmetatable(group, {__index=generic})
group.resetSize = function(self)
local off = 0
if self.border then
off = 1
end
local baseX,baseY = self.x1+off,self.y1+off
self.x1 = self.children[1].x1+baseX-1-off
self.y1 = self.children[1].y1+baseY-1-off
self.x2 = self.children[1].x2+baseX-1+off
self.y2 = self.children[1].y2+baseY-1+off
for i=2,#self.children do
local o = self.children[i]
self.x1 = math.min(self.x1,o.x1+baseX-1-off)
self.y1 = math.min(self.y1,o.y1+baseY-1-off)
self.x2 = math.max(self.x2,o.x2+baseX-1+off)
self.y2 = math.max(self.y2,o.y2+baseY-1+off)
end
local nBaseX,nBaseY = self.x1+off,self.y1+off
local offX,offY = nBaseX-baseX,nBaseY-baseY
for i=1,#self.children do
self.children[i]:move(self.children[i].x1-offX,self.children[i].y1-offY)
end
end
group.addObject = function(self,obj)
local off = 0
if self.border then
off = 1
end
local xOff,yOff,_,_ = self:getAbsolutePosition()
xOff = xOff+off-1
yOff = yOff+off-1
obj.x1,obj.y1,obj.x2,obj.y2 = obj.x1-xOff,obj.y1-yOff,obj.x2-xOff,obj.y2-yOff
obj.parent = self
shapescape.loadObject(obj)
scapeRef[obj] = scapeRef[self]
slideRef[obj] = slideRef[self]
if obj.children then
fixChildren(obj)
end
table.insert(self.children,obj)
obj.id = #self.children
self:resetSize()
if self.active then
obj:initialize()
end
self:resetSize()
local sl = self:getSlide()
if sl then
renderCache[sl] = nil
end
end
local invisibleObjects = {}
generic.setVisible = function(self,visibility)
if not visibility then
invisibleObjects[self] = true
else
invisibleObjects[self] = false
end
end
generic.isVisible = function(self)
return not invisibleObjects[self]
end
-- [[ SLIDE API ]]
local slide = shapescape.slide
slide.initialize = function(self)
self:setVisible(true)
local scape = scapeRef[self]
self.window = window.create(term.current(), 1, 1, self.width, self.height, self:isVisible())
self:render(true)
for i=1,#self.objects do
local obj = self.objects[i]
obj:initialize(scape)
if obj.name and not scape.variables[obj.name] then
scape.variables[obj.name] = obj
end
end
self.active = true
end
slide.getScape = function(self)
return scapeRef[self]
end
slide.getSize = function(self)
return self.width,self.height
end
slide.resize = function(self, width, height)
self.width, self.height = width, height
if self.window then
local x,y = self.window.getPosition()
self.window.reposition(x,y,width,height)
end
end
slide.loadObject = function(self,obj)
shapescape.loadObject(obj)
scapeRef[obj] = scapeRef[self]
slideRef[obj] = self
if obj.children then
fixChildren(obj)
end
renderCache[self] = nil
return obj
end
slide.addObject = function(self,obj)
obj.id = #self.objects+1
table.insert(self.objects,obj)
return self:loadObject(obj)
end
slide.createObject = function(self,oType,x1,y1,x2,y2,properties)
return self:addObject(shapescape.createObject(oType,x1,y1,x2,y2,properties))
end
slide.createRectangle = function(self,x1,y1,x2,y2,fill,border,properties)
return self:addObject(shapescape.createRectangle(x1,y1,x2,y2,fill,border,properties))
end
slide.createWindow = function(self,x1,y1,x2,y2,fill,textColor,border,properties)
return self:addObject(shapescape.createWindow(x1,y1,x2,y2,fill,textColor,border,properties))
end
slide.createGroup = function(self,objects,properties)
return self:addObject(shapescape.createGroup(objects,properties))
end
slide.isVisible = function(self)
return self.visible
end
slide.setVisible = function(self,visibility)
self.visible = not not visibility
if self.window then
self.window.setVisible(self.visible)
end
end
slide.getBackgroundColor = function(self)
if not self.background then
return 0
else
return self.background.color or 0
end
end
slide.setBackgroundColor = function(self,color)
if not self.background then self.background = {} end
self.background.color = color
end
slide.genCache = function(self,objs,doDebug)
if not objs then
renderCache[self] = {}
objs = self.objects
end
local c = renderCache[self]
for i=1,#objs do
local o = objs[i]
if o:isVisible() then
local x1,y1,x2,y2 = o:getAbsolutePosition(true)
for x=x1,x2 do
if not c[x] then
c[x] = {}
end
local cx = c[x]
for y=y1,y2 do
if not cx[y] then
cx[y] = {o}
else
table.insert(cx[y],1,o)
end
end
end
if doDebug then
o:render(1,1,true)
os.sleep(1)
term.clear()
os.sleep(1)
end
if o.children then
self:genCache(o.children, doDebug)
end
end
end
end
slide.render = function(self,full)
local oterm = term.current()
if self.window then
term.redirect(self.window)
end
local function get(pix,x,y)
local fgn = 1
local o = pix[fgn]
local ox,oy = o:getAbsolutePosition(true)
local fgp = renderCache[o] and renderCache[o][y-oy+1] and renderCache[o][y-oy+1][2]:sub(x-ox+1,x-ox+1) or " "
while fgp == " " do
fgn = fgn+1
if fgn > #pix then
fgp = utils.toBlit(term.getBackgroundColor())
break
end
local o = pix[fgn]
ox,oy = o:getAbsolutePosition(true)
fgp = renderCache[o] and renderCache[o][y-oy+1] and renderCache[o][y-oy+1][2]:sub(x-ox+1,x-ox+1) or " "
end
local bgn = 1
local o = pix[bgn]
local ox,oy = o:getAbsolutePosition(true)
local bgp = renderCache[o] and renderCache[o][y-oy+1] and renderCache[o][y-oy+1][3]:sub(x-ox+1,x-ox+1) or " "
while bgp == " " do
bgn = bgn+1
if bgn > #pix then
bgp = utils.toBlit(term.getTextColor())
break
end
local o = pix[bgn]
ox,oy = o:getAbsolutePosition(true)
bgp = renderCache[o] and renderCache[o][y-oy+1] and renderCache[o][y-oy+1][3]:sub(x-ox+1,x-ox+1) or " "
end
local txtn = math.min(fgn,bgn)
if txtn > #pix then
txtp = " "
else
local o = pix[txtn]
local ox,oy = o:getAbsolutePosition(true)
txtp = renderCache[pix[txtn]] and renderCache[pix[txtn]][y-oy+1][1]:sub(x-ox+1,x-ox+1) or " "
end
if #txtp ~= #fgp or #txtp ~= #bgp or #fgp ~= #bgp then
_G.x,_G.y,_G.txtp,_G.fgp,_G.bgp,_G.pix = x,y,txtp,fgp,bgp,pix
error("wtf")
end
--table.insert(debuglogshit,{txtp=txtp,fgp=fgp,bgp=bgp,pix=pix,x=x,y=y})
return txtp,fgp,bgp
end
if self.background then
if self.background.color then
term.setBackgroundColor(self.background.color)
elseif self.background.image then
-- idk
end
end
local bg = utils.toBlit(term.getBackgroundColor())
if full or not renderCache[self] then
term.clear()
for i,o in ipairs(self.objects) do
if not renderCache[o] then
o:render()
end
end
if not renderCache[self] then
self:genCache()
end
local w,h = self:getSize()
local c = renderCache[self]
_G.debugLines = {}
for y=1,h do
term.setCursorPos(1,y)
local line = {{},{},{}}
for x=1,w do
if c[x] and c[x][y] then
local cxy = c[x][y]
l1,l2,l3 = get(cxy,x,y)
_G.debugPix = {l1,l2,l3}
table.insert(line[1],l1)
table.insert(line[2],l2)
table.insert(line[3],l3)
--table.insert(debuglogshit,{l1,l2,l3})
else
table.insert(line[1]," ")
table.insert(line[2],bg)
table.insert(line[3],bg)
--table.insert(debuglogshit,{" ",bg,bg})
end
end
-- THIS LINE INTERRUPTS YOU MOTHERFUCKER WORK PLEASE
_G.moredebugshit = {line[1],line[2],line[3]}
table.insert(debugLines,{table.concat(line[1]),table.concat(line[2]),table.concat(line[3])})
term.blit(table.concat(line[1]),table.concat(line[2]),table.concat(line[3]))
end
elseif changeCache[self] then
--[[for k,v in pairs(changeCache[self]) do
if v == 2 then
renderCache[self] = nil
self:genCache()
self:render(true)
break
end
end]]
if self.debugMode and not self.i then
self.i = 0
end
local c = renderCache[self]
_G.debugRenderList = {}
if not changeCache.previous then
changeCache.previous = {}
end
for o,t in pairs(changeCache[self]) do
if not o.destroyed then
o:render()
table.insert(debugRenderList,o)
local x1,y1,x2,y2 = o:getAbsolutePosition(true)
local cur = {x1=x1,y1=y1,x2=x2,y2=y2}
local changePos = false
if changeCache.previous[o] then
local p = changeCache.previous[o]
for k,v in pairs(p) do
if v ~= cur[k] then
-- render previous position
changePos = true
for y=p.y1,p.y2 do
local line = {{},{},{}}
for x=p.x1,p.x2 do
if c[x] and c[x][y] then
local cxy = c[x][y]
table.remove(cxy,utils.locateEntry(cxy,o))
if #cxy == 0 then
c[x][y] = nil
end
end
if c[x] and c[x][y] then
if self.debugMode then
term.setCursorPos(x,y)
term.setBackgroundColor(2^self.i)
term.write(" ")
else
local cxy = c[x][y]
l1,l2,l3 = get(cxy,x,y)
_G.debugPix = {l1,l2,l3}
table.insert(line[1],l1)
table.insert(line[2],l2)
table.insert(line[3],l3)
end
else
if self.debugMode then
term.setCursorPos(x,y)
term.setBackgroundColor(2^self.i)
term.setTextColor(2^((self.i+1)%16))
term.write("0")
else
table.insert(line[1]," ")
table.insert(line[2],bg)
table.insert(line[3],bg)
end
end
end
if not self.debugMode then
term.setCursorPos(p.x1,y)
term.blit(table.concat(line[1]),table.concat(line[2]),table.concat(line[3]))
end
end
break
end
end
end
changeCache.previous[o] = cur
for y=y1,y2 do
local line = {{},{},{}}
for x=x1,x2 do
if changePos then
if not c[x] then
c[x] = {}
end
if not c[x][y] then
c[x][y] = {}
end
local cxy = c[x][y]
table.insert(cxy,o)
-- compare equal parent levels but FUCK how do I do that
-- OH I KNOW
-- if parent and parent does NOT equal go up a level
-- if not parent or parent equals then bigger ID wins
-- wait fuck im dumb, what if b IS the parent
local function isOnTop(a, b)
if a.parent == b.parent then
return a.id > b.id
elseif a.parent == b then
return true
elseif a == b.parent then
return false
else
if a.parent then
if b.parent then
local r = isOnTop(a.parent, b.parent)
if r ~= nil then
return r
end
end
local r = isOnTop(a.parent, b)
if r ~= nil then
return r
end
end
if b.parent then
local r = isOnTop(a, b.parent)
if r ~= nil then
return r
end
end
end
end
table.sort(cxy,function(a,b) return isOnTop(a,b) end) -- bruh
end
if c[x] and c[x][y] then
if self.debugMode then
term.setCursorPos(x,y)
term.setBackgroundColor(2^self.i)
term.write(" ")
else
local cxy = c[x][y]
l1,l2,l3 = get(cxy,x,y)
_G.debugPix = {l1,l2,l3,cxy=cxy}
table.insert(line[1],l1)
table.insert(line[2],l2)
table.insert(line[3],l3)
end
else
if self.debugMode then
term.setCursorPos(x,y)
term.setBackgroundColor(2^self.i)
term.setTextColor(2^((self.i+1)%16))
term.write("0")
else
table.insert(line[1]," ")
table.insert(line[2],bg)
table.insert(line[3],bg)
end
end
end
if not self.debugMode then
term.setCursorPos(x1,y)
term.blit(table.concat(line[1]),table.concat(line[2]),table.concat(line[3]))
end
end
end
end
if self.debugMode and #debugRenderList > 0 then
self.i = (self.i+1)%16
end
changeCache[self] = {}
end
term.redirect(oterm)
end
slide.destroy = function(self)
local scape = self:getScape()
for k,v in pairs(scape.slides) do
if v == self then
table.remove(scape.slides,k)
end
end
for k,v in ipairs(scape.slides) do
v.id = k
end
for k,v in pairs(self) do
self[k] = nil
end
setmetatable(self,{__index=deleted,__newindex=deleted})
end
-- [[ SCAPE API ]]
local scape = shapescape.scape
scape.initialize = function(self,env,args)
table.insert(shapescape.debug.scapes,self)
self.env = env or _ENV
self.env.shapescape = {
getEvent = function()
return unpack(self.event)
end,
getSlide = function()
return self.slides[self.currentSlide]
end,
setSlide = function(slideID)
-- if string search actually not necessary
if self.slides[slideID] then
self.currentSlide = self.slides[slideID].id
os.pullEvent()
end
end,
getSlides = function()
return self.slides
end,
getScape = function()
return self
end,
exit = function(...)
self.returnValues = table.pack(...)
self.status = "dead"
end,
}
for k,v in pairs(shapescape) do
self.env.shapescape[k] = v
end
if not self.variables then
self.variables = {}
end
setmetatable(self.variables,{__index=_G})
self.status = "running"
self.log = {}
self.args = args or {}
self.currentSlide = 1
self.active = true
--_G.debugSelf = self
for s=1,#self.slides do
-- dont initialize slides until update
local sl = self.slides[s]
if sl.name and not self.slides[sl.name] then
self.slides[sl.name] = sl
end
end
for c=1,#self.classes do
self.classes[c]:initialize()
end
end
scape.loadSlide = function(self,tSlide)
local w,h = term.getSize()
width = tSlide.width or w
height = tSlide.height or h
setmetatable(tSlide,{__index=slide})
scapeRef[tSlide] = self
for o=1,#tSlide.objects do
local obj = tSlide.objects[o]
tSlide:loadObject(obj)
end
return tSlide
end
scape.newSlide = function(self,width,height,backgroundColor)
local w,h = term.getSize()
width = width or w
height = height or h
local sl = {width=width,height=height,visible=true,objects={},id=#self.slides+1}
if backgroundColor then
sl.background = {color=backgroundColor}
end
--sl.window = window.create(term.current(),1,1,width,height,false)
table.insert(self.slides,sl)
return self:loadSlide(sl)
end
scape.removeSlide = function(self, slide)
if type(slide) ~= "table" then
for k,v in pairs(self.slides) do
if k == slide then
slide = v
break
end
end
if type(slide) ~= "table" then
return false
end
end
for k,v in pairs(self.slides) do
if v == slide then
table.remove(self.slides,k)
end
end
return true
end
scape.setSlide = function(self,nSlide)
if type(nSlide) == "table" and nSlide.id then
self.currentSlide = nSlide.id
elseif type(nSlide) == "number" then
self.currentSlide = nSlide
else
error("bad argument #2 to 'scape.setSlide' (expected slide, got "..type(nSlide)..")",2)
end
end
scape.update = function(self, ...)
-- i dont feel like this anymore
-- maybe instead of just mouse_click, make it self.mouse_click or <name>.mouse_click
-- and then mouse_click will execute on every mouse click, no matter where was clicked
local e = table.pack(...)
self.event = e
local s = self.slides[self.currentSlide] -- idiot
if not s.active then
s:initialize()
end
for o=#s.objects,1,-1 do
if not s.objects[o].active then
s.objects[o]:initialize()
end
s.objects[o]:update(...)
end
local secEvent
-- NOTE: transparent objects with children let mouse clicks through when click doesn't collide with any child
local function checkCollisions(objs,prefix)
local clickedObj
for o=#objs,1,-1 do
if objs[o]:isVisible() and objs[o]:isInside(e[3],e[4]) then
if objs[o].children then
local pre
if objs[o].name and prefix then
pre = prefix..objs[o].name.."."
end
clickedObj = checkCollisions(objs[o].children,pre)
end
if not (objs[o].children and objs[o]:getBackgroundColor() == 0 and not clickedObj) then -- only passes through if a child wasn't clicked
local newEvent = utils.instantiate(e)
newEvent[1] = "self."..e[1]
objs[o]:update(unpack(newEvent)) -- will NOT update parent, meaning parent should NEVER pass self.mouse events to children
if not clickedObj then
clickedObj = objs[o]
if objs[o].name and prefix then
secEvent = utils.instantiate(e)
secEvent[1] = (prefix or "")..objs[o].name.."."..e[1]
end
end
return clickedObj
end
end
end
end
if e[1]:find("mouse") == 1 and e[3] and e[4] then
checkCollisions(s.objects,"")
end
if secEvent then
for o=1,#s.objects do
s.objects[o]:update(unpack(secEvent))
end
end
end
scape.render = function(self)
term.setCursorBlink(false)
self.slides[self.currentSlide]:render()
if self.log then
local cursor = captureCursor()
local warns = {}
for l=#self.log,1,-1 do
local entry = self.log[l]
if os.epoch("utc") <= entry.time+5000 then
table.insert(warns,entry)
else
break
end
end
local warnCols = {
["WARNING"] = colors.orange,
["ERROR"] = colors.red,
["MSG"] = colors.lime,
}
local y = 2
local w,h = term.getSize()
for i,warn in ipairs(warns) do
term.setCursorPos(2,y)
local prefix = "["..warn.type.."] "
local lines = utils.wordwrap(prefix..tostring(warn.text),w-4)
width = 0
for l=1,#lines do
width = math.max(width,#lines[l]+2)
end
term.setBackgroundColor(colors.gray)
term.write(string.rep(" ",width))
term.setCursorPos(2,y)
term.setTextColor(warnCols[warn.type])
term.write("\149")
term.setTextColor(colors.white)
term.write(prefix)
term.setTextColor(warnCols[warn.type])
term.write(lines[1]:sub(#prefix+1).." ")
y = y+1
for l=2,#lines do
term.setCursorPos(2,y)
term.write("\149"..lines[l]..string.rep(" ",width-#lines[l]-3))
y = y+1
end
y = y+1
end
restoreCursor(cursor)
end
end
scape.run = function(self,...)
local w,h = term.getSize()
for s=1,#self.slides do
self.slides[s]:resize(w,h)
end
if not self.active then
self:initialize(_ENV, table.pack(...))
end
self:render()
while self.status ~= "dead" do
local e = table.pack(os.pullEvent())
if e[1] == "term_resize" then
local w,h = term.getSize()
for s=1,#self.slides do
self.slides[s]:resize(w,h)
end
end
self:update(table.unpack(e,1,e.n))
self:render()
end
return unpack(self.returnValues or {})
end
scape.createSlide = function(self,objects,width,height)
sl = self:newSlide(width,height)
for o=1,#objects do
local obj = objects[o]
sl:createObject(obj.type,obj.x1,obj.y1,obj.x2,obj.y2,obj)
end
end
scape.addScript = function(self, sCode, sName)
self.assets[self.lastAssetID+1] = {content=sCode,name=sName,id=self.lastAssetID+1}
self.lastAssetID = self.lastAssetID+1
end
scape.getAssetByName = function(self, sName)
for k,v in pairs(self.assets) do
if v.name == sName then
return v
end
end
end
scape.getAssetByID = function(self, nID)
return self.assets[nID]
end
scape.getAPI = function(self)
return shapescape
end
scape.message = function(self,message,type)
type = type or "MSG"
local types = {["MSG"]=true,["WARNING"]=true,["ERROR"]=true}
if not types[type] then
error("Invalid type '"..type.."'",2)
end
for e=#self.log,1,-1 do
if os.epoch("utc") > self.log[e].time+5000 then
break
elseif self.log[e].text == message and self.log[e].type == type then
table.remove(self.log,e)
end
end
table.insert(self.log,{time=os.epoch("local"),text=message,type=type})
end
scape.warn = function(self,warning)
self:message(warning,"WARNING")
end
-- [[ GLOBAL API ]]
shapescape.loadScape = function(tScape)
local sc
if type(tScape) == "table" then
sc = tScape
elseif type(tScape) == "string" and fs.exists(tScape) then
local content = utils.fread(tScape)
sc = textutils.unserialize(content)
end
setmetatable(sc,{__index=scape})
for s=1,#sc.slides do
sc:loadSlide(sc.slides[s])
end
local function classChildren(obj)
for i,o in ipairs(obj.children) do
scapeRef[o] = scapeRef[obj]
if o.children then
classChildren(o)
end
end
end
if not sc.classes then
if sc.templates then
sc.classes = sc.templates
sc.templates = nil
else
sc.classes = {}
end
end
for c=1,#sc.classes do
scapeRef[sc.classes[c]] = sc
if sc.classes[c].children then
classChildren(sc.classes[c])
end
shapescape.class.load(sc.classes[c])
end
return sc
end
shapescape.createScape = function(slides,assets,classes)
local sc = {slides=slides or {},assets=assets or {},classes=classes or {},variables={}} -- maybe width/height or something idk
-- ooh metatables
sc.lastAssetID = 0
for k,v in pairs(sc.assets) do
sc.lastAssetID = math.max(sc.lastAssetID,v.id+1)
end
return shapescape.loadScape(sc)
end
shapescape.loadObject = function(obj)
if type(obj.fill) == "number" then
if obj.fill == 0 then
obj.fill = nil
else
obj.fill = {color=obj.fill}
end
end
if type(obj.border) == "number" then
if obj.border == 0 then
obj.border = nil
else
obj.border = {color=obj.border,weight=1,padding=2}
end
end
if type(obj.text) == "number" then
if obj.text == 0 then
obj.text = nil
else
obj.text = {color=obj.text,content=""}
end
elseif type(obj.text) == "string" then
obj.text = {color=obj.txtcolor or obj.textColor or colors.black,content=obj.text}
end
if not obj.eventListeners then
obj.eventListeners = {}
end
if not obj.pointers then
obj.pointers = {}
end
if obj.type == "group" then
setmetatable(obj,{__index=group})
else
setmetatable(obj,{__index=generic})
end
if obj.children then
for i,child in ipairs(obj.children) do
shapescape.loadObject(child)
child.parent = obj
child.id = i
end
end
local x1,y1,x2,y2 = obj:getAbsolutePosition()
changeCache[obj] = {x1=x1,y1=y1,x2=x2,y2=y2}
return obj
end
shapescape.createObject = function(oType,x1,y1,x2,y2,properties)
properties = properties or {}
local obj = {x1=x1,y1=y1,x2=x2,y2=y2,type=oType}
for k,v in pairs(properties) do
obj[k] = v
end
return shapescape.loadObject(obj)
end
shapescape.createRectangle = function(x1,y1,x2,y2,fill,border,properties)
properties = properties or {}
properties.fill = fill
properties.border = border
return shapescape.createObject("rect",x1,y1,x2,y2,properties)
end
shapescape.createWindow = function(x1,y1,x2,y2,fill,textColor,border,properties)
properties = properties or {}
properties.fill = fill
properties.border = border
properties.textColor = textColor
return shapescape.createObject("window",x1,y1,x2,y2,properties)
end
shapescape.createGroup = function(objects,properties)
properties = properties or {}
properties.children = objects
if not objects or not objects[1] then return end
local x1,y1,x2,y2 = objects[1].x1,objects[1].y1,objects[1].x2,objects[1].y2
for i=2,#objects do
--print(x1,y1,x2,y2)
local o = objects[i]
x1 = math.min(x1,o.x1)
y1 = math.min(y1,o.y1)
x2 = math.max(x2,o.x2)
y2 = math.max(y2,o.y2)
--print("after object: "..o.x1..","..o.y1..","..o.x2..","..o.y2..":",x1,y1,x2,y2)
--os.sleep(0.5)
end
--os.sleep(2)
local obj = shapescape.createObject("group",x1,y1,x2,y2,properties)
setmetatable(obj,{__index=group})
for i,o in ipairs(objects) do
o.x1,o.y1,o.x2,o.y2 = o.x1-x1+1,o.y1-y1+1,o.x2-x1+1,o.y2-y1+1
o.parent = obj
o.id = i
end
return obj
end
--return {utils=utils,generic=generic,scape=scape,createObject=createObject,createRectangle=createRectangle,createGroup=createGroup}
return shapescape