use crate::primitives::termtui::row::Row;
use crate::primitives::termtui::size::Size;
use std::collections::VecDeque;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Pos {
pub col: u16,
pub row: u16,
}
impl Pos {
pub fn new(col: u16, row: u16) -> Self {
Self { col, row }
}
}
#[derive(Clone, Debug)]
pub struct Grid {
rows: VecDeque<Row>,
size: Size,
pos: Pos,
scrollback_len: usize,
scrollback_offset: usize,
used_rows: usize,
scroll_top: u16,
scroll_bottom: u16,
saved_pos: Option<Pos>,
}
impl Grid {
pub fn new(size: Size, scrollback_len: usize) -> Self {
let rows = (0..size.rows)
.map(|_| Row::new(size.cols))
.collect::<VecDeque<_>>();
Self {
rows,
size,
pos: Pos::default(),
scrollback_len,
scrollback_offset: 0,
used_rows: 0,
scroll_top: 0,
scroll_bottom: size.rows,
saved_pos: None,
}
}
pub fn size(&self) -> Size {
self.size
}
pub fn pos(&self) -> Pos {
self.pos
}
pub fn set_pos(&mut self, pos: Pos) {
self.pos = Pos {
col: pos.col.min(self.size.cols.saturating_sub(1)),
row: pos.row.min(self.size.rows.saturating_sub(1)),
};
}
pub fn set_col(&mut self, col: u16) {
self.pos.col = col.min(self.size.cols.saturating_sub(1));
}
pub fn set_row(&mut self, row: u16) {
self.pos.row = row.min(self.size.rows.saturating_sub(1));
}
pub fn save_pos(&mut self) {
self.saved_pos = Some(self.pos);
}
pub fn restore_pos(&mut self) {
if let Some(pos) = self.saved_pos {
self.pos = pos;
}
}
fn row0(&self) -> usize {
self.rows.len().saturating_sub(self.size.rows as usize)
}
pub fn scrollback(&self) -> usize {
self.scrollback_offset
}
pub fn set_scrollback(&mut self, offset: usize) {
let max_offset = self.row0();
self.scrollback_offset = offset.min(max_offset);
}
pub fn scrollback_available(&self) -> usize {
self.row0()
}
pub fn set_scroll_region(&mut self, top: u16, bottom: u16) {
self.scroll_top = top.min(self.size.rows.saturating_sub(1));
self.scroll_bottom = bottom.min(self.size.rows).max(self.scroll_top + 1);
}
pub fn reset_scroll_region(&mut self) {
self.scroll_top = 0;
self.scroll_bottom = self.size.rows;
}
pub fn visible_row(&self, row: u16) -> Option<&Row> {
let idx = self.row0() + row as usize;
let idx = idx.saturating_sub(self.scrollback_offset);
self.rows.get(idx)
}
pub fn drawing_row(&self, row: u16) -> Option<&Row> {
let idx = self.row0() + row as usize;
self.rows.get(idx)
}
pub fn drawing_row_mut(&mut self, row: u16) -> Option<&mut Row> {
let idx = self.row0() + row as usize;
if row as usize >= self.used_rows {
self.used_rows = row as usize + 1;
}
self.rows.get_mut(idx)
}
pub fn current_row(&self) -> Option<&Row> {
self.drawing_row(self.pos.row)
}
pub fn current_row_mut(&mut self) -> Option<&mut Row> {
let row = self.pos.row;
self.drawing_row_mut(row)
}
pub fn scroll_up(&mut self, count: usize) {
for _ in 0..count {
if self.scroll_top == 0 && self.scroll_bottom == self.size.rows {
self.rows.push_back(Row::new(self.size.cols));
while self.rows.len() > self.size.rows as usize + self.scrollback_len {
self.rows.pop_front();
}
} else {
let top_idx = self.row0() + self.scroll_top as usize;
let bottom_idx = self.row0() + self.scroll_bottom as usize - 1;
if top_idx < self.rows.len() && bottom_idx < self.rows.len() {
self.rows.remove(top_idx);
self.rows.insert(bottom_idx, Row::new(self.size.cols));
}
}
}
}
pub fn scroll_down(&mut self, count: usize) {
for _ in 0..count {
let top_idx = self.row0() + self.scroll_top as usize;
let bottom_idx = self.row0() + self.scroll_bottom as usize - 1;
if top_idx < self.rows.len() && bottom_idx < self.rows.len() {
self.rows.remove(bottom_idx);
self.rows.insert(top_idx, Row::new(self.size.cols));
}
}
}
pub fn clear(&mut self) {
for row in self.rows.iter_mut() {
row.clear();
}
self.used_rows = 0;
}
pub fn clear_below(&mut self) {
let pos_row = self.pos.row;
let pos_col = self.pos.col;
let cols = self.size.cols;
let rows = self.size.rows;
if let Some(row) = self.drawing_row_mut(pos_row) {
row.erase(pos_col, cols);
}
for r in (pos_row + 1)..rows {
if let Some(row) = self.drawing_row_mut(r) {
row.clear();
}
}
}
pub fn clear_above(&mut self) {
let pos_row = self.pos.row;
let pos_col = self.pos.col;
for r in 0..pos_row {
if let Some(row) = self.drawing_row_mut(r) {
row.clear();
}
}
if let Some(row) = self.drawing_row_mut(pos_row) {
row.erase(0, pos_col + 1);
}
}
pub fn resize(&mut self, new_size: Size) {
for row in self.rows.iter_mut() {
row.resize(new_size.cols);
}
while self.rows.len() < new_size.rows as usize {
self.rows.push_back(Row::new(new_size.cols));
}
self.size = new_size;
self.pos.col = self.pos.col.min(new_size.cols.saturating_sub(1));
self.pos.row = self.pos.row.min(new_size.rows.saturating_sub(1));
self.scroll_bottom = new_size.rows;
}
pub fn get_selected_text(&self, low_x: i32, low_y: i32, high_x: i32, high_y: i32) -> String {
let mut contents = String::new();
let row0 = self.row0() as i32;
let start_row = (row0 + low_y).max(0) as usize;
let end_row = (row0 + high_y).max(0) as usize;
for (i, row) in self.rows.iter().enumerate() {
if i < start_row || i > end_row {
continue;
}
let width = row.width();
let start_col = if i == start_row {
(low_x.max(0) as u16).min(width)
} else {
0
};
let end_col = if i == end_row {
(high_x.max(0) as u16).min(width)
} else {
width
};
row.write_contents(&mut contents, start_col, end_col);
if i != end_row && !row.wrapped() {
contents.push('\n');
}
}
contents
.lines()
.map(|line| line.trim_end())
.collect::<Vec<_>>()
.join("\n")
}
pub fn visible_rows(&self) -> impl Iterator<Item = &Row> {
let start = self.row0().saturating_sub(self.scrollback_offset);
let end = start + self.size.rows as usize;
self.rows.iter().skip(start).take(end - start)
}
pub fn drawing_rows(&self) -> impl Iterator<Item = &Row> {
let start = self.row0();
self.rows.iter().skip(start).take(self.size.rows as usize)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grid_new() {
let grid = Grid::new(Size::new(80, 24), 1000);
assert_eq!(grid.size().cols, 80);
assert_eq!(grid.size().rows, 24);
assert_eq!(grid.pos().col, 0);
assert_eq!(grid.pos().row, 0);
}
#[test]
fn test_grid_cursor() {
let mut grid = Grid::new(Size::new(80, 24), 1000);
grid.set_pos(Pos::new(10, 5));
assert_eq!(grid.pos(), Pos::new(10, 5));
grid.set_pos(Pos::new(100, 50));
assert_eq!(grid.pos(), Pos::new(79, 23));
}
#[test]
fn test_grid_scroll_up() {
let mut grid = Grid::new(Size::new(80, 24), 100);
if let Some(row) = grid.drawing_row_mut(0) {
if let Some(cell) = row.get_mut(0) {
cell.set_text("A");
}
}
grid.scroll_up(1);
assert_eq!(grid.scrollback_available(), 1);
}
#[test]
fn test_grid_scrollback() {
let mut grid = Grid::new(Size::new(80, 24), 100);
for _ in 0..10 {
grid.scroll_up(1);
}
assert_eq!(grid.scrollback_available(), 10);
grid.set_scrollback(5);
assert_eq!(grid.scrollback(), 5);
grid.set_scrollback(1000);
assert_eq!(grid.scrollback(), 10);
}
#[test]
fn test_grid_get_selected_text() {
let mut grid = Grid::new(Size::new(80, 24), 100);
if let Some(row) = grid.drawing_row_mut(0) {
for (i, c) in "Hello World".chars().enumerate() {
if let Some(cell) = row.get_mut(i as u16) {
cell.set_text(c.to_string());
}
}
}
let text = grid.get_selected_text(0, 0, 5, 0);
assert_eq!(text, "Hello");
}
#[test]
fn test_grid_resize() {
let mut grid = Grid::new(Size::new(80, 24), 100);
grid.resize(Size::new(120, 40));
assert_eq!(grid.size().cols, 120);
assert_eq!(grid.size().rows, 40);
}
}