#[derive(Clone, Debug)]
pub(crate) struct Grid {
size: Size,
pos: Pos,
saved_pos: Pos,
rows: Vec<crate::row::Row>,
scroll_top: u16,
scroll_bottom: u16,
origin_mode: bool,
saved_origin_mode: bool,
scrollback: std::collections::VecDeque<crate::row::Row>,
scrollback_len: usize,
scrollback_offset: usize,
pushed_to_scrollback: u64,
tab_stops: Vec<bool>,
}
const TAB_STOP_INTERVAL: u16 = 8;
fn default_tab_stops(cols: u16) -> Vec<bool> {
(0..cols)
.map(|c| c > 0 && c % TAB_STOP_INTERVAL == 0)
.collect()
}
impl Grid {
pub(crate) fn new(size: Size, scrollback_len: usize) -> Self {
Self {
size,
pos: Pos::default(),
saved_pos: Pos::default(),
rows: vec![],
scroll_top: 0,
scroll_bottom: size.rows - 1,
origin_mode: false,
saved_origin_mode: false,
scrollback: std::collections::VecDeque::new(),
scrollback_len,
scrollback_offset: 0,
pushed_to_scrollback: 0,
tab_stops: default_tab_stops(size.cols),
}
}
pub(crate) fn pushed_to_scrollback(&self) -> u64 {
self.pushed_to_scrollback
}
pub(crate) fn row_at_absolute(&self, abs_row: u64) -> Option<&crate::row::Row> {
let total = self.pushed_to_scrollback;
let scrollback_len = self.scrollback.len() as u64;
let oldest = total.saturating_sub(scrollback_len);
if abs_row < oldest {
return None;
}
if abs_row < total {
let idx = (abs_row - oldest) as usize;
return self.scrollback.get(idx);
}
let drawing_idx = (abs_row - total) as usize;
self.rows.get(drawing_idx)
}
pub(crate) fn allocate_rows(&mut self) {
if self.rows.is_empty() {
self.rows.extend(
std::iter::repeat_with(|| crate::row::Row::new(self.size.cols))
.take(usize::from(self.size.rows)),
);
}
}
fn new_row(&self) -> crate::row::Row {
let mut row = crate::row::Row::new(self.size.cols);
row.mark_dirty();
row
}
pub(crate) fn mark_all_dirty(&mut self) {
for row in &mut self.rows {
row.mark_dirty();
}
}
pub(crate) fn dirty_row_indices(&self) -> impl Iterator<Item = u16> + '_ {
self.rows.iter().enumerate().filter_map(|(idx, row)| {
if row.is_dirty() {
Some(idx.try_into().expect("rows.len() <= u16::MAX by set_size"))
} else {
None
}
})
}
pub(crate) fn clear_dirty(&mut self) {
for row in &mut self.rows {
row.clear_dirty();
}
}
pub(crate) fn clear(&mut self) {
self.pos = Pos::default();
self.saved_pos = Pos::default();
for row in self.drawing_rows_mut() {
row.clear(crate::attrs::Attrs::default());
}
self.scroll_top = 0;
self.scroll_bottom = self.size.rows - 1;
self.origin_mode = false;
self.saved_origin_mode = false;
self.tab_stops = default_tab_stops(self.size.cols);
}
pub(crate) fn size(&self) -> Size {
self.size
}
pub(crate) fn scrollback_len(&self) -> usize {
self.scrollback_len
}
pub(crate) fn resize_simple(&mut self, size: Size) {
if size.cols != self.size.cols {
for row in &mut self.rows {
row.wrap(false);
}
self.tab_stops.resize(usize::from(size.cols), false);
for col in self.size.cols..size.cols {
if col > 0 && col % TAB_STOP_INTERVAL == 0 {
self.tab_stops[usize::from(col)] = true;
}
}
}
if self.scroll_bottom == self.size.rows - 1 {
self.scroll_bottom = size.rows - 1;
}
self.size = size;
for row in &mut self.rows {
row.resize(size.cols, crate::cell::Cell::new());
}
self.rows.resize(usize::from(size.rows), self.new_row());
if self.scroll_bottom >= size.rows {
self.scroll_bottom = size.rows - 1;
}
if self.scroll_bottom < self.scroll_top {
self.scroll_top = 0;
}
self.row_clamp_top(false);
self.row_clamp_bottom(false);
self.col_clamp();
if self.saved_pos.row > self.size.rows - 1 {
self.saved_pos.row = self.size.rows - 1;
}
if self.saved_pos.col > self.size.cols - 1 {
self.saved_pos.col = self.size.cols - 1;
}
}
pub(crate) fn install_reflowed(
&mut self,
drawing: Vec<crate::row::Row>,
scrollback_extra: Vec<crate::row::Row>,
new_size: Size,
new_pos: Pos,
) {
let old_cols = self.size.cols;
self.tab_stops.resize(usize::from(new_size.cols), false);
for col in old_cols..new_size.cols {
if col > 0 && col % TAB_STOP_INTERVAL == 0 {
self.tab_stops[usize::from(col)] = true;
}
}
if self.scroll_bottom == self.size.rows.saturating_sub(1) {
self.scroll_bottom = new_size.rows - 1;
}
debug_assert_eq!(drawing.len(), usize::from(new_size.rows));
self.rows = drawing;
if self.scrollback_len > 0 {
for row in scrollback_extra {
self.scrollback.push_back(row);
self.pushed_to_scrollback = self.pushed_to_scrollback.saturating_add(1);
while self.scrollback.len() > self.scrollback_len {
self.scrollback.pop_front();
}
}
self.scrollback_offset = self.scrollback_offset.min(self.scrollback.len());
}
self.size = new_size;
if self.scroll_bottom >= new_size.rows {
self.scroll_bottom = new_size.rows - 1;
}
if self.scroll_bottom < self.scroll_top {
self.scroll_top = 0;
}
if self.saved_pos.row > new_size.rows - 1 {
self.saved_pos.row = new_size.rows - 1;
}
if self.saved_pos.col > new_size.cols - 1 {
self.saved_pos.col = new_size.cols - 1;
}
self.pos = Pos {
row: new_pos.row.min(new_size.rows - 1),
col: new_pos.col.min(new_size.cols - 1),
};
}
pub(crate) fn pos(&self) -> Pos {
self.pos
}
pub(crate) fn set_pos(&mut self, mut pos: Pos) {
let old_row = self.pos.row;
if self.origin_mode {
pos.row = pos.row.saturating_add(self.scroll_top);
}
self.pos = pos;
self.row_clamp_top(self.origin_mode);
self.row_clamp_bottom(self.origin_mode);
self.col_clamp();
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn save_cursor(&mut self) {
self.saved_pos = self.pos;
self.saved_origin_mode = self.origin_mode;
}
pub(crate) fn restore_cursor(&mut self) {
let old_row = self.pos.row;
self.pos = self.saved_pos;
self.origin_mode = self.saved_origin_mode;
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn visible_rows(&self) -> impl Iterator<Item = &crate::row::Row> {
let scrollback_len = self.scrollback.len();
let rows_len = self.rows.len();
self.scrollback
.iter()
.skip(scrollback_len - self.scrollback_offset)
.take(rows_len)
.chain(
self.rows
.iter()
.take(rows_len.saturating_sub(self.scrollback_offset)),
)
}
pub(crate) fn drawing_rows(&self) -> impl Iterator<Item = &crate::row::Row> {
self.rows.iter()
}
pub(crate) fn scrollback_rows(&self) -> impl Iterator<Item = &crate::row::Row> {
self.scrollback.iter()
}
pub(crate) fn drawing_rows_mut(&mut self) -> impl Iterator<Item = &mut crate::row::Row> {
self.rows.iter_mut()
}
pub(crate) fn visible_row(&self, row: u16) -> Option<&crate::row::Row> {
self.visible_rows().nth(usize::from(row))
}
pub(crate) fn drawing_row(&self, row: u16) -> Option<&crate::row::Row> {
self.drawing_rows().nth(usize::from(row))
}
pub(crate) fn drawing_row_mut(&mut self, row: u16) -> Option<&mut crate::row::Row> {
self.drawing_rows_mut().nth(usize::from(row))
}
pub(crate) fn current_row_mut(&mut self) -> &mut crate::row::Row {
let row = self.pos.row;
let rows = self.size.rows;
self.drawing_row_mut(row)
.unwrap_or_else(|| unreachable!("cursor row {row} kept within {rows} by clamps"))
}
pub(crate) fn visible_cell(&self, pos: Pos) -> Option<&crate::cell::Cell> {
self.visible_row(pos.row).and_then(|r| r.get(pos.col))
}
pub(crate) fn drawing_cell(&self, pos: Pos) -> Option<&crate::cell::Cell> {
self.drawing_row(pos.row).and_then(|r| r.get(pos.col))
}
pub(crate) fn drawing_cell_mut(&mut self, pos: Pos) -> Option<&mut crate::cell::Cell> {
self.drawing_row_mut(pos.row)
.and_then(|r| r.get_mut(pos.col))
}
pub(crate) fn clear_scrollback(&mut self) {
self.scrollback.clear();
self.scrollback_offset = 0;
}
pub(crate) fn set_scrollback(&mut self, rows: usize) {
self.scrollback_offset = rows.min(self.scrollback.len());
}
pub(crate) fn scrollback(&self) -> usize {
self.scrollback_offset
}
pub(crate) fn scrollback_available(&self) -> usize {
self.scrollback.len()
}
pub(crate) fn scrollback_contents(&self, limit: Option<usize>) -> Vec<String> {
let total = self.scrollback.len();
let skip = match limit {
Some(n) => total.saturating_sub(n),
None => 0,
};
self.scrollback
.iter()
.skip(skip)
.map(|row| row.text_contents())
.collect()
}
pub(crate) fn erase_all(&mut self, attrs: crate::attrs::Attrs) {
for row in self.drawing_rows_mut() {
row.clear(attrs);
}
}
pub(crate) fn erase_all_forward(&mut self, attrs: crate::attrs::Attrs) {
let pos = self.pos;
for row in self.drawing_rows_mut().skip(usize::from(pos.row) + 1) {
row.clear(attrs);
}
self.erase_row_forward(attrs);
}
pub(crate) fn erase_all_backward(&mut self, attrs: crate::attrs::Attrs) {
let pos = self.pos;
for row in self.drawing_rows_mut().take(usize::from(pos.row)) {
row.clear(attrs);
}
self.erase_row_backward(attrs);
}
pub(crate) fn erase_row(&mut self, attrs: crate::attrs::Attrs) {
self.current_row_mut().clear(attrs);
}
pub(crate) fn erase_row_forward(&mut self, attrs: crate::attrs::Attrs) {
let size = self.size;
let pos = self.pos;
let row = self.current_row_mut();
for col in pos.col..size.cols {
row.erase(col, attrs);
}
}
pub(crate) fn erase_row_backward(&mut self, attrs: crate::attrs::Attrs) {
let size = self.size;
let pos = self.pos;
let row = self.current_row_mut();
for col in 0..=pos.col.min(size.cols - 1) {
row.erase(col, attrs);
}
}
pub(crate) fn insert_cells(&mut self, count: u16) {
let size = self.size;
let pos = self.pos;
let wide = pos.col < size.cols
&& self
.drawing_cell(pos)
.unwrap_or_else(|| unreachable!("cell at pos.col < size.cols always present"))
.is_wide_continuation();
let row = self.current_row_mut();
for _ in 0..count {
if wide {
row.get_mut(pos.col)
.unwrap_or_else(|| unreachable!("wide => pos.col < size.cols"))
.set_wide_continuation(false);
}
row.insert(pos.col, crate::cell::Cell::new());
if wide {
row.get_mut(pos.col)
.unwrap_or_else(|| unreachable!("wide => pos.col < size.cols"))
.set_wide_continuation(true);
}
}
row.truncate(size.cols);
}
pub(crate) fn delete_cells(&mut self, count: u16) {
let size = self.size;
let pos = self.pos;
let row = self.current_row_mut();
for _ in 0..(count.min(size.cols - pos.col)) {
row.remove(pos.col);
}
row.resize(size.cols, crate::cell::Cell::new());
}
pub(crate) fn erase_cells(&mut self, count: u16, attrs: crate::attrs::Attrs) {
let size = self.size;
let pos = self.pos;
let row = self.current_row_mut();
for col in pos.col..((pos.col.saturating_add(count)).min(size.cols)) {
row.erase(col, attrs);
}
}
pub(crate) fn insert_lines(&mut self, count: u16) {
for _ in 0..count {
self.rows.remove(usize::from(self.scroll_bottom));
self.rows.insert(usize::from(self.pos.row), self.new_row());
self.rows[usize::from(self.scroll_bottom)].wrap(false);
}
self.mark_dirty_range(self.pos.row, self.scroll_bottom);
}
pub(crate) fn delete_lines(&mut self, count: u16) {
for _ in 0..(count.min(self.size.rows - self.pos.row)) {
self.rows
.insert(usize::from(self.scroll_bottom) + 1, self.new_row());
self.rows.remove(usize::from(self.pos.row));
}
self.mark_dirty_range(self.pos.row, self.scroll_bottom);
}
pub(crate) fn scroll_up(&mut self, count: u16) {
for _ in 0..(count.min(self.size.rows - self.scroll_top)) {
self.rows
.insert(usize::from(self.scroll_bottom) + 1, self.new_row());
let removed = self.rows.remove(usize::from(self.scroll_top));
if self.scrollback_len > 0 && !self.scroll_region_active() {
self.scrollback.push_back(removed);
self.pushed_to_scrollback = self.pushed_to_scrollback.saturating_add(1);
while self.scrollback.len() > self.scrollback_len {
self.scrollback.pop_front();
}
if self.scrollback_offset > 0 {
self.scrollback_offset = self.scrollback.len().min(self.scrollback_offset + 1);
}
}
}
self.mark_dirty_range(self.scroll_top, self.scroll_bottom);
}
pub(crate) fn scroll_down(&mut self, count: u16) {
for _ in 0..count {
self.rows.remove(usize::from(self.scroll_bottom));
self.rows
.insert(usize::from(self.scroll_top), self.new_row());
self.rows[usize::from(self.scroll_bottom)].wrap(false);
}
self.mark_dirty_range(self.scroll_top, self.scroll_bottom);
}
fn mark_dirty_range(&mut self, top: u16, bottom: u16) {
let start = usize::from(top);
let end = usize::from(bottom).saturating_add(1).min(self.rows.len());
for row in &mut self.rows[start..end] {
row.mark_dirty();
}
}
fn mark_cursor_rows_dirty(&mut self, old_row: u16) {
let new_row = self.pos.row;
self.mark_dirty_range(old_row, old_row);
if new_row != old_row {
self.mark_dirty_range(new_row, new_row);
}
}
pub(crate) fn set_scroll_region(&mut self, top: u16, bottom: u16) {
let old_row = self.pos.row;
let bottom = bottom.min(self.size().rows - 1);
if top < bottom {
self.scroll_top = top;
self.scroll_bottom = bottom;
} else {
self.scroll_top = 0;
self.scroll_bottom = self.size().rows - 1;
}
self.pos.row = self.scroll_top;
self.pos.col = 0;
self.mark_cursor_rows_dirty(old_row);
}
fn in_scroll_region(&self) -> bool {
self.pos.row >= self.scroll_top && self.pos.row <= self.scroll_bottom
}
fn scroll_region_active(&self) -> bool {
self.scroll_top != 0 || self.scroll_bottom != self.size.rows - 1
}
pub(crate) fn is_origin_mode(&self) -> bool {
self.origin_mode
}
pub(crate) fn set_origin_mode(&mut self, mode: bool) {
self.origin_mode = mode;
self.set_pos(Pos { row: 0, col: 0 });
}
pub(crate) fn row_inc_clamp(&mut self, count: u16) {
let old_row = self.pos.row;
let in_scroll_region = self.in_scroll_region();
self.pos.row = self.pos.row.saturating_add(count);
self.row_clamp_bottom(in_scroll_region);
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn row_inc_scroll(&mut self, count: u16) -> u16 {
let old_row = self.pos.row;
let in_scroll_region = self.in_scroll_region();
self.pos.row = self.pos.row.saturating_add(count);
let lines = self.row_clamp_bottom(in_scroll_region);
let result = if in_scroll_region {
self.scroll_up(lines);
lines
} else {
0
};
self.mark_cursor_rows_dirty(old_row);
result
}
pub(crate) fn row_dec_clamp(&mut self, count: u16) {
let old_row = self.pos.row;
let in_scroll_region = self.in_scroll_region();
self.pos.row = self.pos.row.saturating_sub(count);
self.row_clamp_top(in_scroll_region);
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn row_dec_scroll(&mut self, count: u16) {
let old_row = self.pos.row;
let in_scroll_region = self.in_scroll_region();
let extra_lines = count.saturating_sub(self.pos.row);
self.pos.row = self.pos.row.saturating_sub(count);
let lines = self.row_clamp_top(in_scroll_region);
self.scroll_down(lines + extra_lines);
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn row_set(&mut self, i: u16) {
let old_row = self.pos.row;
self.pos.row = i;
self.row_clamp();
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn col_inc(&mut self, count: u16) {
let old_row = self.pos.row;
self.pos.col = self.pos.col.saturating_add(count);
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn col_inc_clamp(&mut self, count: u16) {
let old_row = self.pos.row;
self.pos.col = self.pos.col.saturating_add(count);
self.col_clamp();
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn col_dec(&mut self, count: u16) {
let old_row = self.pos.row;
self.pos.col = self.pos.col.saturating_sub(count);
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn col_tab(&mut self) {
let old_row = self.pos.row;
'tab: {
for c in (self.pos.col + 1)..self.size.cols {
if self.tab_stops[usize::from(c)] {
self.pos.col = c;
break 'tab;
}
}
self.pos.col = self.size.cols - 1;
}
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn col_tab_n(&mut self, mut count: u16) {
while count > 0 {
self.col_tab();
count -= 1;
}
}
pub(crate) fn col_tab_backward(&mut self, mut count: u16) {
let old_row = self.pos.row;
while count > 0 && self.pos.col > 0 {
let mut found = false;
for c in (0..self.pos.col).rev() {
if self.tab_stops[usize::from(c)] {
self.pos.col = c;
found = true;
break;
}
}
if !found {
self.pos.col = 0;
}
count -= 1;
}
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn tab_stops(&self) -> impl Iterator<Item = u16> + '_ {
self.tab_stops
.iter()
.enumerate()
.filter_map(|(i, &set)| set.then_some(i as u16))
}
pub(crate) fn set_tab_stop(&mut self) {
if (self.pos.col as usize) < self.tab_stops.len() {
self.tab_stops[usize::from(self.pos.col)] = true;
}
}
pub(crate) fn clear_tab_stop(&mut self, mode: u16) {
match mode {
0 if (self.pos.col as usize) < self.tab_stops.len() => {
self.tab_stops[usize::from(self.pos.col)] = false;
}
3 => {
for stop in &mut self.tab_stops {
*stop = false;
}
}
_ => {}
}
}
pub(crate) fn col_set(&mut self, i: u16) {
let old_row = self.pos.row;
self.pos.col = i;
self.col_clamp();
self.mark_cursor_rows_dirty(old_row);
}
pub(crate) fn col_wrap(&mut self, width: u16, wrap: bool) {
if self.pos.col > self.size.cols.saturating_sub(width) {
let prev_pos = self.pos;
self.pos.col = 0;
let scrolled = self.row_inc_scroll(1);
let Some(prev_row) = prev_pos.row.checked_sub(scrolled) else {
return;
};
let new_pos = self.pos;
self.drawing_row_mut(prev_row)
.unwrap_or_else(|| unreachable!("prev cursor row stays in bounds after scroll"))
.wrap(wrap && prev_row + 1 == new_pos.row);
}
}
fn row_clamp_top(&mut self, limit_to_scroll_region: bool) -> u16 {
if limit_to_scroll_region && self.pos.row < self.scroll_top {
let rows = self.scroll_top - self.pos.row;
self.pos.row = self.scroll_top;
rows
} else {
0
}
}
fn row_clamp_bottom(&mut self, limit_to_scroll_region: bool) -> u16 {
let bottom = if limit_to_scroll_region {
self.scroll_bottom
} else {
self.size.rows - 1
};
if self.pos.row > bottom {
let rows = self.pos.row - bottom;
self.pos.row = bottom;
rows
} else {
0
}
}
fn row_clamp(&mut self) {
if self.pos.row > self.size.rows - 1 {
self.pos.row = self.size.rows - 1;
}
}
pub(crate) fn col_clamp(&mut self) {
if self.pos.col > self.size.cols - 1 {
self.pos.col = self.size.cols - 1;
}
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct Size {
pub(crate) rows: u16,
pub(crate) cols: u16,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct Pos {
pub(crate) row: u16,
pub(crate) col: u16,
}
#[cfg(test)]
mod tests {
use super::*;
fn make_grid(rows: u16, cols: u16) -> Grid {
let mut grid = Grid::new(Size { rows, cols }, 0);
grid.allocate_rows();
grid
}
#[test]
fn initial_state() {
let grid = make_grid(24, 80);
assert_eq!(grid.pos(), Pos { row: 0, col: 0 });
assert_eq!(grid.size(), Size { rows: 24, cols: 80 });
}
#[test]
fn cursor_movement() {
let mut grid = make_grid(24, 80);
grid.col_inc_clamp(5);
assert_eq!(grid.pos().col, 5);
grid.row_inc_clamp(3);
assert_eq!(grid.pos().row, 3);
grid.col_dec(2);
assert_eq!(grid.pos().col, 3);
}
#[test]
fn cursor_clamp() {
let mut grid = make_grid(24, 80);
grid.col_inc_clamp(100);
assert_eq!(grid.pos().col, 79);
grid.row_inc_clamp(100);
assert_eq!(grid.pos().row, 23);
}
#[test]
fn save_restore_cursor() {
let mut grid = make_grid(24, 80);
grid.col_inc_clamp(10);
grid.row_inc_clamp(5);
grid.save_cursor();
grid.col_set(0);
grid.row_set(0);
assert_eq!(grid.pos(), Pos { row: 0, col: 0 });
grid.restore_cursor();
assert_eq!(grid.pos(), Pos { row: 5, col: 10 });
}
#[test]
fn scroll_up() {
let mut grid = make_grid(3, 10);
grid.drawing_cell_mut(Pos { row: 0, col: 0 })
.unwrap()
.set('A', crate::attrs::Attrs::default());
grid.scroll_up(1);
assert!(
!grid
.drawing_cell(Pos { row: 0, col: 0 })
.unwrap()
.has_contents()
);
}
#[test]
fn tab_stops_default() {
let mut grid = make_grid(24, 80);
grid.col_tab();
assert_eq!(grid.pos().col, 8);
grid.col_tab();
assert_eq!(grid.pos().col, 16);
}
#[test]
fn tab_stop_custom() {
let mut grid = make_grid(24, 80);
grid.clear_tab_stop(3); grid.col_set(5);
grid.set_tab_stop(); grid.col_set(0);
grid.col_tab();
assert_eq!(grid.pos().col, 5);
grid.col_tab(); assert_eq!(grid.pos().col, 79);
}
#[test]
fn tab_backward() {
let mut grid = make_grid(24, 80);
grid.col_set(20);
grid.col_tab_backward(1);
assert_eq!(grid.pos().col, 16);
grid.col_tab_backward(2);
assert_eq!(grid.pos().col, 0);
}
#[test]
fn tab_clear_at_cursor() {
let mut grid = make_grid(24, 80);
grid.col_set(8);
grid.clear_tab_stop(0); grid.col_set(0);
grid.col_tab();
assert_eq!(grid.pos().col, 16); }
#[test]
fn tab_n() {
let mut grid = make_grid(24, 80);
grid.col_tab_n(3);
assert_eq!(grid.pos().col, 24);
}
#[test]
fn resize() {
let mut grid = make_grid(24, 80);
grid.col_inc_clamp(79);
grid.row_inc_clamp(23);
grid.resize_simple(Size { rows: 10, cols: 40 });
assert_eq!(grid.size(), Size { rows: 10, cols: 40 });
assert!(grid.pos().row <= 9);
assert!(grid.pos().col <= 39);
}
}