local tHex = require("tHex") local process = require("process") local sub = string.sub return function(name, basalt) local base = basalt.getObject("VisualObject")(name, basalt) local objectType = "Program" local object local cachedPath local enviroment = {} local function createBasaltWindow(x, y, width, height) local xCursor, yCursor = 1, 1 local bgColor, fgColor = colors.black, colors.white local cursorBlink = false local visible = false local cacheT = {} local cacheBG = {} local cacheFG = {} local tPalette = {} local emptySpaceLine local emptyColorLines = {} for i = 0, 15 do local c = 2 ^ i tPalette[c] = { basalt.getTerm().getPaletteColour(c) } end local function createEmptyLines() emptySpaceLine = (" "):rep(width) for n = 0, 15 do local nColor = 2 ^ n local sHex = tHex[nColor] emptyColorLines[nColor] = sHex:rep(width) end end local function recreateWindowArray() createEmptyLines() local emptyText = emptySpaceLine local emptyFG = emptyColorLines[colors.white] local emptyBG = emptyColorLines[colors.black] for n = 1, height do cacheT[n] = sub(cacheT[n] == nil and emptyText or cacheT[n] .. emptyText:sub(1, width - cacheT[n]:len()), 1, width) cacheFG[n] = sub(cacheFG[n] == nil and emptyFG or cacheFG[n] .. emptyFG:sub(1, width - cacheFG[n]:len()), 1, width) cacheBG[n] = sub(cacheBG[n] == nil and emptyBG or cacheBG[n] .. emptyBG:sub(1, width - cacheBG[n]:len()), 1, width) end base.updateDraw(base) end recreateWindowArray() local function updateCursor() if xCursor >= 1 and yCursor >= 1 and xCursor <= width and yCursor <= height then --parentTerminal.setCursorPos(xCursor + x - 1, yCursor + y - 1) else --parentTerminal.setCursorPos(0, 0) end --parentTerminal.setTextColor(fgColor) end local function internalBlit(sText, sTextColor, sBackgroundColor) if yCursor < 1 or yCursor > height or xCursor < 1 or xCursor + #sText - 1 > width then return end cacheT[yCursor] = sub(cacheT[yCursor], 1, xCursor - 1) .. sText .. sub(cacheT[yCursor], xCursor + #sText, width) cacheFG[yCursor] = sub(cacheFG[yCursor], 1, xCursor - 1) .. sTextColor .. sub(cacheFG[yCursor], xCursor + #sText, width) cacheBG[yCursor] = sub(cacheBG[yCursor], 1, xCursor - 1) .. sBackgroundColor .. sub(cacheBG[yCursor], xCursor + #sText, width) xCursor = xCursor + #sText if visible then updateCursor() end object:updateDraw() end local function setText(_x, _y, text) if (text ~= nil) then local gText = cacheT[_y] if (gText ~= nil) then cacheT[_y] = sub(gText:sub(1, _x - 1) .. text .. gText:sub(_x + (text:len()), width), 1, width) end end object:updateDraw() end local function setBG(_x, _y, colorStr) if (colorStr ~= nil) then local gBG = cacheBG[_y] if (gBG ~= nil) then cacheBG[_y] = sub(gBG:sub(1, _x - 1) .. colorStr .. gBG:sub(_x + (colorStr:len()), width), 1, width) end end object:updateDraw() end local function setFG(_x, _y, colorStr) if (colorStr ~= nil) then local gFG = cacheFG[_y] if (gFG ~= nil) then cacheFG[_y] = sub(gFG:sub(1, _x - 1) .. colorStr .. gFG:sub(_x + (colorStr:len()), width), 1, width) end end object:updateDraw() end local setTextColor = function(color) if type(color) ~= "number" then error("bad argument #1 (expected number, got " .. type(color) .. ")", 2) elseif tHex[color] == nil then error("Invalid color (got " .. color .. ")", 2) end fgColor = color end local setBackgroundColor = function(color) if type(color) ~= "number" then error("bad argument #1 (expected number, got " .. type(color) .. ")", 2) elseif tHex[color] == nil then error("Invalid color (got " .. color .. ")", 2) end bgColor = color end local setPaletteColor = function(colour, r, g, b) -- have to work on if type(colour) ~= "number" then error("bad argument #1 (expected number, got " .. type(colour) .. ")", 2) end if tHex[colour] == nil then error("Invalid color (got " .. colour .. ")", 2) end local tCol if type(r) == "number" and g == nil and b == nil then tCol = { colours.rgb8(r) } tPalette[colour] = tCol else if type(r) ~= "number" then error("bad argument #2 (expected number, got " .. type(r) .. ")", 2) end if type(g) ~= "number" then error("bad argument #3 (expected number, got " .. type(g) .. ")", 2) end if type(b) ~= "number" then error("bad argument #4 (expected number, got " .. type(b) .. ")", 2) end tCol = tPalette[colour] tCol[1] = r tCol[2] = g tCol[3] = b end end local getPaletteColor = function(colour) if type(colour) ~= "number" then error("bad argument #1 (expected number, got " .. type(colour) .. ")", 2) end if tHex[colour] == nil then error("Invalid color (got " .. colour .. ")", 2) end local tCol = tPalette[colour] return tCol[1], tCol[2], tCol[3] end local basaltwindow = { setCursorPos = function(_x, _y) if type(_x) ~= "number" then error("bad argument #1 (expected number, got " .. type(_x) .. ")", 2) end if type(_y) ~= "number" then error("bad argument #2 (expected number, got " .. type(_y) .. ")", 2) end xCursor = math.floor(_x) yCursor = math.floor(_y) if (visible) then updateCursor() end end, getCursorPos = function() return xCursor, yCursor end; setCursorBlink = function(blink) if type(blink) ~= "boolean" then error("bad argument #1 (expected boolean, got " .. type(blink) .. ")", 2) end cursorBlink = blink end; getCursorBlink = function() return cursorBlink end; getPaletteColor = getPaletteColor, getPaletteColour = getPaletteColor, setBackgroundColor = setBackgroundColor, setBackgroundColour = setBackgroundColor, setTextColor = setTextColor, setTextColour = setTextColor, setPaletteColor = setPaletteColor, setPaletteColour = setPaletteColor, getBackgroundColor = function() return bgColor end; getBackgroundColour = function() return bgColor end; getSize = function() return width, height end; getTextColor = function() return fgColor end; getTextColour = function() return fgColor end; basalt_resize = function(_width, _height) width, height = _width, _height recreateWindowArray() end; basalt_reposition = function(_x, _y) x, y = _x, _y end; basalt_setVisible = function(vis) visible = vis end; drawBackgroundBox = function(_x, _y, _width, _height, bgCol) for n = 1, _height do setBG(_x, _y + (n - 1), tHex[bgCol]:rep(_width)) end end; drawForegroundBox = function(_x, _y, _width, _height, fgCol) for n = 1, _height do setFG(_x, _y + (n - 1), tHex[fgCol]:rep(_width)) end end; drawTextBox = function(_x, _y, _width, _height, symbol) for n = 1, _height do setText(_x, _y + (n - 1), symbol:rep(_width)) end end; basalt_update = function() for n = 1, height do object:addBlit(1, n, cacheT[n], cacheFG[n], cacheBG[n]) end end, scroll = function(offset) assert(type(offset) == "number", "bad argument #1 (expected number, got " .. type(offset) .. ")") if offset ~= 0 then for newY = 1, height do local y = newY + offset if y < 1 or y > height then cacheT[newY] = emptySpaceLine cacheFG[newY] = emptyColorLines[fgColor] cacheBG[newY] = emptyColorLines[bgColor] else cacheT[newY] = cacheT[y] cacheBG[newY] = cacheBG[y] cacheFG[newY] = cacheFG[y] end end end if (visible) then updateCursor() end end, isColor = function() return basalt.getTerm().isColor() end; isColour = function() return basalt.getTerm().isColor() end; write = function(text) text = tostring(text) if (visible) then internalBlit(text, tHex[fgColor]:rep(text:len()), tHex[bgColor]:rep(text:len())) end end; clearLine = function() if (visible) then setText(1, yCursor, (" "):rep(width)) setBG(1, yCursor, tHex[bgColor]:rep(width)) setFG(1, yCursor, tHex[fgColor]:rep(width)) end if (visible) then updateCursor() end end; clear = function() for n = 1, height do setText(1, n, (" "):rep(width)) setBG(1, n, tHex[bgColor]:rep(width)) setFG(1, n, tHex[fgColor]:rep(width)) end if (visible) then updateCursor() end end; blit = function(text, fgcol, bgcol) if type(text) ~= "string" then error("bad argument #1 (expected string, got " .. type(text) .. ")", 2) end if type(fgcol) ~= "string" then error("bad argument #2 (expected string, got " .. type(fgcol) .. ")", 2) end if type(bgcol) ~= "string" then error("bad argument #3 (expected string, got " .. type(bgcol) .. ")", 2) end if #fgcol ~= #text or #bgcol ~= #text then error("Arguments must be the same length", 2) end if (visible) then internalBlit(text, fgcol, bgcol) end end } return basaltwindow end base:setZIndex(5) base:setSize(30, 12) local pWindow = createBasaltWindow(1, 1, 30, 12) local curProcess local paused = false local queuedEvent = {} local function updateCursor(self) local parent = self:getParent() local xCur, yCur = pWindow.getCursorPos() local obx, oby = self:getPosition() local w,h = self:getSize() if (obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1) then parent:setCursor(self:isFocused() and pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) end end local function resumeProcess(self, event, ...) local ok, result = curProcess:resume(event, ...) if (ok==false)and(result~=nil)and(result~="Terminated")then local val = self:sendEvent("program_error", result) if(val~=false)then error("Basalt Program - "..result) end end if(curProcess:getStatus()=="dead")then self:sendEvent("program_done") end end local function mouseEvent(self, event, p1, x, y) if (curProcess == nil) then return false end if not (curProcess:isDead()) then if not (paused) then local absX, absY = self:getAbsolutePosition() resumeProcess(self, event, p1, x-absX+1, y-absY+1) updateCursor(self) end end end local function keyEvent(self, event, key, isHolding) if (curProcess == nil) then return false end if not (curProcess:isDead()) then if not (paused) then if (self.draw) then resumeProcess(self, event, key, isHolding) updateCursor(self) end end end end object = { getType = function(self) return objectType end; show = function(self) base.show(self) pWindow.setBackgroundColor(self:getBackground()) pWindow.setTextColor(self:getForeground()) pWindow.basalt_setVisible(true) return self end; hide = function(self) base.hide(self) pWindow.basalt_setVisible(false) return self end; setPosition = function(self, x, y, rel) base.setPosition(self, x, y, rel) pWindow.basalt_reposition(self:getPosition()) return self end, getBasaltWindow = function() return pWindow end; getBasaltProcess = function() return curProcess end; setSize = function(self, width, height, rel) base.setSize(self, width, height, rel) pWindow.basalt_resize(self:getWidth(), self:getHeight()) return self end; getStatus = function(self) if (curProcess ~= nil) then return curProcess:getStatus() end return "inactive" end; setEnviroment = function(self, env) enviroment = env or {} return self end, execute = function(self, path, ...) cachedPath = path or cachedPath curProcess = process:new(cachedPath, pWindow, enviroment, ...) pWindow.setBackgroundColor(colors.black) pWindow.setTextColor(colors.white) pWindow.clear() pWindow.setCursorPos(1, 1) pWindow.setBackgroundColor(self:getBackground()) pWindow.setTextColor(self:getForeground() or colors.white) pWindow.basalt_setVisible(true) resumeProcess(self) paused = false self:listenEvent("mouse_click", self) self:listenEvent("mouse_up", self) self:listenEvent("mouse_drag", self) self:listenEvent("mouse_scroll", self) self:listenEvent("key", self) self:listenEvent("key_up", self) self:listenEvent("char", self) self:listenEvent("other_event", self) return self end; setExecute = function(self, path, ...) return self:execute(path, ...) end, stop = function(self) local parent = self:getParent() if (curProcess ~= nil) then if not (curProcess:isDead()) then resumeProcess(self, "terminate") if (curProcess:isDead()) then parent:setCursor(false) end end end parent:removeEvents(self) return self end; pause = function(self, p) paused = p or (not paused) if (curProcess ~= nil) then if not (curProcess:isDead()) then if not (paused) then self:injectEvents(table.unpack(queuedEvent)) queuedEvent = {} end end end return self end; isPaused = function(self) return paused end; injectEvent = function(self, event, ign, ...) if (curProcess ~= nil) then if not (curProcess:isDead()) then if (paused == false) or (ign) then resumeProcess(self, event, ...) else table.insert(queuedEvent, { event = event, args = {...} }) end end end return self end; getQueuedEvents = function(self) return queuedEvent end; updateQueuedEvents = function(self, events) queuedEvent = events or queuedEvent return self end; injectEvents = function(self, ...) if (curProcess ~= nil) then if not (curProcess:isDead()) then for _, value in pairs({...}) do resumeProcess(self, value.event, table.unpack(value.args)) end end end return self end; mouseHandler = function(self, button, x, y) if (base.mouseHandler(self, button, x, y)) then mouseEvent(self, "mouse_click", button, x, y) return true end return false end, mouseUpHandler = function(self, button, x, y) if (base.mouseUpHandler(self, button, x, y)) then mouseEvent(self, "mouse_up", button, x, y) return true end return false end, scrollHandler = function(self, dir, x, y) if (base.scrollHandler(self, dir, x, y)) then mouseEvent(self, "mouse_scroll", dir, x, y) return true end return false end, dragHandler = function(self, button, x, y) if (base.dragHandler(self, button, x, y)) then mouseEvent(self, "mouse_drag", button, x, y) return true end return false end, keyHandler = function(self, key, isHolding) if(base.keyHandler(self, key, isHolding))then keyEvent(self, "key", key, isHolding) return true end return false end, keyUpHandler = function(self, key) if(base.keyUpHandler(self, key))then keyEvent(self, "key_up", key) return true end return false end, charHandler = function(self, char) if(base.charHandler(self, char))then keyEvent(self, "char", char) return true end return false end, getFocusHandler = function(self) base.getFocusHandler(self) if (curProcess ~= nil) then if not (curProcess:isDead()) then if not (paused) then local parent = self:getParent() if (parent ~= nil) then local xCur, yCur = pWindow.getCursorPos() local obx, oby = self:getPosition() local w,h = self:getSize() if (obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1) then parent:setCursor(pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) end end end end end end, loseFocusHandler = function(self) base.loseFocusHandler(self) if (curProcess ~= nil) then if not (curProcess:isDead()) then local parent = self:getParent() if (parent ~= nil) then parent:setCursor(false) end end end end, eventHandler = function(self, event, ...) base.eventHandler(self, event, ...) if curProcess == nil then return end if not curProcess:isDead() then if not paused then resumeProcess(self, event, ...) if self:isFocused() then local parent = self:getParent() local obx, oby = self:getPosition() local xCur, yCur = pWindow.getCursorPos() local w,h = self:getSize() if obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1 then parent:setCursor(pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) end end else table.insert(queuedEvent, { event = event, args = { ... } }) end end end, resizeHandler = function(self, ...) base.resizeHandler(self, ...) if (curProcess ~= nil) then if not (curProcess:isDead()) then if not (paused) then pWindow.basalt_resize(self:getSize()) resumeProcess(self, "term_resize", self:getSize()) else pWindow.basalt_resize(self:getSize()) table.insert(queuedEvent, { event = "term_resize", args = { self:getSize() } }) end end end end, repositionHandler = function(self, ...) base.repositionHandler(self, ...) if (curProcess ~= nil) then if not (curProcess:isDead()) then pWindow.basalt_reposition(self:getPosition()) end end end, draw = function(self) base.draw(self) self:addDraw("program", function() local parent = self:getParent() local obx, oby = self:getPosition() local xCur, yCur = pWindow.getCursorPos() local w,h = self:getSize() pWindow.basalt_update() end) end, onError = function(self, ...) for _,v in pairs(table.pack(...))do if(type(v)=="function")then self:registerEvent("program_error", v) end end local parent = self:getParent() self:listenEvent("other_event") return self end, onDone = function(self, ...) for _,v in pairs(table.pack(...))do if(type(v)=="function")then self:registerEvent("program_done", v) end end local parent = self:getParent() self:listenEvent("other_event") return self end, } object.__index = object return setmetatable(object, base) end