From ba251ebbf4bc39d499670b2d94ad2a25379dab40 Mon Sep 17 00:00:00 2001 From: LDDestroier Date: Wed, 26 Feb 2020 18:34:53 -0500 Subject: [PATCH] Fixed a major oopsie --- windont/windont.lua | 1079 +++++++++++++++++++++++++------------------ 1 file changed, 626 insertions(+), 453 deletions(-) diff --git a/windont/windont.lua b/windont/windont.lua index 3cef11b..bf4bc91 100644 --- a/windont/windont.lua +++ b/windont/windont.lua @@ -1,485 +1,658 @@ -if not fs.exists("windont.lua") then - print("'windont.lua' not found! Downloading...") - local net = http.get("https://github.com/LDDestroier/CC/raw/master/windont/windont.lua") - if net then - local file = fs.open("windont.lua", "w") - file.write(net.readAll()) - file.close() - net.close() +-- Windon't +-- enhanced window API by LDDestroier +-- intended for general use within all me new programs +-- +-- Unique features: +-- + Transparency within windows +-- + Built-in window layering + +-- stores each base terminal's framebuffers to optimize rendering +local oldScreenBuffer = {} + +local table_insert = table.insert +local table_concat = table.concat +local math_floor = math.floor +local to_blit = {} +local to_colors = {} + +local table_compare = function(tbl1, tbl2) + if type(tbl1) ~= "table" or type(tbl2) ~= "table" then + return tbl1 == tbl2 else - error("Could not download Windon't.", 0) + for k,v in pairs(tbl1) do + if tbl1[k] ~= tbl2[k] then + return false + end + end + for k,v in pairs(tbl2) do + if tbl1[k] ~= tbl2[k] then + return false + end + end + return true end end -local windont = require "windont" -windont.default.alwaysRender = false +local getTime = function() + return 24 * os.day() + os.time() +end -local scr_x, scr_y = term.getSize() -local keysDown = {} +for i = 1, 16 do + to_blit[2 ^ (i - 1)] = ("0123456789abcdef"):sub(i, i) + to_colors[("0123456789abcdef"):sub(i, i)] = 2 ^ (i - 1) +end +to_blit[0], to_colors["-"] = "-", 0 -local instances = {} -local knownNames = { - ["rom/programs/shell.lua"] = "CraftOS Shell", - ["rom/programs/edit.lua"] = "Edit", - ["rom/programs/gps.lua"] = "GPS", - ["rom/programs/shutdown.lua"] = "Shutdown", - ["rom/programs/monitor.lua"] = "Monitor Redirect", - ["rom/programs/emu.lua"] = "Emu (CCEmuX)", - ["rom/programs/exit.lua"] = "Goodbye!", - ["rom/programs/lua.lua"] = "Lua Interpreter", - - ["rom/programs/fun/adventure.lua"] = "Adventure", - ["rom/programs/fun/worm.lua"] = "Worm", - ["rom/programs/fun/dj.lua"] = "DJ", - ["rom/programs/fun/hello.lua"] = "Hello world!", - - ["rom/programs/turtle/dance.lua"] = "Dance!", - ["rom/programs/turtle/craft.lua"] = "Craft", - ["rom/programs/turtle/excavate.lua"] = "Excavate", - ["rom/programs/turtle/refuel.lua"] = "Refueling...", - ["rom/programs/turtle/go.lua"] = "Go (Turtle)", - ["rom/programs/turtle/turn.lua"] = "Turn (Turtle)", - - ["rom/programs/http/pastebin.lua"] = "Pastebin", - ["rom/programs/http/wget.lua"] = "Wget", - - ["rom/programs/pocket/falling.lua"] = "Falling", - - ["rom/programs/rednet/chat.lua"] = "Chat", - ["rom/programs/rednet/repeat.lua"] = "Rednet Repeat", - - ["rom/programs/command/exec.lua"] = "Exec (Command)", - ["rom/programs/command/commands.lua"] = "Commands", +local nativePalette = { -- native palette colors, since some terminals are naughty and don't contain term.nativePaletteColor() + [ 1 ] = { + 0.94117647409439, + 0.94117647409439, + 0.94117647409439, + }, + [ 2 ] = { + 0.94901961088181, + 0.69803923368454, + 0.20000000298023, + }, + [ 4 ] = { + 0.89803922176361, + 0.49803921580315, + 0.84705883264542, + }, + [ 8 ] = { + 0.60000002384186, + 0.69803923368454, + 0.94901961088181, + }, + [ 16 ] = { + 0.87058824300766, + 0.87058824300766, + 0.42352941632271, + }, + [ 32 ] = { + 0.49803921580315, + 0.80000001192093, + 0.098039217293262, + }, + [ 64 ] = { + 0.94901961088181, + 0.69803923368454, + 0.80000001192093, + }, + [ 128 ] = { + 0.29803922772408, + 0.29803922772408, + 0.29803922772408, + }, + [ 256 ] = { + 0.60000002384186, + 0.60000002384186, + 0.60000002384186, + }, + [ 512 ] = { + 0.29803922772408, + 0.60000002384186, + 0.69803923368454, + }, + [ 1024 ] = { + 0.69803923368454, + 0.40000000596046, + 0.89803922176361, + }, + [ 2048 ] = { + 0.20000000298023, + 0.40000000596046, + 0.80000001192093, + }, + [ 4096 ] = { + 0.49803921580315, + 0.40000000596046, + 0.29803922772408, + }, + [ 8192 ] = { + 0.34117648005486, + 0.65098041296005, + 0.30588236451149, + }, + [ 16384 ] = { + 0.80000001192093, + 0.29803922772408, + 0.29803922772408, + }, + [ 32768 ] = { + 0.066666670143604, + 0.066666670143604, + 0.066666670143604, + } } --- events that can only be passed if the instance is focused -local FocusEvents = { - ["mouse_click"] = true, - ["mouse_drag"] = true, - ["mouse_up"] = true, - ["key"] = true, - ["key_up"] = true, - ["char"] = true, - ["monitor_touch"] = true, - ["paste"] = true, - ["terminate"] = true - -- mouse_scroll is intentionally excluded -} - --- events that must have their XY values altered according to the position of the window -local CoordinateEvents = { - ["mouse_click"] = {3, 4}, - ["mouse_drag"] = {3, 4}, - ["mouse_up"] = {3, 4}, - ["mouse_scroll"] = {3, 4} -} - -local desktop = windont.newWindow(1, 1, scr_x, scr_y, { - backColor = "9" -}) -local overlay = windont.newWindow(1, 1, scr_x, scr_y, { - backColor = "-" -}) -desktop.redraw() - -local instanceBoxCheck = function(i, x, y, useMain) - assert(type(x) == "number", "x must be number") - assert(type(y) == "number", "y must be number") - return ( - x < 1 or x > (useMain and instances[i].mainWindow.meta.width or instances[i].termWindow.meta.width) or - y < 1 or y > (useMain and instances[i].mainWindow.meta.height or instances[i].termWindow.meta.height) - ) -end - -local focusInstance = function(i) - if instances[i or false] then - if i ~= 1 then - local instance = instances[i] - instances[1].focused = false - table.remove(instances, i) - table.insert(instances, 1, instance) - instances[1].focused = true - end - end -end - --- checks if (x, y) is within a rectangle between (rx1, ry1), (rx2, ry2) -local rectangleCheck = function(x, y, rx1, ry1, rx2, ry2) - return - (x >= rx1 and x <= rx2) and - (y >= ry1 and y <= ry2) -end - --- checks which instance should be selected if you were to click on (x, y) -local checkInstanceByPos = function(x, y, useTerm) - for i = 1, #instances do - if rectangleCheck( - x, - y, - instances[i].mainWindow.meta.x + (useTerm and 1 or 0), - instances[i].mainWindow.meta.y + (useTerm and 1 or 0), - instances[i].mainWindow.meta.x + (useTerm and 1 or 0) + (useTerm and instances[i].termWindow or instances[i].mainWindow).meta.width - 1, - instances[i].mainWindow.meta.y + (useTerm and 1 or 0) + (useTerm and instances[i].termWindow or instances[i].mainWindow).meta.height - 1 - ) then - return i - end - end - return false -end - -local resumeInstance = function(i, _evt, isCoordinateEvent) - local evt = {} - for k,v in pairs(_evt) do - evt[k] = v - end - if isCoordinateEvent then - evt[3] = evt[3] - instances[i].mainWindow.meta.x - evt[4] = evt[4] - instances[i].mainWindow.meta.y - end - repeat - if (isCoordinateEvent and instanceBoxCheck(i, evt[3], evt[4])) then - break - end - oldTerm = term.redirect(instances[i].termWindow) - success, result = coroutine.resume(instances[i].coroutine, table.unpack(evt)) - term.redirect(oldTerm) - if success and coroutine.status(instances[i].coroutine) ~= "dead" then - instances[i].cFilter = result +-- check if space on screenBuffer is transparent +local checkTransparent = function(buffer, x, y, blitLayer) + if buffer[blitLayer or 1][y] then + if blitLayer then + return (buffer[blitLayer][y][x] and buffer[blitLayer][y][x] ~= "-") else - instances[i].alive = false - end - until true -end - -local newInstance = function(x, y, width, height, program, pName, addBorder) - local output = {} - if addBorder then - output.mainWindow = windont.newWindow(x, y, width, height + 1, { - baseTerm = desktop, - alwaysRender = false, - backColor = "7" - }) - output.termWindow = windont.newWindow(1, 2, width, height, { - baseTerm = output.mainWindow, - alwaysRender = false, - }) - output.oldTermPos = {1, 2, width, height} - output.oldMainPos = {x, y, width, height + 1} - else - output.mainWindow = windont.newWindow(x, y, width + 2, height + 2, { - baseTerm = desktop, - alwaysRender = false, - backColor = "7" - }) - output.termWindow = windont.newWindow(2, 2, width, height, { - baseTerm = output.mainWindow, - alwaysRender = false, - }) - output.oldTermPos = {2, 2, width, height} - output.oldMainPos = {x, y, width + 2, height + 2} - end - - local mw = output.mainWindow -- contains the titlebar, and is the base terminal for termWindow - local tw = output.termWindow -- contains the program's terminal output - - tw.meta.transformation = function(x, y, char, text, back, meta) - if x > mw.meta.width - 2 then - return {x, y, " "}, {x, y, "-"}, {x, y, "-"} - else - return {x, y, char}, {x, y, text}, {x, y, back} - end - end - - output.refreshMainWindow = function() - for y = 1, mw.meta.height do - mw.setCursorPos(1, y) - if y == 1 or y == mw.meta.height then - mw.blit( - (" "):rep(mw.meta.width), - ("7"):rep(mw.meta.width), - ("7"):rep(mw.meta.width) - ) + if (not buffer[2][y][x] or buffer[2][y][x] == "-") and (not buffer[3][y][x] or buffer[3][y][x] == "-") then + return false + elseif (not buffer[3][y][x] or buffer[3][y][x] == "-") and (not buffer[1][y][x] or buffer[1][y][x] == " ") then + return false else - mw.blit(" ","7","7") - mw.setCursorPos(mw.meta.width, y) - mw.blit(" ","7","7") + return buffer[1][y][x] and buffer[2][y][x] and buffer[3][y][x] end end end - output.refreshMainWindow() +end - -- pausing will probably be implemented later - output.paused = false - output.timeMod = 0 - output.clockMod = 0 - output.timers = {} +local expect = function(value, default, valueType) + if value == nil or (valueType and type(value) ~= valueType) then + return default + else + return value + end +end - output.alive = true - output.focused = true - output.manipMode = 0 - output.title = pName or (type(program) == "string" and (knownNames[fs.combine("", program)] or fs.getName(program))) or tostring(program) - output.writeTitleBar = function() - mw.setCursorPos(1, 1) - if output.focused then - mw.setTextColor(colors.white) +local windont = { + doClearScreen = false, -- if true, will clear the screen during render + ignoreUnchangedLines = true, -- if true, the render function will check each line it renders against the last framebuffer and ignore it if they are the same + useSetVisible = false, -- if true, sets the base terminal's visibility to false before rendering + sameCharWillStencil = false, -- if true, if one window is layered atop another and both windows have a spot where the character is the same, and the top window's text color is transparent, it will use the TEXT color of the lower window instead of the BACKGROUND color + default = { + baseTerm = term.current(), -- default base terminal for all windows + textColor = "0", -- default text color (what " " corresponds to in term.blit's second argument) + backColor = "f", -- default background color (what " " corresponds to in term.blit's third argument) + blink = true, + visible = true, + alwaysRender = true, -- if true, new windows will always render if they are written to + }, + info = { + BLIT_CALLS = 0, -- amount of term.blit calls during the last render + LAST_RENDER_TIME = 0, -- last time in which render was called + LAST_RENDER_AMOUNT = 0, -- amount of windows drawn during last render + LAST_RENDER_WINDOWS = {}, -- table of the last window objects that were rendered + } +} + +-- draws one or more windon't objects +-- should not draw over any terminal space that isn't occupied by a window + +windont.render = function(options, ...) +-- potential options: +-- number: onlyX1 +-- number: onlyX2 +-- number: onlyY +-- boolean: force (forces render / ignores optimiztaion that compares current framebuffer to old one) +-- terminal: baseTerm (forces to render onto this terminal instead of the window's base terminal) + + local windows = {...} + options = options or {} + local bT, scr_x, scr_y + + -- checks if "options" is actually the first window, just in case + if type(options.meta) == "table" then + if ( + type(options.meta.buffer) == "table" and + type(options.meta.x) == "number" and + type(options.meta.y) == "number" and + type(options.meta.newBuffer) == "function" + ) then + table_insert(windows, 1, options) + end + end + + local screenBuffer = {{}, {}, {}} + local blitList = {} -- list of blit commands per line + local c = 1 -- current blitList entry + + local cTime = getTime() + + local AMNT_OF_BLITS = 0 -- how many blit calls are there? + + local cx, cy -- each window's absolute X and Y + local char_cx, text_cx, back_cx -- each window's transformed absolute X's in table form + local char_cy, text_cy, back_cy -- each window's transformed absolute X's in table form + local buffer -- each window's buffer + local newChar, newText, newBack -- if the transformation function declares a new dot, this is it + local oriChar, oriText, oriBack + local char_out, text_out, back_out -- three tables, directly returned from the transformation functions + + local baseTerms = {} + if type(options.baseTerm) == "table" then + for i = 1, #windows do + baseTerms[options.baseTerm] = baseTerms[options.baseTerm] or {} + baseTerms[options.baseTerm][i] = true + end + else + for i = 1, #windows do + baseTerms[windows[i].meta.baseTerm] = baseTerms[windows[i].meta.baseTerm] or {} + baseTerms[windows[i].meta.baseTerm][i] = true + end + end + + for bT, bT_list in pairs(baseTerms) do + if bT == output then + bT = options.baseTerm or output.meta.baseTerm + end + if windont.useSetVisible and bT.setVisible then + bT.setVisible(false) + end + scr_x, scr_y = bT.getSize() + -- try entire buffer transformations + for i = #windows, 1, -1 do + if bT_list[i] then + if windows[i].meta.metaTransformation then + -- metaTransformation functions needn't return a value + windows[i].meta.metaTransformation(windows[i].meta) + end + end + end + for y = options.onlyY or 1, options.onlyY or scr_y do + screenBuffer[1][y] = {} + screenBuffer[2][y] = {} + screenBuffer[3][y] = {} + blitList = {} + c = 1 + for x = options.onlyX1 or 1, math.min(scr_x, options.onlyX2 or scr_x) do + for i = #windows, 1, -1 do + if bT_list[i] then + newChar, newText, newBack = nil + if windows[i].meta.visible then + buffer = windows[i].meta.buffer + + cx = 1 + x + -windows[i].meta.x + cy = 1 + y + -windows[i].meta.y + char_cx, text_cx, back_cx = cx, cx, cx + char_cy, text_cy, back_cy = cy, cy, cy + + oriChar = (buffer[1][cy] or {})[cx] + oriText = (buffer[2][cy] or {})[cx] + oriBack = (buffer[3][cy] or {})[cx] + + -- try transformation + if windows[i].meta.transformation then + char_out, text_out, back_out = windows[i].meta.transformation(cx, cy, oriChar, oriText, oriBack, windows[i].meta) + + if char_out then + char_cx = math_floor(char_out[1] or cx) + char_cy = math_floor(char_out[2] or cy) + if (char_out[1] % 1 ~= 0) or (char_out[2] % 1 ~= 0) then + newChar = " " + else + newChar = char_out[3] + end + end + + if text_out then + text_cx = math_floor(text_out[1] or cx) + text_cy = math_floor(text_out[2] or cy) + newText = text_out[3] + end + + if back_out then + back_cx = math_floor(back_out[1] or cx) + back_cy = math_floor(back_out[2] or cy) + newBack = back_out[3] + end + end + + if checkTransparent(buffer, char_cx, char_cy) or checkTransparent(buffer, text_cx, text_cy) or checkTransparent(buffer, back_cx, back_cy) then + + screenBuffer[2][y][x] = newText or checkTransparent(buffer, text_cx, text_cy, 2) and (buffer[2][text_cy][text_cx]) or ( + (buffer[1][text_cy][text_cx] == screenBuffer[1][y][x]) and (windont.sameCharWillStencil) and + screenBuffer[2][y][x] + or + screenBuffer[3][y][x] + ) + screenBuffer[1][y][x] = newChar or checkTransparent(buffer, char_cx, char_cy ) and (buffer[1][char_cy][char_cx]) or screenBuffer[1][y][x] + screenBuffer[3][y][x] = newBack or checkTransparent(buffer, back_cx, back_cy, 3) and (buffer[3][back_cy][back_cx]) or screenBuffer[3][y][x] + end + end + end + end + + if windont.doClearScreen then + screenBuffer[1][y][x] = screenBuffer[1][y][x] or " " + end + screenBuffer[2][y][x] = screenBuffer[2][y][x] or windont.default.backColor -- intentionally not the default text color + screenBuffer[3][y][x] = screenBuffer[3][y][x] or windont.default.backColor + + if checkTransparent(screenBuffer, x, y) then + if checkTransparent(screenBuffer, -1 + x, y) then + blitList[c][1] = blitList[c][1] .. screenBuffer[1][y][x] + blitList[c][2] = blitList[c][2] .. screenBuffer[2][y][x] + blitList[c][3] = blitList[c][3] .. screenBuffer[3][y][x] + else + c = x + blitList[c] = { + screenBuffer[1][y][x], + screenBuffer[2][y][x], + screenBuffer[3][y][x] + } + end + end + end + if (not oldScreenBuffer[bT]) or (not windont.ignoreUnchangedLines) or (options.force) or ( + (not table_compare(screenBuffer[1][y], oldScreenBuffer[bT][1][y])) or + (not table_compare(screenBuffer[2][y], oldScreenBuffer[bT][2][y])) or + (not table_compare(screenBuffer[3][y], oldScreenBuffer[bT][3][y])) + ) then + for k,v in pairs(blitList) do + bT.setCursorPos(k, y) + bT.blit(v[1], v[2], v[3]) + AMNT_OF_BLITS = 1 + AMNT_OF_BLITS + end + end + end + oldScreenBuffer[bT] = screenBuffer + if windont.useSetVisible and bT.setVisible then + if not multishell then + bT.setVisible(true) + elseif multishell.getFocus() == multishell.getCurrent() then + bT.setVisible(true) + end + end + end + + windont.info.LAST_RENDER_AMOUNT = #windows + windont.info.BLIT_CALLS = AMNT_OF_BLITS + windont.info.LAST_RENDER_WINDOWS = windows + windont.info.LAST_RENDER_TIME = cTime + windont.info.LAST_RENDER_DURATION = getTime() + -cTime + +end + +-- creates a new windon't object that can be manipulated the same as a regular window + +windont.newWindow = function( x, y, width, height, misc ) + + -- check argument types + assert(type(x) == "number", "argument #1 must be number, got " .. type(x)) + assert(type(y) == "number", "argument #2 must be number, got " .. type(y)) + assert(type(width) == "number", "argument #3 must be number, got " .. type(width)) + assert(type(height) == "number", "argument #4 must be number, got " .. type(height)) + + -- check argument validity + assert(x > 0, "x position must be above zero") + assert(y > 0, "y position must be above zero") + assert(width > 0, "width must be above zero") + assert(height > 0, "height must be above zero") + + local output = {} + misc = misc or {} + local meta = { + x = expect(x, 1), -- x position of the window + y = expect(y, 1), -- y position of the window + width = width, -- width of the buffer + height = height, -- height of the buffer + buffer = expect(misc.buffer, {}, "table"), -- stores contents of terminal in buffer[1][y][x] format + renderBuddies = expect(misc.renderBuddies, {}, "table"), -- renders any other window objects stored here after rendering here + baseTerm = expect(misc.baseTerm, windont.default.baseTerm, "table"), -- base terminal for which this window draws on + isColor = expect(misc.isColor, term.isColor(), "boolean"), -- if true, then it's an advanced computer + + transformation = expect(misc.transformation, nil, "function"), -- function that transforms the char/text/back dots of the window + metaTransformation = expect(misc.miscTransformation, nil, "function"), -- function that transforms the whole output.meta function + + cursorX = expect(misc.cursorX, 1), + cursorY = expect(misc.cursorY, 1), + + textColor = expect(misc.textColor, windont.default.textColor, "string"), -- current text color + backColor = expect(misc.backColor, windont.default.backColor, "string"), -- current background color + + blink = expect(misc.blink, windont.default.blink, "boolean"), -- cursor blink + alwaysRender = expect(misc.alwaysRender, windont.default.alwaysRender, "boolean"), -- render after every terminal operation + visible = expect(misc.visible, windont.default.visible, "boolean"), -- if false, don't render ever + + -- make a new buffer (optionally uses an existing buffer as a reference) + newBuffer = function(width, height, char, text, back, drawAtop) + local output = {{}, {}, {}} + drawAtop = drawAtop or {{}, {}, {}} + for y = 1, height do + output[1][y] = output[1][y] or {} + output[2][y] = output[2][y] or {} + output[3][y] = output[3][y] or {} + for x = 1, width do + output[1][y][x] = (drawAtop[1][y] or {})[x] or (output[1][y][x] or (char or " ")) + output[2][y][x] = (drawAtop[2][y] or {})[x] or (output[2][y][x] or (text or "0")) + output[3][y][x] = (drawAtop[3][y] or {})[x] or (output[3][y][x] or (back or "f")) + end + end + return output + end + } + + bT = meta.baseTerm + + -- initialize the buffer + meta.buffer = meta.newBuffer(meta.width, meta.height, " ", meta.textColor, meta.backColor) + + output.meta = meta + + output.write = function(text) + assert(type(text) == "string" or type(text) == "number", "expected string, got " .. type(text)) + local initX = meta.cursorX + for i = 1, #tostring(text) do + if meta.cursorX >= 1 and meta.cursorX <= meta.width and meta.cursorY >= 1 and meta.cursorY <= meta.height then + if not meta.buffer[1] then + error("what the fuck happened") + end + meta.buffer[1][meta.cursorY][meta.cursorX] = tostring(text):sub(i,i) + meta.buffer[2][meta.cursorY][meta.cursorX] = meta.textColor + meta.buffer[3][meta.cursorY][meta.cursorX] = meta.backColor + end + meta.cursorX = meta.cursorX + 1 + end + if meta.alwaysRender then + output.redraw( + -1 + meta.x + initX, + -1 + meta.x + meta.cursorX, + -1 + meta.y + meta.cursorY + ) + end + end + + output.blit = function(char, text, back) + assert(type(char) == "string" and type(text) == "string" and type(back) == "string", "all arguments must be strings") + assert(#char == #text and #text == #back, "arguments must be same length") + local initX = meta.cursorX + for i = 1, #char do + if meta.cursorX >= 1 and meta.cursorX <= meta.width and meta.cursorY >= 1 and meta.cursorY <= meta.height then + meta.buffer[1][meta.cursorY][meta.cursorX] = char:sub(i,i) + meta.buffer[2][meta.cursorY][meta.cursorX] = text:sub(i,i) == " " and windont.default.textColor or text:sub(i,i) + meta.buffer[3][meta.cursorY][meta.cursorX] = back:sub(i,i) == " " and windont.default.backColor or back:sub(i,i) + meta.cursorX = meta.cursorX + 1 + end + end + if meta.alwaysRender then + output.redraw( + -1 + meta.x + initX, + -1 + meta.x + meta.cursorX, + -1 + meta.y + meta.cursorY + ) + end + end + + output.setCursorPos = function(x, y) + assert(type(x) == "number", "argument #1 must be number, got " .. type(x)) + assert(type(y) == "number", "argument #2 must be number, got " .. type(y)) + meta.cursorX, meta.cursorY = math.floor(x), math.floor(y) + if meta.alwaysRender then + if bT == output then + bT = output.meta.baseTerm + end + bT.setCursorPos( + -1 + meta.x + meta.cursorX, + -1 + meta.y + meta.cursorY + ) + end + end + + output.getCursorPos = function() + return meta.cursorX, meta.cursorY + end + + output.setTextColor = function(color) + if to_blit[color] then + meta.textColor = to_blit[color] else - mw.setTextColor(colors.lightGray) + error("Invalid color (got " .. color .. ")") end - mw.setBackgroundColor(colors.gray) - mw.clearLine() + end + output.setTextColour = output.setTextColor - if #output.title <= (mw.meta.width - 4) then - mw.write(output.title) -- write full title + output.setBackgroundColor = function(color) + if to_blit[color] then + meta.backColor = to_blit[color] else - mw.write(output.title:sub(1, mw.meta.width - 7) .. "...") -- draw abreviated title + error("Invalid color (got " .. color .. ")") end - mw.setCursorPos(mw.meta.width - 3, 1) - mw.write(" \22\94\215") -- minimize / maximize / close + end + output.setBackgroundColour = output.setBackgroundColor + + output.getTextColor = function() + return to_colors[meta.textColor] + end + output.getTextColour = output.getTextColor + + output.getBackgroundColor = function() + return to_colors[meta.backColor] + end + output.getBackgroundColour = output.getBackgroundColor + + output.setVisible = function(visible) + assert(type(visible) == "boolean", "bad argument #1 (expected boolean, got " .. type(visible) .. ")") + meta.visible = visible and true or false end - if type(program) == "string" then - output.main = function() - tw.clear() - return shell.run(program) + output.clear = function() + meta.buffer = meta.newBuffer(meta.width, meta.height, " ", meta.textColor, meta.backColor) + if meta.alwaysRender then + output.redraw() end - elseif type(program) == "function" then - output.main = program end - --local env = {} - --setmetatable(env, {__index = _G}) - --setfenv(output.main, env) - - output.coroutine = coroutine.create(output.main) - output.cFilter = nil - - for i = 1, #instances do - instances[i].focused = false + output.clearLine = function() + meta.buffer[1][meta.cursorY] = nil + meta.buffer[2][meta.cursorY] = nil + meta.buffer[3][meta.cursorY] = nil + meta.buffer = meta.newBuffer(meta.width, meta.height, " ", meta.textColor, meta.backColor, meta.buffer) + if meta.alwaysRender then + bT.setCursorPos(meta.x, -1 + meta.y + meta.cursorY) + bT.blit( + (" "):rep(meta.width), + (meta.textColor):rep(meta.width), + (meta.backColor):rep(meta.width) + ) + end end - table.insert(instances, 1, output) + output.getLine = function(y) + assert(type(y) == "number", "bad argument #1 (expected number, got " .. type(y) .. ")") + assert(meta.buffer[1][y], "Line is out of range.") + return table_concat(meta.buffer[1][y]), table_concat(meta.buffer[2][y]), table_concat(meta.buffer[3][y]) + end - resumeInstance(1, {}, false) + output.scroll = function(amplitude) + if math.abs(amplitude) < meta.height then -- minor optimization + local blank = {{}, {}, {}} + for x = 1, meta.width do + blank[1][x] = " " + blank[2][x] = meta.textColor + blank[3][x] = meta.backColor + end + for y = 1, meta.height do + meta.buffer[1][y] = meta.buffer[1][y + amplitude] or blank[1] + meta.buffer[2][y] = meta.buffer[2][y + amplitude] or blank[2] + meta.buffer[3][y] = meta.buffer[3][y + amplitude] or blank[3] + end + else + meta.buffer = meta.newBuffer(meta.width, meta.height, " ", meta.textColor, meta.backColor) + end + if meta.alwaysRender then + if math_floor(amplitude) ~= 0 then + output.redraw() + end + end + end + + output.getSize = function() + return meta.width, meta.height + end + + output.isColor = function() + return meta.isColor + end + output.isColour = output.isColor + + output.reposition = function(x, y, width, height) + assert(type(x) == "number", "bad argument #1 (expected number, got " .. type(x) .. ")") + assert(type(y) == "number", "bad argument #2 (expected number, got " .. type(y) .. ")") + meta.x = math_floor(x) + meta.y = math_floor(y) + if width then + assert(type(width) == "number", "bad argument #3 (expected number, got " .. type(width) .. ")") + assert(type(height) == "number", "bad argument #4 (expected number, got " .. type(height) .. ")") + meta.width = width + meta.height = height + meta.buffer = meta.newBuffer(meta.width, meta.height, " ", meta.textColor, meta.backColor, meta.buffer) + end + if meta.alwaysRender then + output.redraw() + end + end + + output.restoreCursor = function() + bT.setCursorPos( + math.max(0, -1 + meta.x + meta.cursorX), + math.max(0, -1 + meta.y + meta.cursorY) + ) + bT.setCursorBlink(meta.blink) + end + + output.getPosition = function() + return meta.x, meta.y + end + + output.setCursorBlink = function(blink) + meta.blink = blink and true or false + end + + output.getCursorBlink = function(blink) + return meta.blink + end + + output.setPaletteColor = bT.setPaletteColor + output.setPaletteColour = bT.setPaletteColour + output.getPaletteColor = bT.getPaletteColor + output.getPaletteColour = bT.getPaletteColour + + if bT.getPaletteColor then + output.nativePaletteColor = bT.nativePaletteColor or function(col) + if nativePalette[col] then + return table.unpack(nativePalette[col]) + else + return table.unpack(nativePalette[1]) -- I don't get how this function takes in non-base2 numbers... + end + end + end + + output.redraw = function(x1, x2, y, options) + options = options or {} + options.onlyX1 = x1 + options.onlyX2 = x2 + options.onlyY = y + if #meta.renderBuddies > 0 then + windont.render(options, output, table.unpack(meta.renderBuddies)) + else + windont.render(options, output) + end + output.restoreCursor() + end + + if meta.alwaysRender then + output.redraw() + end return output + end -local moveInstance = function(i, x, y, newWidth, newHeight, relative) - desktop.clear() - newWidth = math.max(newWidth or instances[i].mainWindow.meta.width, 3) - newHeight = math.max(newHeight or instances[i].mainWindow.meta.height, 3) - if relative then - if x == 0 and y == 0 then - if (not newWidth or newWidth == instances[i].mainWindow.meta.width) and (not newHeight or newHeight == instances[i].mainWindow.meta.height) then - return - end - end - instances[i].mainWindow.reposition(instances[i].mainWindow.meta.x + x, instances[i].mainWindow.meta.y + y, newWidth, newHeight) - else - if x == instances[i].mainWindow.meta.x and y == instances[i].mainWindow.meta.y then - if (not newWidth or newWidth == instances[i].mainWindow.meta.width) and (not newHeight or newHeight == instances[i].mainWindow.meta.height) then - return - end - end - instances[i].mainWindow.reposition(x, y, newWidth, newHeight) - end - instances[i].termWindow.reposition( - 2, 2, - math.max(instances[i].oldTermPos[3], instances[i].mainWindow.meta.width - 2), - math.max(instances[i].oldTermPos[4], instances[i].mainWindow.meta.height - 2) - ) - instances[i].termWindow.redraw(nil,nil,nil,{force = true}) - instances[i].refreshMainWindow() -end - -local render = function() - local wins = {} - for i = 1, #instances do - wins[i] = instances[i].mainWindow - instances[i].termWindow.redraw() - end - windont.render({force = true}, table.unpack(wins)) - windont.render({}, overlay, desktop) -end - -local main = function() - - newInstance(3, 3, 30, 12, "rom/programs/shell.lua", nil) - newInstance(8, 5, 30, 12, "rom/programs/shell.lua", nil) - - local evt, success, result, oldTerm - local cx, cy - local isCoordinateEvent - local usedCoordinateEvent - local isManipulatingInstance = false - -- handles input system - local keyTimer = os.startTimer(0.05) - - while true do - evt = {coroutine.yield()} - if evt[1] == "key" and not evt[3] then - keysDown[evt[2]] = 0 - elseif evt[1] == "key_up" then - keysDown[evt[2]] = nil - end - if evt[1] == "mouse_click" then - focusInstance(checkInstanceByPos(evt[3], evt[4])) - end - if evt[1] == "timer" and evt[2] == keyTimer then - keyTimer = os.startTimer(0.05) - for k,v in pairs(keysDown) do - keysDown[k] = v + 0.05 - end - - render() - - -- move windows with arrow keys (for now) - if false then - if keysDown[keys.right] then - desktop.clear() - instances[1].mainWindow.reposition(instances[1].mainWindow.meta.x + 1, instances[1].mainWindow.meta.y) - end - if keysDown[keys.left] then - desktop.clear() - instances[1].mainWindow.reposition(instances[1].mainWindow.meta.x - 1, instances[1].mainWindow.meta.y) - end - if keysDown[keys.up] then - desktop.clear() - instances[1].mainWindow.reposition(instances[1].mainWindow.meta.x, instances[1].mainWindow.meta.y - 1) - end - if keysDown[keys.down] then - desktop.clear() - instances[1].mainWindow.reposition(instances[1].mainWindow.meta.x, instances[1].mainWindow.meta.y + 1) - end - end - else - usedCoordinateEvent = false - isCoordinateEvent = false - for i = 1, #instances do - if (not isManipulatingInstance) and evt[1] == "mouse_click" and (keysDown[keys.leftAlt] or ( - (checkInstanceByPos(evt[3], evt[4], false) == i) and not (checkInstanceByPos(evt[3], evt[4], true) == i) - )) then - if evt[2] == 1 then - -- dragging a window - if instances[i].manipMode == 0 then - instances[i].manipMode = 1 - isManipulatingInstance = true - instances[i].dragging = {instances[i].mainWindow.meta.x - evt[3], instances[i].mainWindow.meta.y - evt[4]} - end - elseif evt[2] == 2 then - -- resizing a window - if instances[i].manipMode == 0 then - instances[i].manipMode = 2 - isManipulatingInstance = true - instances[i].oldTermPos = { - instances[i].termWindow.meta.x, - instances[i].termWindow.meta.y, - instances[i].termWindow.meta.width, - instances[i].termWindow.meta.height - } - instances[i].oldMainPos = { - instances[i].mainWindow.meta.x, - instances[i].mainWindow.meta.y, - instances[i].mainWindow.meta.width, - instances[i].mainWindow.meta.height - } - if evt[3] > (instances[i].mainWindow.meta.x + (instances[i].mainWindow.meta.width) - 1) - 2 then - instances[i].resizingRight = (instances[i].mainWindow.meta.x + instances[i].mainWindow.meta.width - 1) - evt[3] - elseif evt[3] < (instances[i].mainWindow.meta.x + 2) then - instances[i].resizingLeft = (instances[i].mainWindow.meta.x - 1) - evt[3] - end - if evt[4] > (instances[i].mainWindow.meta.y + (instances[i].mainWindow.meta.height) - 1) - 2 then - instances[i].resizingBottom = (instances[i].mainWindow.meta.y + instances[i].mainWindow.meta.height - 1) - evt[4] - elseif evt[4] < (instances[i].mainWindow.meta.y + 2) then - instances[i].resizingTop = (instances[i].mainWindow.meta.y - 1) - evt[4] - end - end - end - else - if evt[1] == "mouse_up" then - if instances[i].manipMode == 2 then - instances[i].mainWindow.clear() - instances[i].termWindow.reposition(2, 2, instances[i].mainWindow.meta.width - 2, instances[i].mainWindow.meta.height - 2) - instances[i].termWindow.redraw(nil, nil, nil, {force = true}) - end - instances[i].manipMode = 0 - isManipulatingInstance = false - instances[i].dragging = nil - instances[i].resizingRight = nil - instances[i].resizingLeft = nil - instances[i].resizingBottom = nil - instances[i].resizingTop = nil - elseif evt[1] == "mouse_drag" then - if isManipulatingInstance then - if instances[i].manipMode == 1 then - moveInstance( - i, - instances[i].dragging[1] + evt[3], - instances[i].dragging[2] + evt[4] - ) - elseif instances[i].manipMode == 2 then - local newX, newY = instances[i].mainWindow.meta.x, instances[i].mainWindow.meta.y - local newWidth, newHeight = instances[i].mainWindow.meta.width, instances[i].mainWindow.meta.height - if instances[i].resizingRight then - newWidth = instances[i].resizingRight + (evt[3] - instances[i].oldMainPos[1] + 1) - end - if instances[i].resizingLeft then - newX = instances[i].resizingLeft + evt[3] + 1 - newWidth = instances[i].oldMainPos[3] + (instances[i].oldMainPos[1] - newX) - end - if instances[i].resizingBottom then - newHeight = instances[i].resizingBottom + (evt[4] - instances[i].oldMainPos[2] + 1) - end - if instances[i].resizingTop then - newY = instances[i].resizingTop + evt[4] + 1 - newHeight = instances[i].oldMainPos[4] + (instances[i].oldMainPos[2] - newY) - end - moveInstance( - i, - newX, - newY, - newWidth, - newHeight - ) - end - end - end - if (instances[i].cFilter == evt[1] or instances[i].cFilter == "terminate" or (not instances[i].cFilter)) then - if (instances[i].focused or not FocusEvents[evt[1]]) then - resumeInstance(i, evt, CoordinateEvents[evt[1]]) - if CoordinateEvents[evt[1]] then - usedCoordinateEvent = true - end - end - end - end - instances[i].writeTitleBar() - end - end - - -- check for dead instances - for i = #instances, 1, -1 do - if not instances[i].alive then - if instances[i].focused then - if instances[i - 1] then - instances[i - 1].focused = true - elseif instances[i + 1] then - instances[i + 1].focused = true - end - end - table.remove(instances, i) - desktop.clear() - end - end - - -- make sure the focused window is on top - for i = 1, #instances do - if instances[i].focused then - instances[i], instances[1] = instances[1], instances[i] - break - end - end - end -end - -main() +return windont