use crate::{ColorPair, Result};
use std::cmp::{max, min};
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Cell {
pub(crate) ch: char,
pub(crate) colors: Option<ColorPair>,
pub(crate) modified: bool,
}
#[derive(Debug)]
pub(crate) struct BufferChange {
pub(crate) y: u16,
pub(crate) x: u16,
pub(crate) text: String,
pub(crate) colors: Option<ColorPair>,
}
impl Cell {
#[allow(dead_code)] pub fn new(ch: char, colors: Option<ColorPair>) -> Self {
Self {
ch,
colors,
modified: true, }
}
pub fn empty() -> Self {
Self {
ch: ' ',
colors: None,
modified: false,
}
}
}
pub struct Buffer {
width: u16,
height: u16,
current: Vec<Cell>, previous: Vec<Cell>, dirty_min_x: Option<u16>,
dirty_max_x: Option<u16>,
dirty_min_y: Option<u16>,
dirty_max_y: Option<u16>,
}
impl Buffer {
pub(crate) fn new(width: u16, height: u16) -> Self {
let size = width as usize * height as usize;
let current = vec![Cell::empty(); size];
let previous = vec![Cell::empty(); size];
Self {
width,
height,
current,
previous,
dirty_min_x: None,
dirty_max_x: None,
dirty_min_y: None,
dirty_max_y: None,
}
}
fn coords_to_index(&self, x: u16, y: u16) -> usize {
(y as usize * self.width as usize) + x as usize
}
pub(crate) fn write_char(
&mut self,
y: u16,
x: u16,
ch: char,
colors: Option<ColorPair>,
) -> Result<()> {
if x >= self.width || y >= self.height {
return Err(crate::Error::BufferSizeError {
x,
y,
width: self.width,
height: self.height,
});
}
let idx = self.coords_to_index(x, y);
let cell = &mut self.current[idx];
if cell.ch != ch || cell.colors != colors {
cell.ch = ch;
cell.colors = colors;
cell.modified = true;
match self.dirty_min_x {
None => self.dirty_min_x = Some(x),
Some(min_x) => self.dirty_min_x = Some(min(min_x, x)),
}
match self.dirty_max_x {
None => self.dirty_max_x = Some(x),
Some(max_x) => self.dirty_max_x = Some(max(max_x, x)),
}
match self.dirty_min_y {
None => self.dirty_min_y = Some(y),
Some(min_y) => self.dirty_min_y = Some(min(min_y, y)),
}
match self.dirty_max_y {
None => self.dirty_max_y = Some(y),
Some(max_y) => self.dirty_max_y = Some(max(max_y, y)),
}
}
Ok(())
}
pub(crate) fn write_str(
&mut self,
y: u16,
x: u16,
s: &str,
colors: Option<ColorPair>,
) -> Result<()> {
if x >= self.width || y >= self.height {
return Err(crate::Error::BufferSizeError {
x,
y,
width: self.width,
height: self.height,
});
}
for (i, ch) in s.chars().enumerate() {
let x_pos = x + i as u16;
if x_pos >= self.width {
break; }
self.write_char(y, x_pos, ch, colors)?;
}
Ok(())
}
pub(crate) fn clear(&mut self) {
for cell in &mut self.current {
if cell.ch != ' ' || cell.colors.is_some() {
*cell = Cell::empty();
cell.modified = true;
}
}
self.dirty_min_x = Some(0);
self.dirty_max_x = Some(self.width - 1);
self.dirty_min_y = Some(0);
self.dirty_max_y = Some(self.height - 1);
}
pub(crate) fn clear_line(&mut self, y: u16) -> Result<()> {
if y >= self.height {
return Err(crate::Error::LineOutOfBoundsError {
y,
height: self.height,
});
}
let start_idx = self.coords_to_index(0, y);
let end_idx = start_idx + self.width as usize;
for cell in &mut self.current[start_idx..end_idx] {
if cell.ch != ' ' || cell.colors.is_some() {
*cell = Cell::empty();
cell.modified = true;
}
}
match self.dirty_min_x {
None => self.dirty_min_x = Some(0),
Some(min_x) => self.dirty_min_x = Some(min(min_x, 0)),
}
match self.dirty_max_x {
None => self.dirty_max_x = Some(self.width - 1),
Some(max_x) => self.dirty_max_x = Some(max(max_x, self.width - 1)),
}
match self.dirty_min_y {
None => self.dirty_min_y = Some(y),
Some(min_y) => self.dirty_min_y = Some(min(min_y, y)),
}
match self.dirty_max_y {
None => self.dirty_max_y = Some(y),
Some(max_y) => self.dirty_max_y = Some(max(max_y, y)),
}
Ok(())
}
pub(crate) fn process_changes(&mut self) -> Vec<BufferChange> {
let mut changes = Vec::new();
if let (Some(min_x), Some(max_x), Some(min_y), Some(max_y)) = (
self.dirty_min_x,
self.dirty_max_x,
self.dirty_min_y,
self.dirty_max_y,
) {
for y in min_y..=max_y {
let mut x = min_x;
while x <= max_x {
let idx = self.coords_to_index(x, y);
let current = &self.current[idx];
let previous = &self.previous[idx];
if !current.modified && current == previous {
x += 1;
continue;
}
let mut run_length = 1;
let mut run_str = String::with_capacity((max_x - min_x + 1) as usize);
run_str.push(current.ch);
while x + run_length <= max_x {
let next_idx = self.coords_to_index(x + run_length, y);
let next_cell = &self.current[next_idx];
let next_prev = &self.previous[next_idx];
if next_cell.colors != current.colors
|| (!next_cell.modified && next_cell == next_prev)
{
break;
}
run_str.push(next_cell.ch);
run_length += 1;
}
changes.push(BufferChange {
y,
x,
text: run_str,
colors: current.colors,
});
x += run_length;
}
}
}
std::mem::swap(&mut self.current, &mut self.previous);
for cell in &mut self.current {
cell.modified = false;
}
self.dirty_min_x = None;
self.dirty_max_x = None;
self.dirty_min_y = None;
self.dirty_max_y = None;
changes
}
#[allow(dead_code)]
pub(crate) fn get_stats(&self) -> BufferStats {
let dirty_rows = match (self.dirty_min_y, self.dirty_max_y) {
(Some(min), Some(max)) => (max - min + 1) as usize,
_ => 0,
};
let dirty_cols = match (self.dirty_min_x, self.dirty_max_x) {
(Some(min), Some(max)) => (max - min + 1) as usize,
_ => 0,
};
let modified_cells = self.current.iter().filter(|c| c.modified).count();
BufferStats {
width: self.width,
height: self.height,
dirty_rows,
dirty_cols,
modified_cells,
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct BufferStats {
pub width: u16,
pub height: u16,
pub dirty_rows: usize,
pub dirty_cols: usize,
pub modified_cells: usize,
}