local Canvas = require('opus.ui.canvas') local colors = _G.colors local term = _G.term local _gsub = string.gsub local Terminal = { } local mapColorToGray = { [ colors.white ] = colors.white, [ colors.orange ] = colors.lightGray, [ colors.magenta ] = colors.lightGray, [ colors.lightBlue ] = colors.lightGray, [ colors.yellow ] = colors.lightGray, [ colors.lime ] = colors.lightGray, [ colors.pink ] = colors.lightGray, [ colors.gray ] = colors.gray, [ colors.lightGray ] = colors.lightGray, [ colors.cyan ] = colors.lightGray, [ colors.purple ] = colors.gray, [ colors.blue ] = colors.gray, [ colors.brown ] = colors.gray, [ colors.green ] = colors.lightGray, [ colors.red ] = colors.gray, [ colors.black ] = colors.black, } -- Replacement for window api with scrolling and buffering function Terminal.window(parent, sx, sy, w, h, isVisible) isVisible = isVisible ~= false if not w or not h then w, h = parent.getSize() end local win = { } local maxScroll = 100 local cx, cy = 1, 1 local blink = false local _bg, _fg = parent.getBackgroundColor(), parent.getTextColor() win.canvas = Canvas({ x = sx, y = sy, width = w, height = h, isColor = parent.isColor(), offy = 0, bg = _bg, fg = _fg, }) local function update() if isVisible then win.canvas:render(parent) win.setCursorPos(cx, cy) end end local function scrollTo(y) y = math.max(0, y) y = math.min(#win.canvas.lines - win.canvas.height, y) if y ~= win.canvas.offy then win.canvas.offy = y win.canvas:dirty() update() end end function win.write(str) str = tostring(str) or '' win.canvas:write(cx, cy + win.canvas.offy, str, win.canvas.bg, win.canvas.fg) win.setCursorPos(cx + #str, cy) update() end function win.blit(str, fg, bg) win.canvas:blit(cx, cy + win.canvas.offy, str, bg, fg) win.setCursorPos(cx + #str, cy) update() end function win.clear() win.canvas.offy = 0 for i = #win.canvas.lines, win.canvas.height + 1, -1 do win.canvas.lines[i] = nil end win.canvas:clear() update() end function win.getLine(n) local line = win.canvas.lines[n] return line.text, line.fg, line.bg end function win.clearLine() win.canvas:clearLine(cy + win.canvas.offy) win.setCursorPos(cx, cy) update() end function win.getCursorPos() return cx, cy end function win.setCursorPos(x, y) cx, cy = math.floor(x), math.floor(y) if isVisible then parent.setCursorPos(cx + win.canvas.x - 1, cy + win.canvas.y - 1) end end function win.getCursorBlink() return blink end function win.setCursorBlink(b) blink = b if isVisible then parent.setCursorBlink(b) end end function win.isColor() return win.canvas.isColor end win.isColour = win.isColor function win.setTextColor(c) win.canvas.fg = c end win.setTextColour = win.setTextColor function win.getPaletteColor(n) if parent.getPaletteColor then return parent.getPaletteColor(n) end return 0, 0, 0 end win.getPaletteColour = win.getPaletteColor function win.setPaletteColor(n, r, g, b) if parent.setPaletteColor then return parent.setPaletteColor(n, r, g, b) end end win.setPaletteColour = win.setPaletteColor function win.setBackgroundColor(c) win.canvas.bg = c end win.setBackgroundColour = win.setBackgroundColor function win.getSize() return win.canvas.width, win.canvas.height end function win.scroll(n) n = n or 1 if n > 0 then local lines = #win.canvas.lines for i = 1, n do win.canvas.lines[lines + i] = { } win.canvas:clearLine(lines + i) end while #win.canvas.lines > maxScroll do table.remove(win.canvas.lines, 1) end scrollTo(#win.canvas.lines) win.canvas:dirty() update() end end function win.getTextColor() return win.canvas.fg end win.getTextColour = win.getTextColor function win.getBackgroundColor() return win.canvas.bg end win.getBackgroundColour = win.getBackgroundColor function win.setVisible(visible) if visible ~= isVisible then isVisible = visible if isVisible then win.canvas:dirty() update() end end end function win.redraw() if isVisible then win.canvas:dirty() update() end end function win.restoreCursor() if isVisible then win.setCursorPos(cx, cy) win.setTextColor(win.canvas.fg) win.setCursorBlink(blink) end end function win.getPosition() return win.canvas.x, win.canvas.y end function win.reposition(x, y, width, height) win.canvas.x, win.canvas.y = x, y win.canvas:resize(width or win.canvas.width, height or win.canvas.height) end --[[ Additional methods ]]-- function win.scrollDown() scrollTo(win.canvas.offy + 1) end function win.scrollUp() scrollTo(win.canvas.offy - 1) end function win.scrollTop() scrollTo(0) end function win.scrollBottom() scrollTo(#win.canvas.lines) end function win.setMaxScroll(ms) maxScroll = ms end function win.getCanvas() return win.canvas end function win.getParent() return parent end win.canvas:clear() return win end -- get windows contents function Terminal.getContents(win) if not win.getLine then error('window is required') end local lines = { } local _, h = win.getSize() for i = 1, h do local text, fg, bg = win.getLine(i) lines[i] = { text = text, fg = fg, bg = bg, } end return lines end function Terminal.colorToGrayscale(c) return mapColorToGray[c] end function Terminal.toGrayscale(ct) local methods = { 'setBackgroundColor', 'setBackgroundColour', 'setTextColor', 'setTextColour' } for _,v in pairs(methods) do local fn = ct[v] ct[v] = function(c) fn(mapColorToGray[c]) end end local bcolors = { [ '1' ] = '8', [ '2' ] = '8', [ '3' ] = '8', [ '4' ] = '8', [ '5' ] = '8', [ '6' ] = '8', [ '9' ] = '8', [ 'a' ] = '7', [ 'b' ] = '7', [ 'c' ] = '7', [ 'd' ] = '8', [ 'e' ] = '7', } local function translate(s) if s then s = _gsub(s, "%w", bcolors) end return s end local fn = ct.blit ct.blit = function(text, fg, bg) fn(text, translate(fg), translate(bg)) end end function Terminal.getNullTerm(ct) local nt = Terminal.copy(ct) local methods = { 'blit', 'clear', 'clearLine', 'scroll', 'setCursorBlink', 'setCursorPos', 'write' } for _,v in pairs(methods) do nt[v] = function() end end return nt end function Terminal.copy(it, ot) ot = ot or { } for k,v in pairs(it) do if type(v) == 'function' then ot[k] = v end end return ot end function Terminal.mirror(ct, dt) local t = { } for k,f in pairs(ct) do t[k] = function(...) local ret = { f(...) } if dt[k] then dt[k](...) end return table.unpack(ret) end end return t end function Terminal.readPassword(prompt) if prompt then term.write(prompt) end local fn = term.current().write term.current().write = function() end local s pcall(function() s = _G.read(prompt) end) term.current().write = fn if s == '' then return end return s end return Terminal