use async_trait::async_trait;
use r3bl_rs_utils_core::*;
use super::*;
use crate::{tui::DEBUG_SHOW_PIPELINE, *};
pub async fn paint(pipeline: &RenderPipeline, flush_kind: FlushKind, shared_tw_data: &SharedTWData) {
let maybe_saved_pipeline = shared_tw_data.read().await.maybe_render_pipeline.clone();
if let Some(saved_pipeline) = maybe_saved_pipeline {
optimized_paint::paint(pipeline, &saved_pipeline, flush_kind, shared_tw_data).await;
} else {
no_optimize_paint(pipeline, flush_kind, shared_tw_data).await;
}
shared_tw_data.write().await.maybe_render_pipeline = Some(pipeline.clone());
}
pub mod optimized_paint {
use super::*;
impl RenderOps {
async fn paint(&self, skip_flush: &mut bool, shared_tw_data: &SharedTWData) {
for op in self.iter() {
route_paint_render_op_to_backend(skip_flush, op, shared_tw_data).await;
}
}
fn contains_hoisted_ops(&self) -> bool {
self.iter().any(|op| {
matches!(
op,
RenderOp::RequestShowCaretAtPositionAbs(_) | RenderOp::RequestShowCaretAtPositionRelTo(_, _)
)
})
}
fn contains_paint_caret_ops(&self) -> bool {
self.iter().any(|op| {
if let RenderOp::PrintTextWithAttributes(_, Some(style)) = op {
style.reverse
} else {
false
}
})
}
fn contains_clear_screen_ops(&self) -> bool { self.iter().any(|op| matches!(op, RenderOp::ClearScreen)) }
fn get_text_with_attributes_ops(&self) -> Vec<usize> {
let vec_op: Vec<&RenderOp> = self
.iter()
.filter(|op| matches!(op, RenderOp::PrintTextWithAttributes(_, _)))
.collect();
vec_op
.iter()
.map(|op| {
if let RenderOp::PrintTextWithAttributes(string, _) = op {
string.len()
} else {
0
}
})
.collect()
}
fn has_less_or_more_text_than(&self, other: &Self) -> bool {
let lhs_ops = self.get_text_with_attributes_ops();
let rhs_ops = other.get_text_with_attributes_ops();
lhs_ops.iter().sum::<usize>() != rhs_ops.iter().sum::<usize>()
}
fn has_different_lines_than(&self, other: &Self) -> bool {
let lhs_ops = self.get_text_with_attributes_ops();
let rhs_ops = other.get_text_with_attributes_ops();
lhs_ops.len() != rhs_ops.len()
}
fn has_fewer_ops_than(&self, other: &Self) -> bool { self.len() < other.len() }
}
impl<'a> From<&'a RenderPipeline> for Vec<&'a RenderOps> {
fn from(pipeline: &'a RenderPipeline) -> Self {
let mut vec_hoisted_ops: Vec<&RenderOps> = vec![];
let mut vec_ops: Vec<&RenderOps> = vec![];
for z_order in RENDER_ORDERED_Z_ORDER_ARRAY.iter() {
if let Some(vec_render_ops) = pipeline.get(z_order) {
for render_ops in vec_render_ops.iter() {
if render_ops.contains_hoisted_ops() {
vec_hoisted_ops.push(render_ops);
} else {
vec_ops.push(render_ops);
}
}
}
}
if vec_hoisted_ops.len() > 1 {
log_no_err!(
WARN,
"🥕 Too many requests to show caret at position (some will be clobbered): {:?}",
vec_hoisted_ops,
);
}
vec_ops.extend(vec_hoisted_ops);
vec_ops
}
}
pub async fn paint(
pipeline: &RenderPipeline,
saved_pipeline: &RenderPipeline,
flush_kind: FlushKind,
shared_tw_data: &SharedTWData,
) {
let mut skip_flush = false;
let new_vec_render_ops: Vec<&RenderOps> = pipeline.into();
let saved_vec_render_ops: Vec<&RenderOps> = saved_pipeline.into();
if new_vec_render_ops.len() < saved_vec_render_ops.len() {
no_optimize_paint(pipeline, flush_kind, shared_tw_data).await;
return;
}
for (new_vec_index, new_ops) in new_vec_render_ops.iter().enumerate() {
match saved_vec_render_ops.get(new_vec_index) {
Some(saved_ops) => {
if new_ops != saved_ops {
new_ops.paint(&mut skip_flush, shared_tw_data).await;
print_debug_diff_render_ops(new_ops, saved_ops);
}
}
None => {
if new_ops.contains_clear_screen_ops() {
no_optimize_paint(pipeline, flush_kind, shared_tw_data).await;
return;
} else {
new_ops.paint(&mut skip_flush, shared_tw_data).await;
}
}
}
}
if !skip_flush {
RenderOp::default().flush()
};
call_if_true!(DEBUG_SHOW_PIPELINE, {
log_no_err!(INFO, "🎨 render_pipeline::paint() ok ✅: pipeline: \n{:?}", pipeline,);
});
}
fn print_debug_diff_render_ops(new_ops: &RenderOps, saved_ops: &RenderOps) {
call_if_true!(DEBUG_SHOW_PAINT_OPTIMIZATION_HEURISTIC, {
log_no_err!(
DEBUG,
"\n🤔🧨🎨 [Repaint introspection] new_ops != saved_ops,
\rnew_ops: {:?},
\rsaved_ops: {:?},
\nhas_fewer_ops_than: {:?}\
\nhas_less_or_more_text_than:{:?}\
\nhas_different_lines_than: {:?}\
\ncontains_paint_caret_ops: {:?}\
\ncontains_clear_screen_ops: {:?}\
\ncontains_hoisted_ops: {:?}\
\n",
new_ops,
saved_ops,
if new_ops.has_fewer_ops_than(saved_ops) {
"✅"
} else {
"🚫"
},
if new_ops.has_less_or_more_text_than(saved_ops) {
"✅"
} else {
"🚫"
},
if new_ops.has_different_lines_than(saved_ops) {
"✅"
} else {
"🚫"
},
if new_ops.contains_paint_caret_ops() {
"✅"
} else {
"🚫"
},
if new_ops.contains_clear_screen_ops() {
"✅"
} else {
"🚫"
},
if new_ops.contains_hoisted_ops() { "✅" } else { "🚫" },
);
});
}
}
pub async fn no_optimize_paint(pipeline: &RenderPipeline, flush_kind: FlushKind, shared_tw_data: &SharedTWData) {
let mut skip_flush = false;
if let FlushKind::ClearBeforeFlush = flush_kind {
RenderOp::default().clear_before_flush();
}
let mut special_hoisted_op_vec: Vec<RenderOp> = vec![];
for z_order in RENDER_ORDERED_Z_ORDER_ARRAY.iter() {
if let Some(render_ops_vec) = pipeline.get(z_order) {
for (_render_ops_index, render_ops) in render_ops_vec.iter().enumerate() {
for (_render_op_index, render_op) in render_ops.iter().enumerate() {
match render_op {
RenderOp::RequestShowCaretAtPositionAbs(_) | RenderOp::RequestShowCaretAtPositionRelTo(_, _) => {
special_hoisted_op_vec.push(render_op.clone());
}
_ => {
route_paint_render_op_to_backend(&mut skip_flush, render_op, shared_tw_data).await;
}
}
}
}
}
}
if special_hoisted_op_vec.len() > 1 {
log_no_err!(
WARN,
"🥕 Too many requests to show caret at position (some will be clobbered): {:?}",
special_hoisted_op_vec,
);
}
for special_render_op in &special_hoisted_op_vec {
route_paint_render_op_to_backend(&mut skip_flush, special_render_op, shared_tw_data).await;
}
if !skip_flush {
RenderOp::default().flush()
};
call_if_true!(DEBUG_SHOW_PIPELINE, {
log_no_err!(INFO, "🎨 render_pipeline::paint() ok ✅: pipeline: \n{:?}", pipeline,);
});
}
pub async fn sanitize_and_save_abs_position(orig_abs_pos: Position, shared_tw_data: &SharedTWData) -> Position {
let Size {
cols: max_cols,
rows: max_rows,
} = shared_tw_data.read().await.size;
let mut sanitized_abs_pos: Position = orig_abs_pos;
if orig_abs_pos.col > max_cols {
sanitized_abs_pos.col = max_cols;
}
if orig_abs_pos.row > max_rows {
sanitized_abs_pos.row = max_rows;
}
shared_tw_data.write().await.cursor_position = sanitized_abs_pos;
debug(orig_abs_pos, sanitized_abs_pos);
return sanitized_abs_pos;
fn debug(orig_pos: Position, sanitized_pos: Position) {
call_if_true!(DEBUG_TUI_MOD, {
if sanitized_pos != orig_pos {
log_no_err!(
INFO,
"pipeline : 📍🗜️ Attempt to set cursor position {:?} \
outside of terminal window; clamping to nearest edge of window {:?}",
orig_pos,
sanitized_pos
);
}
});
call_if_true!(DEBUG_SHOW_PIPELINE_EXPANDED, {
log_no_err!(
INFO,
"pipeline : 📍 Save the cursor position {:?} \
to SharedTWData",
sanitized_pos
);
});
}
}
pub async fn route_paint_render_op_to_backend(
skip_flush: &mut bool,
render_op: &RenderOp,
shared_tw_data: &SharedTWData,
) {
match TERMINAL_LIB_BACKEND {
TerminalLibBackend::Crossterm => {
RenderOpImplCrossterm {}
.paint(skip_flush, render_op, shared_tw_data)
.await;
}
TerminalLibBackend::Termion => todo!(), }
}
#[async_trait]
pub trait PaintRenderOp {
async fn paint(&self, skip_flush: &mut bool, render_op: &RenderOp, shared_tw_data: &SharedTWData);
}