use std::fmt::{Debug, Display};
use r3bl_rs_utils_core::*;
use r3bl_rs_utils_macro::style;
use serde::*;
use crate::*;
const DEFAULT_CURSOR_CHAR: char = '▒';
#[derive(Debug)]
enum CaretPaintStyle {
#[allow(dead_code)]
GlobalCursor,
LocalPaintedEffect,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct EditorRenderEngine {
pub current_box: FlexBox,
}
impl EditorRenderEngine {
pub fn viewport_width(&self) -> ChUnit { self.current_box.style_adjusted_bounds_size.cols }
pub fn viewport_height(&self) -> ChUnit { self.current_box.style_adjusted_bounds_size.rows }
pub async fn apply<S, A>(
&mut self,
args: EditorEngineArgs<'_, S, A>,
input_event: &InputEvent,
) -> CommonResult<Option<EditorBuffer>>
where
S: Default + Display + Clone + PartialEq + Debug + Sync + Send,
A: Default + Display + Clone + Sync + Send,
{
let EditorEngineArgs {
buffer,
component_registry,
shared_tw_data,
self_id,
..
} = args;
if let Ok(editor_event) = EditorBufferCommand::try_from(input_event) {
let mut new_editor_buffer = buffer.clone();
EditorBuffer::apply_editor_event(
self,
&mut new_editor_buffer,
editor_event,
shared_tw_data,
component_registry,
self_id,
);
Ok(Some(new_editor_buffer))
} else {
Ok(None)
}
}
pub async fn render<S, A>(
&mut self,
args: EditorEngineArgs<'_, S, A>,
current_box: &FlexBox,
) -> CommonResult<RenderPipeline>
where
S: Default + Display + Clone + PartialEq + Debug + Sync + Send,
A: Default + Display + Clone + Sync + Send,
{
throws_with_return!({
let EditorEngineArgs {
buffer,
component_registry,
..
} = args;
self.current_box = current_box.clone();
let render_args = RenderArgs {
buffer,
component_registry,
};
if buffer.is_empty() {
self.render_empty_state(&render_args)
} else {
let q_content = self.render_content(&render_args);
let q_caret = self.render_caret(CaretPaintStyle::LocalPaintedEffect, &render_args);
render_pipeline!(@join_and_drop q_content, q_caret)
}
})
}
fn render_content<S, A>(&mut self, render_args: &RenderArgs<'_, S, A>) -> RenderPipeline
where
S: Default + Display + Clone + PartialEq + Debug + Sync + Send,
A: Default + Display + Clone + Sync + Send,
{
let RenderArgs { buffer, .. } = render_args;
let mut render_pipeline = render_pipeline!(@new_empty);
let Size {
cols: max_display_col_count,
rows: max_display_row_count,
} = self.current_box.style_adjusted_bounds_size;
for (row_index, line) in buffer
.get_lines()
.iter()
.skip(ch!(@to_usize buffer.get_scroll_offset().row))
.enumerate()
{
if ch!(row_index) > max_display_row_count {
break;
}
let truncated_line = line.truncate_start_by_n_col(buffer.get_scroll_offset().col);
let truncated_line = UnicodeString::from(truncated_line);
let truncated_line = truncated_line.truncate_end_to_fit_display_cols(max_display_col_count);
render_pipeline! {
@push_into render_pipeline at ZOrder::Normal =>
RenderOp::MoveCursorPositionRelTo(
self.current_box.style_adjusted_origin_pos, position! { col: 0 , row: ch!(@to_usize row_index) }
),
RenderOp::ApplyColors(self.current_box.get_computed_style()),
RenderOp::PrintTextWithAttributes(truncated_line.into(), self.current_box.get_computed_style()),
RenderOp::ResetColor
};
}
render_pipeline
}
fn render_caret<S, A>(
&mut self,
style: CaretPaintStyle,
render_args: &RenderArgs<'_, S, A>,
) -> RenderPipeline
where
S: Default + Display + Clone + PartialEq + Debug + Sync + Send,
A: Default + Display + Clone + Sync + Send,
{
let RenderArgs {
component_registry,
buffer,
..
} = render_args;
let mut render_pipeline: RenderPipeline = RenderPipeline::default();
if component_registry
.has_focus
.does_current_box_have_focus(&self.current_box)
{
match style {
CaretPaintStyle::GlobalCursor => {
render_pipeline! {
@push_into render_pipeline at ZOrder::Caret =>
RenderOp::RequestShowCaretAtPositionRelTo(
self.current_box.style_adjusted_origin_pos, buffer.get_caret(CaretKind::Raw))
};
}
CaretPaintStyle::LocalPaintedEffect => {
let str_at_caret: String = if let Some(UnicodeStringSegmentSliceResult {
unicode_string_seg: str_seg,
..
}) = get_content::string_at_caret(buffer, self)
{
str_seg.string
} else {
DEFAULT_CURSOR_CHAR.into()
};
render_pipeline! {
@push_into render_pipeline at ZOrder::Caret =>
RenderOp::MoveCursorPositionRelTo(
self.current_box.style_adjusted_origin_pos, buffer.get_caret(CaretKind::Raw)),
RenderOp::PrintTextWithAttributes(
str_at_caret,
style! { attrib: [reverse] }.into()),
RenderOp::MoveCursorPositionRelTo(
self.current_box.style_adjusted_origin_pos, buffer.get_caret(CaretKind::Raw))
};
}
}
}
render_pipeline
}
fn render_empty_state<S, A>(&mut self, render_args: &RenderArgs<'_, S, A>) -> RenderPipeline
where
S: Default + Display + Clone + PartialEq + Debug + Sync + Send,
A: Default + Display + Clone + Sync + Send,
{
let RenderArgs {
component_registry, ..
} = render_args;
let mut render_pipeline: RenderPipeline = RenderPipeline::default();
let mut content_cursor_pos = position! { col: 0 , row: 0 };
render_pipeline! {
@push_into render_pipeline at ZOrder::Normal =>
RenderOp::MoveCursorPositionRelTo(
self.current_box.style_adjusted_origin_pos, position! { col: 0 , row: 0 }),
RenderOp::ApplyColors(style! {
color_fg: TWColor::Red
}.into()),
RenderOp::PrintTextWithAttributes("No content added".into(), None),
RenderOp::ResetColor
};
if component_registry
.has_focus
.does_current_box_have_focus(&self.current_box)
{
render_pipeline! {
@push_into render_pipeline at ZOrder::Normal =>
RenderOp::MoveCursorPositionRelTo(
self.current_box.style_adjusted_origin_pos,
content_cursor_pos.add_row_with_bounds(
ch!(1), self.current_box.style_adjusted_bounds_size.rows)),
RenderOp::PrintTextWithAttributes("👀".into(), None)
};
}
render_pipeline
}
}