use std::{
fmt::Display,
io::{Write, stdout},
ops::{Index, IndexMut},
};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{
enums::{Color, Cursor, Modifier},
geometry::{Rect, Vec2},
style::Style,
};
use super::cell::Cell;
#[derive(Debug, Clone)]
pub struct Buffer {
rect: Rect,
content: Vec<Cell>,
}
impl Buffer {
#[must_use]
pub fn empty<R>(rect: R) -> Self
where
R: Into<Rect>,
{
let rect = rect.into();
let area = rect.area();
Self {
rect,
content: vec![Cell::default(); area],
}
}
#[must_use]
pub fn filled<R>(rect: R, cell: Cell) -> Self
where
R: Into<Rect>,
{
let rect = rect.into();
let area = rect.area();
Self {
rect,
content: vec![cell; area],
}
}
pub fn render(&self) {
let mut id = 0;
let mut style = (Color::Default, Color::Default, Modifier::empty());
for y in 0..self.height() {
print!("{}", Cursor::Pos(self.x(), self.y() + y));
for _ in 0..self.width() {
let child = &self.content[id];
style = Self::render_cell(child, style);
id += 1;
}
}
print!("\x1b[0m");
_ = stdout().flush();
}
pub fn render_diff(&self, diff: &Buffer) {
if self.rect() != diff.rect() {
self.render();
return;
}
let mut id = 0;
let mut style = (Color::Default, Color::Default, Modifier::empty());
for y in 0..self.height() {
let mut prev = false;
for x in 0..self.width() {
let child = &self.content[id];
let dchild = &diff.content[id];
id += 1;
if child == dchild {
prev = false;
continue;
}
if !prev {
print!("{}", Cursor::Pos(self.x() + x, self.y() + y))
}
style = Self::render_cell(child, style);
prev = true;
}
}
print!("\x1b[0m");
_ = stdout().flush();
}
#[must_use]
pub fn subset(&self, rect: Rect) -> Buffer {
let mut buffer = Buffer::empty(rect);
for pos in rect.into_iter() {
buffer.set(self[self.index_of(&pos)].clone(), &pos);
}
buffer
}
pub fn merge(&mut self, buffer: Buffer) {
let rect = self.rect().union(buffer.rect());
let mut merged = Buffer::empty(rect);
for (i, pos) in self.rect().into_iter().enumerate() {
merged.set(self.content[i].clone(), &pos);
}
for (i, pos) in buffer.rect().into_iter().enumerate() {
merged.set(buffer.content[i].clone(), &pos);
}
self.rect = merged.rect;
self.content = merged.content;
}
pub fn move_to(&mut self, pos: Vec2) {
self.rect.move_to(pos);
}
pub fn cell(&self, pos: &Vec2) -> Option<&Cell> {
let id = self.index_of(pos);
self.content.get(id)
}
pub fn cell_mut(&mut self, pos: &Vec2) -> Option<&mut Cell> {
let id = self.index_of(pos);
self.content.get_mut(id)
}
pub fn set(&mut self, cell: Cell, pos: &Vec2) {
let id = self.index_of(pos);
self.content[id] = cell;
}
pub fn set_str<T>(&mut self, str: T, pos: &Vec2)
where
T: AsRef<str>,
{
let mut id = self.index_of(pos);
let mut left = self.content.len().saturating_sub(id);
let graphemes = UnicodeSegmentation::graphemes(str.as_ref(), true)
.map(|t| (t, t.width()))
.filter(|(_, w)| *w > 0)
.map_while(|(t, w)| {
left = left.checked_sub(w)?;
Some((t, w))
});
for (grapheme, width) in graphemes {
self.content[id].val(grapheme);
let next = id + width;
id += 1;
while id < next {
self.content[id].val("\0");
id += 1;
}
}
}
pub fn set_str_styled<T, S>(&mut self, str: T, pos: &Vec2, style: S)
where
T: AsRef<str>,
S: Into<Style>,
{
let mut id = self.index_of(pos);
let mut left = self.content.len().saturating_sub(id);
let style = style.into();
let graphemes = UnicodeSegmentation::graphemes(str.as_ref(), true)
.map(|t| (t, t.width()))
.filter(|(_, w)| *w > 0)
.map_while(|(t, w)| {
left = left.checked_sub(w)?;
Some((t, w))
});
for (grapheme, width) in graphemes {
self.content[id].val(grapheme).style(style);
let next = id + width;
id += 1;
while id < next {
self.content[id].val("\0");
id += 1;
}
}
}
pub fn set_val(&mut self, val: &str, pos: &Vec2) {
let id = self.index_of(pos);
self.content[id].val(val);
}
pub fn set_char(&mut self, c: char, pos: &Vec2) {
let id = self.index_of(pos);
self.content[id].char(c);
}
pub fn set_style(&mut self, style: Style, pos: &Vec2) {
let id = self.index_of(pos);
self.content[id].style(style);
}
pub fn set_fg(&mut self, fg: Color, pos: &Vec2) {
let id = self.index_of(pos);
self.content[id].fg(fg);
}
pub fn set_bg(&mut self, bg: Color, pos: &Vec2) {
let id = self.index_of(pos);
self.content[id].bg(bg);
}
pub fn set_modifier(&mut self, modifier: Modifier, pos: &Vec2) {
let id = self.index_of(pos);
self.content[id].modifier(modifier);
}
pub fn set_area_style(&mut self, style: Style, area: Rect) {
for pos in area {
if let Some(id) = self.index_of_opt(&pos) {
self.content[id].style(style);
}
}
}
pub fn rect(&self) -> &Rect {
&self.rect
}
pub fn pos(&self) -> &Vec2 {
self.rect.pos()
}
pub fn x(&self) -> usize {
self.rect.x()
}
pub fn left(&self) -> usize {
self.rect.left()
}
pub fn right(&self) -> usize {
self.rect.right()
}
pub fn y(&self) -> usize {
self.rect.y()
}
pub fn top(&self) -> usize {
self.rect.top()
}
pub fn bottom(&self) -> usize {
self.rect.bottom()
}
pub fn size(&self) -> &Vec2 {
self.rect.size()
}
pub fn width(&self) -> usize {
self.rect.width()
}
pub fn height(&self) -> usize {
self.rect.height()
}
pub fn area(&self) -> usize {
self.rect.area()
}
pub fn content(&self) -> &[Cell] {
&self.content
}
pub fn index_of(&self, pos: &Vec2) -> usize {
(pos.x - self.x()) + (pos.y - self.y()) * self.rect.width()
}
pub fn index_of_opt(&self, pos: &Vec2) -> Option<usize> {
if !self.rect.contains_pos(pos) {
return None;
}
Some((pos.x - self.x()) + (pos.y - self.y()) * self.rect.width())
}
pub fn pos_of(&self, id: usize) -> Vec2 {
let (x, y) = (id % self.width(), id / self.width());
Vec2::new(x + self.x(), y + self.y())
}
pub fn pos_of_opt(&self, id: usize) -> Option<Vec2> {
if id >= self.content.len() {
return None;
}
let (x, y) = (id % self.width(), id / self.width());
Some(Vec2::new(x + self.x(), y + self.y()))
}
}
impl Buffer {
fn render_cell(
cell: &Cell,
mut style: (Color, Color, Modifier),
) -> (Color, Color, Modifier) {
if cell.val == "\0" {
return style;
}
if cell.modifier != style.2 {
if !style.2.is_empty() {
print!("\x1b[0m");
}
print!("{}", cell.modifier);
style = (Color::Default, Color::Default, cell.modifier);
}
if cell.fg != style.0 {
style.0 = cell.fg;
print!("{}", cell.fg.to_fg());
}
if cell.bg != style.1 {
style.1 = cell.bg;
print!("{}", cell.bg.to_bg());
}
print!("{}", cell.val);
style
}
fn write_cell(
f: &mut std::fmt::Formatter<'_>,
cell: &Cell,
style: &mut (Color, Color, Modifier),
) -> std::fmt::Result {
if cell.modifier != style.2 {
if !style.2.is_empty() {
write!(f, "\x1b[0m")?;
}
write!(f, "{}", cell.modifier)?;
*style = (Color::Default, Color::Default, cell.modifier);
}
if cell.fg != style.0 {
style.0 = cell.fg;
write!(f, "{}", cell.fg.to_fg())?;
}
if cell.bg != style.1 {
style.1 = cell.bg;
write!(f, "{}", cell.bg.to_bg())?;
}
write!(f, "{}", cell.val)
}
}
impl Display for Buffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut id = 0;
let mut style = (Color::Default, Color::Default, Modifier::empty());
for y in 0..self.height() {
if y != 0 {
writeln!(f)?;
}
for _x in 0..self.width() {
let child = &self.content[id];
Self::write_cell(f, child, &mut style)?;
id += 1;
}
}
write!(f, "\x1b[0m")
}
}
impl Index<usize> for Buffer {
type Output = Cell;
fn index(&self, index: usize) -> &Self::Output {
self.content.get(index).unwrap_or_else(|| {
panic!("index {index} is outside of the buffer")
})
}
}
impl IndexMut<usize> for Buffer {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
self.content.get_mut(index).unwrap_or_else(|| {
panic!("index {index} is outside of the buffer")
})
}
}
impl<P> Index<P> for Buffer
where
P: Into<Vec2>,
{
type Output = Cell;
fn index(&self, index: P) -> &Self::Output {
let pos = index.into();
self.cell(&pos).unwrap_or_else(|| {
panic!("position {pos} is outside of the buffer")
})
}
}
impl<P> IndexMut<P> for Buffer
where
P: Into<Vec2>,
{
fn index_mut(&mut self, index: P) -> &mut Self::Output {
let pos = index.into();
self.cell_mut(&pos).unwrap_or_else(|| {
panic!("position {pos} is outside of the buffer")
})
}
}