172 lines
3.9 KiB
Rust
172 lines
3.9 KiB
Rust
use crossterm::style::ContentStyle;
|
|
use unicode_segmentation::UnicodeSegmentation;
|
|
|
|
use crate::widthdb::WidthDB;
|
|
|
|
#[derive(Debug, Default, 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, PartialEq, Eq)]
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct Buffer {
|
|
size: Size,
|
|
data: Vec<Cell>,
|
|
}
|
|
|
|
impl Buffer {
|
|
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(crate) fn at(&self, x: u16, y: u16) -> &Cell {
|
|
assert!(x < self.size.width);
|
|
assert!(y < self.size.height);
|
|
&self.data[self.index(x, y)]
|
|
}
|
|
|
|
pub fn size(&self) -> Size {
|
|
self.size
|
|
}
|
|
|
|
/// Resize the buffer and reset its contents.
|
|
///
|
|
/// The buffer's contents are reset even if the buffer is already the
|
|
/// correct size.
|
|
pub fn resize(&mut self, size: Size) {
|
|
if size == self.size() {
|
|
self.data.fill_with(Cell::empty);
|
|
} else {
|
|
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 the contents of the buffer.
|
|
///
|
|
/// `buf.reset()` is equivalent to `buf.resize(buf.size())`.
|
|
pub fn reset(&mut self) {
|
|
self.data.fill_with(Cell::empty);
|
|
}
|
|
|
|
pub fn write(
|
|
&mut self,
|
|
widthdb: &mut WidthDB,
|
|
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 = widthdb.width(grapheme);
|
|
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> 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.buffer.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))
|
|
}
|
|
}
|