------------------- -- Console class -- ------------------- do Console = class() local CHAR_DELETE = "^DEL^" local CHAR_CLEAR = "^CLEAR^" local CHAR_CURSOR_BACK = "^CB^" local CHAR_CURSOR_FWD = "^CF^" local CHAR_KILL_INPUT = "^KILLINP^" local CHAR_NEWLINE = "\n" local CHAR_TAB = "\t" local MAGIC_CHAR_PATTERN = "^%^.+%^$" function Console:init() self.lineBuffer = {} self.backgroundColor = platform.isColorDisplay() and 0 or 0xFFFFFF self:fit() self.input = {} self.cursorY = 1 self.cursorX = 1 self.cursorVisible = true self.linesMoved = 0 end function Console:fit(w, h) self.height = h or platform.window:height() self.width = w or platform.window:width() self.rows = math.floor(self.height / M_CHAR_HEIGHT) self.cols = math.floor(self.width / M_CHAR_WIDTH) end function Console:draw(gc) gc:setColorRGB(self.backgroundColor) gc:fillRect(0, 0, self.width, self.height) local lines = #self.lineBuffer for line=1, math.min(lines, self.rows) do gc:drawStringM(self.lineBuffer[line], 0, (line - 1) * M_CHAR_HEIGHT) end if self.cursorVisible then gc:drawStringM("_", (self.cursorX-1) * M_CHAR_WIDTH, (self.cursorY-1) * M_CHAR_HEIGHT) end end function Console:moveUp() self.linesMoved = self.linesMoved + 1 local lineBuffer = self.lineBuffer for line=1, #lineBuffer do lineBuffer[line] = lineBuffer[line+1] end end function Console:write(str, ignoreCursor) local lineBuffer = self.lineBuffer local rows = self.rows local cols = self.cols local x = self.cursorX local y = self.cursorY while #lineBuffer < y do table.insert(lineBuffer, "") end local predata = lineBuffer[y]:sub(1, x-1) while #predata+1 < x do predata = predata .. " " end str = predata .. str local lines = str:gsub(CHAR_TAB, " "):split(CHAR_NEWLINE) local bufferLine = "" for _, line in ipairs(lines) do repeat bufferLine = lineBuffer[y] if not bufferLine then table.insert(lineBuffer, "") bufferLine = "" end local linePart = line:sub(1, cols) lineBuffer[y] = linePart .. bufferLine:sub(#linePart+1, -1) x = #linePart + 1 line = line:sub(cols + 1, -1) y = y + 1 until #line == 0 end if x > cols then x = 1 y = y + 1 end while #lineBuffer > rows do self:moveUp() y = y - 1 end if not ignoreCursor then self.cursorY, self.cursorX = y-1, x end platform.window:invalidateAll() return x, y-1 end function Console:clear() self.lineBuffer = {} self.cursorX, self.cursorY = 1,1 platform.window:invalidateAll() end function Console:print(...) local str = "" local inp = {...} for i=1, #inp do str = str .. tostring(inp[i]) .. (i ~= #inp and CHAR_TAB or "") end self:write(str .. CHAR_NEWLINE) platform.window:invalidateAll() end -- Only call this from within the running session! function Console:read(session) local char while not char do char = table.remove(self.input, 1) session:sleep(0) end return char end -- Only call this from within the running session! function Console:readLine(session, str) local cursorInputOffset = 0 local x, y, moved = self.cursorX, self.cursorY, self.linesMoved local lx, ly = x, y local result = str or "" result = result:gsub(CHAR_NEWLINE, " ") local char = "" repeat -- Update the input result = result:sub(1, #result+cursorInputOffset) .. char .. result:sub(#result+cursorInputOffset+1, -1) -- Clear previous input self.cursorX, self.cursorY = x, y moved = self.linesMoved self:write(string.rep(" ", #result+1), true) if moved ~= self.linesMoved then y = math.max(y - (self.linesMoved - moved), 1) self.cursorY = y end -- Print updated string, and move the cursor to the correct position lx, ly = self:write(result) for _=cursorInputOffset, -1 do self:cursorBack() end -- Read a new char char = self:read(session) -- Magic char? if char:find(MAGIC_CHAR_PATTERN) then -- If the user send a delete char if char == CHAR_DELETE then result = result:sub(1, #result+cursorInputOffset-1) .. result:sub(#result+cursorInputOffset+1, -1) -- If the user wants to clear elseif char == CHAR_CLEAR then cursorInputOffset = 0 self.cursorX, self.cursorY = x, y self:write(string.rep(" ", #result+1), true) result = "" -- If the user (wants to) move the cursor back elseif char == CHAR_CURSOR_BACK and -cursorInputOffset < #result then cursorInputOffset = cursorInputOffset - 1 self:cursorBack() -- If the users (wants to) move the cursor forward elseif char == CHAR_CURSOR_FWD and cursorInputOffset < 0 then cursorInputOffset = cursorInputOffset + 1 self:cursorForward() -- Kill the input! elseif char == CHAR_KILL_INPUT then -- Clear previous input and return what the user has entered until now self.cursorX, self.cursorY = x, y self:write(string.rep(" ", #result+1), true) return result end -- Clear the char char = "" end until char == CHAR_NEWLINE self.cursorX, self.cursorY = lx, ly self:write(CHAR_NEWLINE) return result end function Console:dataIn(str) table.insert(self.input, str) end function Console:cursorBack() local x, y = self.cursorX, self.cursorY x = x - 1 if x == 0 then y = math.max(1, y - 1) x = #self.lineBuffer[y] end self.cursorX, self.cursorY = x, y return x, y end function Console:cursorForward() local x, y = self.cursorX, self.cursorY local cols, rows = self.cols, self.rows x = x + 1 if x > self.cols then y = y + 1 x = 1 end self.cursorX, self.cursorY = x, y return x, y end end