use crate::core::color::Color;
use crate::core::rect::Rect;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Cell {
pub ch: char,
pub fg: Color,
pub bg: Option<Color>,
pub bold: bool,
pub italic: bool,
pub underlined: bool,
}
impl Cell {
pub fn new(ch: char, fg: Color, bg: Option<Color>) -> Self {
Self {
ch,
fg,
bg,
bold: false,
italic: false,
underlined: false,
}
}
pub fn with_bold(mut self, bold: bool) -> Self {
self.bold = bold;
self
}
pub fn with_italic(mut self, italic: bool) -> Self {
self.italic = italic;
self
}
pub fn with_underlined(mut self, underlined: bool) -> Self {
self.underlined = underlined;
self
}
pub fn empty() -> Self {
Self {
ch: ' ',
fg: Color::BLACK,
bg: None,
bold: false,
italic: false,
underlined: false,
}
}
}
#[derive(Debug, Clone)]
pub struct Buffer {
pub width: usize,
pub height: usize,
cells: Vec<Cell>,
background: Option<Color>,
}
impl Buffer {
pub fn new(width: usize, height: usize) -> Self {
let cells = vec![Cell::empty(); width * height];
Self {
width,
height,
cells,
background: None,
}
}
pub fn with_background(width: usize, height: usize, bg: Option<Color>) -> Self {
let cell = match bg {
Some(c) => Cell {
ch: ' ',
fg: c,
bg: Some(c),
bold: false,
italic: false,
underlined: false,
},
None => Cell::empty(),
};
let cells = vec![cell; width * height];
Self {
width,
height,
cells,
background: bg,
}
}
pub fn index(&self, x: usize, y: usize) -> usize {
y * self.width + x
}
pub fn get(&self, x: usize, y: usize) -> Option<&Cell> {
if x < self.width && y < self.height {
Some(&self.cells[self.index(x, y)])
} else {
None
}
}
pub fn set(&mut self, x: usize, y: usize, cell: Cell) {
if x < self.width && y < self.height {
let idx = self.index(x, y);
self.cells[idx] = cell;
}
}
pub fn set_str(&mut self, x: usize, y: usize, s: &str, fg: Color, bg: Option<Color>) {
for (i, ch) in s.chars().enumerate() {
let cell_x = x + i;
if cell_x < self.width {
self.set(
cell_x,
y,
Cell {
ch,
fg,
bg,
bold: false,
italic: false,
underlined: false,
},
);
}
}
}
pub fn set_str_bold(&mut self, x: usize, y: usize, s: &str, fg: Color, bg: Option<Color>) {
for (i, ch) in s.chars().enumerate() {
let cell_x = x + i;
if cell_x < self.width {
self.set(
cell_x,
y,
Cell {
ch,
fg,
bg,
bold: true,
italic: false,
underlined: false,
},
);
}
}
}
pub fn fill(&mut self, area: Rect, ch: char, fg: Color, bg: Option<Color>) {
for y in area.y as usize..area.bottom() as usize {
for x in area.x as usize..area.right() as usize {
self.set(
x,
y,
Cell {
ch,
fg,
bg,
bold: false,
italic: false,
underlined: false,
},
);
}
}
}
pub fn clear(&mut self, area: Rect) {
let bg = self.background;
for y in area.y as usize..area.bottom() as usize {
for x in area.x as usize..area.right() as usize {
self.set(
x,
y,
Cell {
ch: ' ',
fg: Color::BLACK,
bg,
bold: false,
italic: false,
underlined: false,
},
);
}
}
}
pub fn to_ansi_string(&self) -> String {
let mut out = String::with_capacity(self.width * self.height * 8);
let mut last_fg = Color::BLACK;
let mut last_bg: Option<Color> = None;
let mut last_bold = false;
let mut last_italic = false;
let mut last_underlined = false;
for y in 0..self.height {
out.push_str(&format!("\x1b[{};1H", y + 1));
for x in 0..self.width {
let cell = &self.cells[y * self.width + x];
if cell.fg != last_fg {
out.push_str(&cell.fg.to_ansi_fg());
last_fg = cell.fg;
}
if cell.bg != last_bg {
match cell.bg {
Some(c) => out.push_str(&c.to_ansi_bg()),
None => out.push_str("\x1b[49m"),
}
last_bg = cell.bg;
}
if cell.bold != last_bold {
if cell.bold {
out.push_str("\x1b[1m");
} else {
out.push_str("\x1b[22m");
}
last_bold = cell.bold;
}
if cell.italic != last_italic {
if cell.italic {
out.push_str("\x1b[3m");
} else {
out.push_str("\x1b[23m");
}
last_italic = cell.italic;
}
if cell.underlined != last_underlined {
if cell.underlined {
out.push_str("\x1b[4m");
} else {
out.push_str("\x1b[24m");
}
last_underlined = cell.underlined;
}
out.push(cell.ch);
}
}
out.push_str("\x1b[0m");
out
}
pub fn to_plain_string(&self) -> String {
let mut out = String::new();
for y in 0..self.height {
for x in 0..self.width {
let cell = &self.cells[y * self.width + x];
out.push(cell.ch);
}
if y < self.height - 1 {
out.push('\n');
}
}
out
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn cells(&self) -> &[Cell] {
&self.cells
}
pub fn cells_mut(&mut self) -> &mut [Cell] {
&mut self.cells
}
pub fn merge_from(&mut self, other: &Buffer, x: usize, y: usize) {
for oy in 0..other.height {
for ox in 0..other.width {
let tx = x + ox;
let ty = y + oy;
if tx < self.width && ty < self.height {
if let Some(cell) = other.get(ox, oy) {
if cell.ch != ' ' || cell.bg.is_some() {
self.set(tx, ty, *cell);
}
}
}
}
}
}
pub fn blend_cell(&mut self, x: usize, y: usize, cell: Cell) {
if x < self.width && y < self.height {
if let Some(existing) = self.get(x, y) {
if cell.ch != ' ' {
self.set(x, y, cell);
} else if cell.bg.is_some() {
let mut blended = *existing;
blended.bg = cell.bg;
self.set(x, y, blended);
}
}
}
}
pub fn resize(&mut self, width: usize, height: usize) {
let mut new_cells = vec![Cell::empty(); width * height];
for y in 0..self.height.min(height) {
for x in 0..self.width.min(width) {
let idx = y * self.width + x;
new_cells[y * width + x] = self.cells[idx];
}
}
self.cells = new_cells;
self.width = width;
self.height = height;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_buffer_creation() {
let buf = Buffer::new(10, 5);
assert_eq!(buf.width(), 10);
assert_eq!(buf.height(), 5);
}
#[test]
fn test_buffer_set_get() {
let mut buf = Buffer::new(10, 10);
let cell = Cell::new('A', Color::WHITE, Some(Color::BLACK));
buf.set(5, 3, cell);
assert_eq!(buf.get(5, 3).unwrap().ch, 'A');
}
#[test]
fn test_buffer_fill() {
let mut buf = Buffer::new(10, 10);
let area = Rect::new(2, 2, 4, 3);
buf.fill(area, 'X', Color::GREEN, Some(Color::BLACK));
assert_eq!(buf.get(2, 2).unwrap().ch, 'X');
assert_eq!(buf.get(5, 4).unwrap().ch, 'X');
assert_eq!(buf.get(1, 1).unwrap().ch, ' ');
}
#[test]
fn test_buffer_to_plain_string() {
let mut buf = Buffer::new(3, 2);
buf.set_str(0, 0, "Hi", Color::WHITE, None);
buf.set(2, 0, Cell::new('!', Color::WHITE, None));
assert_eq!(buf.to_plain_string(), "Hi!\n ");
}
}