use std::{fmt::{self, Debug, Write},
ops::{Deref, DerefMut}};
use diff_chunks::PixelCharDiffChunks;
use smallvec::smallvec;
use super::{FlushKind, RenderOps};
use crate::{col,
dim_underline,
fg_green,
fg_magenta,
get_mem_size,
inline_string,
ok,
row,
tiny_inline_string,
ColWidth,
GetMemSize,
InlineString,
InlineVec,
List,
LockedOutputDevice,
Pos,
Size,
TinyInlineString,
TuiColor,
TuiStyle};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct OffscreenBuffer {
pub buffer: PixelCharLines,
pub window_size: Size,
pub my_pos: Pos,
pub my_fg_color: Option<TuiColor>,
pub my_bg_color: Option<TuiColor>,
}
impl GetMemSize for OffscreenBuffer {
fn get_mem_size(&self) -> usize {
self.buffer.get_mem_size()
+ std::mem::size_of::<Size>()
+ std::mem::size_of::<Pos>()
+ std::mem::size_of::<Option<TuiColor>>()
+ std::mem::size_of::<Option<TuiColor>>()
}
}
pub mod diff_chunks {
use super::*;
#[derive(Clone, Default, PartialEq)]
pub struct PixelCharDiffChunks {
pub inner: List<DiffChunk>,
}
pub type DiffChunk = (Pos, PixelChar);
impl Deref for PixelCharDiffChunks {
type Target = List<DiffChunk>;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl From<List<DiffChunk>> for PixelCharDiffChunks {
fn from(list: List<DiffChunk>) -> Self { Self { inner: list } }
}
}
mod offscreen_buffer_impl {
use super::*;
impl Debug for PixelCharDiffChunks {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (pos, pixel_char) in self.iter() {
writeln!(f, "\t{pos:?}: {pixel_char:?}")?;
}
ok!()
}
}
impl Deref for OffscreenBuffer {
type Target = PixelCharLines;
fn deref(&self) -> &Self::Target { &self.buffer }
}
impl DerefMut for OffscreenBuffer {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.buffer }
}
impl Debug for OffscreenBuffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "window_size: {:?}, ", self.window_size)?;
let height = self.window_size.row_height.as_usize();
for row_index in 0..height {
if let Some(row) = self.buffer.get(row_index) {
if row_index > 0 {
writeln!(f)?;
}
writeln!(
f,
"{}",
fg_green(&inline_string!("row_index: {}", row_index))
)?;
write!(f, "{row:?}")?;
}
}
writeln!(f)
}
}
impl OffscreenBuffer {
pub fn diff(&self, other: &Self) -> Option<PixelCharDiffChunks> {
if self.window_size != other.window_size {
return None;
}
let mut acc = List::default();
for (row_idx, (self_row, other_row)) in
self.buffer.iter().zip(other.buffer.iter()).enumerate()
{
for (col_idx, (self_pixel_char, other_pixel_char)) in
self_row.iter().zip(other_row.iter()).enumerate()
{
if self_pixel_char != other_pixel_char {
let pos = col(col_idx) + row(row_idx);
acc.push((pos, other_pixel_char.clone()));
}
}
}
Some(PixelCharDiffChunks::from(acc))
}
pub fn new_with_capacity_initialized(window_size: Size) -> Self {
Self {
buffer: PixelCharLines::new_with_capacity_initialized(window_size),
window_size,
my_pos: Default::default(),
my_fg_color: None,
my_bg_color: None,
}
}
pub fn clear(&mut self) {
for line in self.buffer.iter_mut() {
for pixel_char in line.iter_mut() {
if pixel_char != &PixelChar::Spacer {
*pixel_char = PixelChar::Spacer;
}
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PixelCharLines {
pub lines: InlineVec<PixelCharLine>,
}
mod pixel_char_lines_impl {
use super::*;
impl GetMemSize for PixelCharLines {
fn get_mem_size(&self) -> usize { get_mem_size::slice_size(self.lines.as_ref()) }
}
impl Deref for PixelCharLines {
type Target = InlineVec<PixelCharLine>;
fn deref(&self) -> &Self::Target { &self.lines }
}
impl DerefMut for PixelCharLines {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.lines }
}
impl PixelCharLines {
pub fn new_with_capacity_initialized(window_size: Size) -> Self {
let window_height = window_size.row_height;
let window_width = window_size.col_width;
Self {
lines: smallvec![
PixelCharLine::new_with_capacity_initialized(window_width);
window_height.as_usize()
],
}
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct PixelCharLine {
pub pixel_chars: InlineVec<PixelChar>,
}
impl GetMemSize for PixelCharLine {
fn get_mem_size(&self) -> usize {
get_mem_size::slice_size(self.pixel_chars.as_ref())
}
}
mod pixel_char_line_impl {
use super::*;
impl Debug for PixelCharLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut void_indices: InlineVec<usize> = smallvec![];
let mut spacer_indices: InlineVec<usize> = smallvec![];
let mut void_count: InlineVec<TinyInlineString> = smallvec![];
let mut spacer_count: InlineVec<TinyInlineString> = smallvec![];
const MAX_PIXEL_CHARS_PER_LINE: usize = 6;
let mut char_count = 0;
for (col_index, pixel_char) in self.iter().enumerate() {
match pixel_char {
PixelChar::Void => {
void_count.push(TinyInlineString::from(col_index.to_string()));
void_indices.push(col_index);
}
PixelChar::Spacer => {
spacer_count.push(TinyInlineString::from(col_index.to_string()));
spacer_indices.push(col_index);
}
_ => {}
}
write!(
f,
"{}{:?}",
dim_underline(&tiny_inline_string!("{col_index:03}")),
pixel_char
)?;
char_count += 1;
if char_count >= MAX_PIXEL_CHARS_PER_LINE {
char_count = 0;
writeln!(f)?;
}
}
{
if !void_count.is_empty() {
write!(f, "void [ ")?;
fmt_impl_index_values(&void_indices, f)?;
write!(f, " ]")?;
if !spacer_count.is_empty() {
write!(f, " | ")?;
}
}
if !spacer_count.is_empty() {
if !void_count.is_empty() {
write!(f, ", ")?;
}
write!(f, "spacer [ ")?;
fmt_impl_index_values(&spacer_indices, f)?;
write!(f, " ]")?;
}
}
ok!()
}
}
fn fmt_impl_index_values(
values: &[usize],
f: &mut fmt::Formatter<'_>,
) -> std::fmt::Result {
let mut acc_current_range: InlineVec<usize> = smallvec![];
mod helpers {
pub enum Peek {
NextItemContinuesRange,
NextItemDoesNotContinueRange,
}
pub fn peek_does_next_item_continues_range(
values: &[usize],
index: usize,
) -> Peek {
if values.get(index + 1).is_none() {
return Peek::NextItemDoesNotContinueRange;
}
if values[index + 1] == values[index] + 1 {
Peek::NextItemContinuesRange
} else {
Peek::NextItemDoesNotContinueRange
}
}
pub enum CurrentRange {
DoesNotExist,
Exists,
}
pub fn does_current_range_exist(current_range: &[usize]) -> CurrentRange {
match current_range.is_empty() {
true => CurrentRange::DoesNotExist,
false => CurrentRange::Exists,
}
}
}
for (index, value) in values.iter().enumerate() {
match (
helpers::peek_does_next_item_continues_range(values, index),
helpers::does_current_range_exist(&acc_current_range),
) {
(
helpers::Peek::NextItemContinuesRange,
helpers::CurrentRange::DoesNotExist,
) => {
acc_current_range.push(*value);
}
(
helpers::Peek::NextItemDoesNotContinueRange,
helpers::CurrentRange::DoesNotExist,
) => {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{value}")?;
}
(
helpers::Peek::NextItemContinuesRange,
helpers::CurrentRange::Exists,
) => {
acc_current_range.push(*value);
}
(
helpers::Peek::NextItemDoesNotContinueRange,
helpers::CurrentRange::Exists,
) => {
if index > 0 {
write!(f, ", ")?;
}
acc_current_range.push(*value);
write!(
f,
"{}-{}",
acc_current_range[0],
acc_current_range[acc_current_range.len() - 1]
)?;
acc_current_range.clear();
}
}
}
ok!()
}
impl PixelCharLine {
pub fn new_with_capacity_initialized(window_width: ColWidth) -> Self {
Self {
pixel_chars: smallvec![PixelChar::Spacer; window_width.as_usize()],
}
}
}
impl Deref for PixelCharLine {
type Target = InlineVec<PixelChar>;
fn deref(&self) -> &Self::Target { &self.pixel_chars }
}
impl DerefMut for PixelCharLine {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.pixel_chars }
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum PixelChar {
Void,
Spacer,
PlainText {
text: TinyInlineString,
maybe_style: Option<TuiStyle>,
},
}
impl GetMemSize for PixelChar {
fn get_mem_size(&self) -> usize {
match self {
PixelChar::Void => std::mem::size_of::<PixelChar>(),
PixelChar::Spacer => std::mem::size_of::<PixelChar>(),
PixelChar::PlainText {
text,
maybe_style: _,
} => {
std::mem::size_of::<PixelChar>()
+ text.len()
+ std::mem::size_of::<Option<TuiStyle>>()
}
}
}
}
const EMPTY_CHAR: char = '╳';
const VOID_CHAR: char = '❯';
mod pixel_char_impl {
use super::*;
impl Default for PixelChar {
fn default() -> Self { Self::Spacer }
}
impl Debug for PixelChar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const WIDTH: usize = 16;
fn truncate(s: &str, max_chars: usize) -> &str {
match s.char_indices().nth(max_chars) {
None => s,
Some((idx, _)) => &s[..idx],
}
}
match self {
PixelChar::Void => {
write!(f, " V {VOID_CHAR:░^WIDTH$}")?;
}
PixelChar::Spacer => {
write!(f, " S {EMPTY_CHAR:░^WIDTH$}")?;
}
PixelChar::PlainText { text, maybe_style } => {
let mut acc_tmp = InlineString::with_capacity(WIDTH);
match maybe_style {
Some(style) => {
write!(acc_tmp, "'{text}'→{style}")?;
}
_ => {
write!(acc_tmp, "'{text}'")?;
}
};
let trunc_output = truncate(&acc_tmp, WIDTH);
write!(f, " {} {trunc_output: ^WIDTH$}", fg_magenta("P"))?;
}
};
ok!()
}
}
}
pub trait OffscreenBufferPaint {
fn render(&mut self, offscreen_buffer: &OffscreenBuffer) -> RenderOps;
fn render_diff(&mut self, diff_chunks: &PixelCharDiffChunks) -> RenderOps;
fn paint(
&mut self,
render_ops: RenderOps,
flush_kind: FlushKind,
window_size: Size,
locked_output_device: LockedOutputDevice<'_>,
is_mock: bool,
);
fn paint_diff(
&mut self,
render_ops: RenderOps,
window_size: Size,
locked_output_device: LockedOutputDevice<'_>,
is_mock: bool,
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_eq2, height, new_style, tui_color, width};
#[test]
fn test_offscreen_buffer_construction() {
let window_size = width(10) + height(2);
let my_offscreen_buffer =
OffscreenBuffer::new_with_capacity_initialized(window_size);
assert_eq2!(my_offscreen_buffer.buffer.len(), 2);
assert_eq2!(my_offscreen_buffer.buffer[0].len(), 10);
assert_eq2!(my_offscreen_buffer.buffer[1].len(), 10);
for line in my_offscreen_buffer.buffer.iter() {
for pixel_char in line.iter() {
assert_eq2!(pixel_char, &PixelChar::Spacer);
}
}
}
#[test]
fn test_offscreen_buffer_re_init() {
let window_size = width(10) + height(2);
let mut my_offscreen_buffer =
OffscreenBuffer::new_with_capacity_initialized(window_size);
let text_1 = "a".into();
my_offscreen_buffer.buffer[0][0] = PixelChar::PlainText {
text: text_1,
maybe_style: Some(new_style!(color_bg: {tui_color!(green)})),
};
let text_2 = "z".into();
my_offscreen_buffer.buffer[1][9] = PixelChar::PlainText {
text: text_2,
maybe_style: Some(new_style!(color_bg: {tui_color!(red)})),
};
my_offscreen_buffer.clear();
for line in my_offscreen_buffer.buffer.iter() {
for pixel_char in line.iter() {
assert_eq2!(pixel_char, &PixelChar::Spacer);
}
}
}
}