Create simple terminal and buffer abstractions

This commit is contained in:
Joscha 2022-05-21 19:59:07 +02:00
parent 704bb67c7b
commit bbaea3b5bf
5 changed files with 290 additions and 8 deletions

164
src/buffer.rs Normal file
View file

@ -0,0 +1,164 @@
use crossterm::style::ContentStyle;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Size {
pub width: u16,
pub height: u16,
}
impl Size {
pub const ZERO: Self = Self {
width: 0,
height: 0,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pos {
pub x: i32,
pub y: i32,
}
impl Pos {
pub const ZERO: Self = Self { x: 0, y: 0 };
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone)]
pub struct Cell {
pub content: Box<str>,
pub style: ContentStyle,
pub width: u8,
pub offset: u8,
}
impl Cell {
pub fn empty() -> Self {
Self {
content: " ".to_string().into_boxed_str(),
style: ContentStyle::default(),
width: 1,
offset: 0,
}
}
}
pub struct Buffer {
size: Size,
data: Vec<Cell>,
}
impl Buffer {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
size: Size::ZERO,
data: vec![],
}
}
fn index(&self, x: u16, y: u16) -> usize {
assert!(x < self.size.width);
assert!(y < self.size.height);
let x: usize = x.into();
let y: usize = y.into();
let width: usize = self.size.width.into();
y * width + x
}
pub fn size(&self) -> Size {
self.size
}
pub fn resize(&mut self, size: Size) {
let width: usize = size.width.into();
let height: usize = size.height.into();
let len = width * height;
self.size = size;
self.data.clear();
self.data.resize_with(len, Cell::empty);
}
/// Reset all cells of the buffer to be empty and have no styling.
pub fn reset(&mut self) {
self.resize(self.size);
}
pub fn write(&mut self, mut pos: Pos, content: &str, style: ContentStyle) {
if pos.y < 0 || pos.y >= self.size.height as i32 {
return;
}
for grapheme in content.graphemes(true) {
let width = grapheme.width().max(1) as u8; // TODO Use actual width
if pos.x >= 0 && pos.x + width as i32 <= self.size.width as i32 {
// Grapheme fits on buffer in its entirety
let grapheme = grapheme.to_string().into_boxed_str();
for offset in 0..width {
let i = self.index(pos.x as u16 + offset as u16, pos.y as u16);
self.data[i] = Cell {
content: grapheme.clone(),
style,
width,
offset,
};
}
}
pos.x += width as i32;
}
}
pub fn cells(&self) -> Cells<'_> {
Cells {
buffer: self,
x: 0,
y: 0,
}
}
}
pub struct Cells<'a> {
buffer: &'a Buffer,
x: u16,
y: u16,
}
impl<'a> Cells<'a> {
fn at(&self, x: u16, y: u16) -> &'a Cell {
assert!(x < self.buffer.size.width);
assert!(y < self.buffer.size.height);
&self.buffer.data[self.buffer.index(x, y)]
}
}
impl<'a> Iterator for Cells<'a> {
type Item = (u16, u16, &'a Cell);
fn next(&mut self) -> Option<Self::Item> {
if self.y >= self.buffer.size.height {
return None;
}
let (x, y) = (self.x, self.y);
let cell = self.at(self.x, self.y);
assert!(cell.offset == 0);
self.x += cell.width as u16;
if self.x >= self.buffer.size.width {
self.x = 0;
self.y += 1;
}
Some((x, y, cell))
}
}