use super::cell::{Cell, CellFlags, Rgb};
#[derive(Clone)]
pub struct Buffer {
cells: Vec<Cell>,
width: u16,
height: u16,
overflow: Vec<String>,
}
impl Buffer {
pub fn new(width: u16, height: u16) -> Self {
assert!(width > 0 && height > 0, "Buffer dimensions must be non-zero");
let size = (width as usize) * (height as usize);
Self {
cells: vec![Cell::EMPTY; size],
width,
height,
overflow: Vec::new(),
}
}
#[inline]
pub const fn width(&self) -> u16 {
self.width
}
#[inline]
pub const fn height(&self) -> u16 {
self.height
}
#[inline]
pub const fn len(&self) -> usize {
self.cells.len()
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.cells.is_empty()
}
#[inline]
pub fn cells(&self) -> &[Cell] {
&self.cells
}
#[inline]
pub fn cells_mut(&mut self) -> &mut [Cell] {
&mut self.cells
}
#[inline]
pub const fn index_of(&self, x: u16, y: u16) -> Option<usize> {
if x < self.width && y < self.height {
Some((y as usize) * (self.width as usize) + (x as usize))
} else {
None
}
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub const fn coords_of(&self, index: usize) -> Option<(u16, u16)> {
if index < self.cells.len() {
let x = (index % (self.width as usize)) as u16;
let y = (index / (self.width as usize)) as u16;
Some((x, y))
} else {
None
}
}
#[inline]
pub fn get(&self, x: u16, y: u16) -> Option<&Cell> {
self.index_of(x, y).map(|i| &self.cells[i])
}
#[inline]
pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> {
self.index_of(x, y).map(|i| &mut self.cells[i])
}
#[inline]
pub fn set(&mut self, x: u16, y: u16, cell: Cell) -> bool {
if let Some(idx) = self.index_of(x, y) {
self.cells[idx] = cell;
true
} else {
false
}
}
pub fn set_grapheme(&mut self, x: u16, y: u16, grapheme: &str, fg: Rgb, bg: Rgb) -> u8 {
let Some(idx) = self.index_of(x, y) else {
return 0;
};
let width = u8::try_from(unicode_width::UnicodeWidthStr::width(grapheme)).unwrap_or(1);
let cell = if let Some(mut cell) = Cell::from_grapheme(grapheme) {
cell.set_fg(fg).set_bg(bg);
cell
} else {
#[allow(clippy::cast_possible_truncation)]
let overflow_idx = self.overflow.len() as u32;
self.overflow.push(grapheme.to_string());
Cell::overflow(overflow_idx, width).with_fg(fg).with_bg(bg)
};
self.cells[idx] = cell;
if width == 2 {
if let Some(next_idx) = self.index_of(x + 1, y) {
self.cells[next_idx] = Cell::wide_continuation().with_bg(bg);
}
}
width
}
pub fn get_grapheme(&self, x: u16, y: u16) -> Option<&str> {
let cell = self.get(x, y)?;
if cell.is_wide_continuation() {
return None;
}
if cell.flags().contains(CellFlags::OVERFLOW) {
let idx = cell.overflow_index()?;
self.overflow.get(idx as usize).map(String::as_str)
} else {
cell.grapheme()
}
}
#[inline]
pub fn get_overflow(&self, index: u32) -> Option<&str> {
self.overflow.get(index as usize).map(String::as_str)
}
pub fn fill_rect(&mut self, x: u16, y: u16, width: u16, height: u16, cell: Cell) {
for row in y..(y + height).min(self.height) {
for col in x..(x + width).min(self.width) {
if let Some(idx) = self.index_of(col, row) {
self.cells[idx] = cell;
}
}
}
}
pub fn clear(&mut self) {
self.cells.fill(Cell::EMPTY);
self.overflow.clear();
}
pub fn clear_rect(&mut self, x: u16, y: u16, width: u16, height: u16) {
self.fill_rect(x, y, width, height, Cell::EMPTY);
}
pub fn resize(&mut self, new_width: u16, new_height: u16) {
if new_width == self.width && new_height == self.height {
return;
}
let new_size = (new_width as usize) * (new_height as usize);
let mut new_cells = vec![Cell::EMPTY; new_size];
let copy_width = self.width.min(new_width) as usize;
let copy_height = self.height.min(new_height) as usize;
for y in 0..copy_height {
let old_start = y * (self.width as usize);
let new_start = y * (new_width as usize);
new_cells[new_start..new_start + copy_width]
.copy_from_slice(&self.cells[old_start..old_start + copy_width]);
}
self.cells = new_cells;
self.width = new_width;
self.height = new_height;
}
pub fn copy_from(&mut self, other: &Self) {
debug_assert_eq!(self.width, other.width);
debug_assert_eq!(self.height, other.height);
self.cells.copy_from_slice(&other.cells);
self.overflow.clone_from(&other.overflow);
}
pub const fn swap(&mut self, other: &mut Self) {
std::mem::swap(&mut self.cells, &mut other.cells);
std::mem::swap(&mut self.width, &mut other.width);
std::mem::swap(&mut self.height, &mut other.height);
std::mem::swap(&mut self.overflow, &mut other.overflow);
}
pub fn rows(&self) -> impl Iterator<Item = &[Cell]> {
self.cells.chunks(self.width as usize)
}
pub fn rows_mut(&mut self) -> impl Iterator<Item = &mut [Cell]> {
self.cells.chunks_mut(self.width as usize)
}
pub fn memory_usage(&self) -> usize {
let cells_size = self.cells.len() * std::mem::size_of::<Cell>();
let overflow_size: usize = self.overflow.iter().map(|s| s.len() + 32).sum();
cells_size + overflow_size + std::mem::size_of::<Self>()
}
}
impl std::fmt::Debug for Buffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Buffer")
.field("width", &self.width)
.field("height", &self.height)
.field("overflow_count", &self.overflow.len())
.field("memory_bytes", &self.memory_usage())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_buffer_new() {
let buffer = Buffer::new(80, 24);
assert_eq!(buffer.width(), 80);
assert_eq!(buffer.height(), 24);
assert_eq!(buffer.len(), 80 * 24);
}
#[test]
#[should_panic]
fn test_buffer_zero_width() {
Buffer::new(0, 24);
}
#[test]
fn test_buffer_get_set() {
let mut buffer = Buffer::new(80, 24);
let cell = Cell::new('X');
assert!(buffer.set(5, 10, cell));
assert_eq!(buffer.get(5, 10).unwrap().grapheme(), Some("X"));
}
#[test]
fn test_buffer_bounds() {
let buffer = Buffer::new(80, 24);
assert!(buffer.get(79, 23).is_some());
assert!(buffer.get(80, 23).is_none());
assert!(buffer.get(79, 24).is_none());
}
#[test]
fn test_buffer_index_coords() {
let buffer = Buffer::new(80, 24);
assert_eq!(buffer.index_of(5, 10), Some(10 * 80 + 5));
assert_eq!(buffer.coords_of(10 * 80 + 5), Some((5, 10)));
}
#[test]
fn test_buffer_set_grapheme() {
let mut buffer = Buffer::new(80, 24);
let width = buffer.set_grapheme(0, 0, "A", Rgb::WHITE, Rgb::BLACK);
assert_eq!(width, 1);
assert_eq!(buffer.get_grapheme(0, 0), Some("A"));
let width = buffer.set_grapheme(5, 0, "日", Rgb::WHITE, Rgb::BLACK);
assert_eq!(width, 2);
assert_eq!(buffer.get_grapheme(5, 0), Some("日"));
assert!(buffer.get(6, 0).unwrap().is_wide_continuation());
}
#[test]
fn test_buffer_overflow() {
let mut buffer = Buffer::new(80, 24);
let emoji = "👨👩👧👦";
let width = buffer.set_grapheme(0, 0, emoji, Rgb::WHITE, Rgb::BLACK);
assert!(width > 0);
assert!(buffer.get(0, 0).unwrap().is_overflow());
assert_eq!(buffer.get_grapheme(0, 0), Some(emoji));
}
#[test]
fn test_buffer_fill_rect() {
let mut buffer = Buffer::new(80, 24);
let cell = Cell::new('X');
buffer.fill_rect(10, 5, 3, 2, cell);
assert_eq!(buffer.get(10, 5).unwrap().grapheme(), Some("X"));
assert_eq!(buffer.get(11, 5).unwrap().grapheme(), Some("X"));
assert_eq!(buffer.get(12, 5).unwrap().grapheme(), Some("X"));
assert_eq!(buffer.get(10, 6).unwrap().grapheme(), Some("X"));
assert_eq!(buffer.get(9, 5).unwrap().grapheme(), Some(" ")); }
#[test]
fn test_buffer_clear() {
let mut buffer = Buffer::new(80, 24);
buffer.set(5, 5, Cell::new('X'));
buffer.clear();
assert_eq!(buffer.get(5, 5), Some(&Cell::EMPTY));
}
#[test]
fn test_buffer_resize() {
let mut buffer = Buffer::new(80, 24);
buffer.set(5, 5, Cell::new('X'));
buffer.resize(100, 30);
assert_eq!(buffer.width(), 100);
assert_eq!(buffer.height(), 30);
assert_eq!(buffer.get(5, 5).unwrap().grapheme(), Some("X"));
buffer.resize(10, 10);
assert_eq!(buffer.get(5, 5).unwrap().grapheme(), Some("X")); assert!(buffer.get(15, 15).is_none()); }
#[test]
fn test_buffer_swap() {
let mut a = Buffer::new(80, 24);
let mut b = Buffer::new(80, 24);
a.set(0, 0, Cell::new('A'));
b.set(0, 0, Cell::new('B'));
a.swap(&mut b);
assert_eq!(a.get(0, 0).unwrap().grapheme(), Some("B"));
assert_eq!(b.get(0, 0).unwrap().grapheme(), Some("A"));
}
#[test]
fn test_buffer_memory_usage() {
let buffer = Buffer::new(200, 50);
let usage = buffer.memory_usage();
assert!(usage >= 160_000);
assert!(usage < 200_000); }
}