use std::{
cmp::Ordering,
collections::BTreeMap,
fmt::Debug,
io::{self, Write},
};
use crossterm::{
cursor,
style::{Color, Print, PrintStyledContent, Stylize},
terminal, QueueableCommand,
};
use crate::tui_settings::TileTexture;
use super::{TermCell, TerminalBuffer};
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug, Default)]
pub struct SparseTerminalDoubleBuffer {
prev_buf: BTreeMap<(u16, u16), TermCell>,
next_buf: BTreeMap<(u16, u16), TermCell>,
x_vp: u16,
y_vp: u16,
w_vp: u16,
h_vp: u16,
}
impl TerminalBuffer for SparseTerminalDoubleBuffer {
fn offset_and_area(&self) -> ((u16, u16), (u16, u16)) {
((self.x_vp, self.y_vp), (self.w_vp, self.h_vp))
}
fn reset_with_offset_and_area(&mut self, (x, y): (u16, u16), (w, h): (u16, u16)) {
self.prev_buf.clear();
self.next_buf.clear();
self.x_vp = x;
self.y_vp = y;
self.w_vp = w;
self.h_vp = h;
}
fn write_char(&mut self, x: u16, y: u16, cell: TermCell) {
if x < self.w_vp && y < self.h_vp {
self.next_buf.insert((x, y), cell);
}
}
fn write_tile(&mut self, x: u16, y: u16, tile: TileTexture, fg: Color) {
if y >= self.h_vp {
return;
}
let [ch0, ch1] = tile.0;
if x >= self.w_vp {
return;
}
self.next_buf.insert((x, y), TermCell { ch: ch0, fg });
if x + 1 >= self.w_vp {
return;
}
self.next_buf.insert((x + 1, y), TermCell { ch: ch1, fg });
}
fn write_str(&mut self, x: u16, y: u16, str: &str, fg: Color) {
if y >= self.h_vp {
return;
}
for (dx, ch) in str.chars().enumerate() {
if x + dx as u16 >= self.w_vp {
return;
}
self.next_buf
.insert((x + dx as u16, y), TermCell { ch, fg });
}
}
fn flush(&mut self, term: &mut impl Write) -> io::Result<()> {
term.queue(terminal::BeginSynchronizedUpdate)?;
let mut old_buffer = self.prev_buf.iter();
let mut new_buffer = self.next_buf.iter();
let mut old_pos_cell = old_buffer.next();
let mut new_pos_cell = new_buffer.next();
loop {
match (old_pos_cell, new_pos_cell) {
(None, None) => break,
#[rustfmt::skip]
(Some((old_x_y, TermCell { ch: _old_ch, fg: old_fg })),
None
) => {
term.queue(cursor::MoveTo(self.x_vp + old_x_y.0, self.y_vp + old_x_y.1));
term.queue(PrintStyledContent(' '.with(Color::Reset)))?;
old_pos_cell = old_buffer.next();
}
#[rustfmt::skip]
(None,
Some((new_x_y, TermCell { ch: new_ch, fg: new_fg })),
) => {
term.queue(cursor::MoveTo(self.x_vp + new_x_y.0, self.y_vp + new_x_y.1));
term.queue(PrintStyledContent(new_ch.with(*new_fg)))?;
new_pos_cell = new_buffer.next();
}
#[rustfmt::skip]
(Some((old_x_y, TermCell { ch: old_ch, fg: old_fg })),
Some((new_x_y, TermCell { ch: new_ch, fg: new_fg })),
) => {
match old_x_y.cmp(new_x_y) {
Ordering::Less => {
term.queue(cursor::MoveTo(self.x_vp + old_x_y.0, self.y_vp + old_x_y.1));
term.queue(PrintStyledContent(' '.with(Color::Reset)))?;
old_pos_cell = old_buffer.next();
}
Ordering::Greater => {
term.queue(cursor::MoveTo(self.x_vp + new_x_y.0, self.y_vp + new_x_y.1));
term.queue(PrintStyledContent(new_ch.with(*new_fg)))?;
new_pos_cell = new_buffer.next();
}
Ordering::Equal => {
if new_fg != old_fg || new_ch != old_ch {
term.queue(cursor::MoveTo(self.x_vp + new_x_y.0, self.y_vp + new_x_y.1));
term.queue(PrintStyledContent(new_ch.with(*new_fg)))?;
}
old_pos_cell = old_buffer.next();
new_pos_cell = new_buffer.next();
}
}
}
}
}
term.queue(cursor::MoveTo(0, 0))?
.queue(terminal::EndSynchronizedUpdate)?
.flush()?;
std::mem::swap(&mut self.prev_buf, &mut self.next_buf);
self.next_buf.clear();
Ok(())
}
}