//! The terminal's text: a grid of cells, a cursor, and which rows changed. use alloc::vec; use alloc::vec::Vec; pub const MAX_COLS: usize = 160; pub const MAX_ROWS: usize = 64; /// On the heap: a full screen is more than a program stack holds. pub struct Grid { cells: Vec, cols: usize, rows: usize, pub col: usize, pub row: usize, /// Rows to redraw. dirty: Vec, /// Rows everything moved up by since the screen last caught up: moving /// the pixels is cheaper than drawing every row again. scrolled: usize, } impl Grid { pub fn new(cols: usize, rows: usize) -> Self { let (cols, rows) = (cols.clamp(1, MAX_COLS), rows.clamp(1, MAX_ROWS)); Self { cells: vec![b' '; cols * rows], cols, rows, col: 0, row: 0, dirty: vec![true; rows], scrolled: 0 } } pub fn row(&self, r: usize) -> &[u8] { &self.cells[r * self.cols..(r + 1) * self.cols] } pub fn rows(&self) -> usize { self.rows } /// The rows changed since the last call, each once. pub fn take_dirty(&mut self) -> impl Iterator + '_ { let rows = self.rows; self.dirty[..rows].iter_mut().enumerate().filter_map(|(r, d)| core::mem::take(d).then_some(r)) } /// How far to move the screen up before drawing the dirty rows, which /// already account for it. pub fn take_scrolled(&mut self) -> usize { core::mem::take(&mut self.scrolled) } fn mark(&mut self, r: usize) { self.dirty[r] = true; } fn newline(&mut self) { // The row the cursor leaves, so its cursor cell is redrawn. self.mark(self.row); self.col = 0; if self.row + 1 < self.rows { self.row += 1; return; } // At the bottom: everything moves up a row. self.cells.copy_within(self.cols.., 0); let last = (self.rows - 1) * self.cols; self.cells[last..].fill(b' '); // Rows still to redraw move up with their text. self.dirty.copy_within(1.., 0); self.mark(self.rows - 1); if self.scrolled + 1 >= self.rows { // Scrolled a whole screen: just draw it all. self.touch_all(); self.scrolled = 0; } else { self.scrolled += 1; } } /// Writes one byte at the cursor: `\n` moves to the next line, other /// bytes wrap at the right edge. pub fn put(&mut self, b: u8) { if b == b'\n' { self.newline(); return; } if self.col == self.cols { self.newline(); } self.cells[self.row * self.cols + self.col] = b; self.mark(self.row); self.col += 1; } /// Erases the cell before the cursor, on this row only. pub fn back(&mut self) { if self.col > 0 { self.col -= 1; self.cells[self.row * self.cols + self.col] = b' '; self.mark(self.row); } } /// Marks every row to be drawn again. pub fn touch_all(&mut self) { self.dirty.fill(true); } pub fn clear(&mut self) { self.cells.fill(b' '); self.col = 0; self.row = 0; self.dirty.fill(true); self.scrolled = 0; } }