use super::ProcessManager;
use crate::{ANSIBasicColor, ArrayOverflowResult, FlushKind, IndexOps, LengthOps,
OffscreenBuffer, OutputDevice, PixelChar, Size, TuiStyle, col,
core::coordinates::{idx, len},
lock_output_device_as_mut,
tui::terminal_lib_backends::{OffscreenBufferPaint,
OffscreenBufferPaintImplCrossterm},
tui_style_attrib::Bold,
tui_style_attribs};
pub const STATUS_BAR_HEIGHT: u16 = 1;
pub const MAX_PROCESSES: usize = 9;
pub struct OutputRenderer {
terminal_size: Size,
}
impl std::fmt::Debug for OutputRenderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OutputRenderer")
.field("terminal_size", &self.terminal_size)
.finish()
}
}
impl OutputRenderer {
#[must_use]
pub fn new(terminal_size: Size) -> Self { Self { terminal_size } }
pub fn render_from_active_buffer(
&mut self,
output_device: &OutputDevice,
process_manager: &ProcessManager,
) -> miette::Result<()> {
let active_buffer = process_manager.get_active_buffer();
let mut composite_buffer = active_buffer.clone();
self.composite_status_bar_into_buffer(&mut composite_buffer, process_manager);
self.paint_buffer(&composite_buffer, output_device);
Ok(())
}
fn composite_status_bar_into_buffer(
&mut self,
ofs_buf: &mut OffscreenBuffer,
process_manager: &ProcessManager,
) {
let status_text = self.generate_status_text(process_manager);
let last_row_idx = self.terminal_size.row_height.as_usize().saturating_sub(1);
for col_idx in 0..self.terminal_size.col_width.as_usize() {
ofs_buf[last_row_idx][col_idx] = PixelChar::Spacer;
}
let status_style = TuiStyle {
attribs: tui_style_attribs(Bold),
color_fg: Some(ANSIBasicColor::White.into()),
color_bg: Some(ANSIBasicColor::Blue.into()),
..Default::default()
};
for (col_idx, ch) in status_text.chars().enumerate() {
if self.terminal_size.col_width.is_overflowed_by(col(col_idx))
== ArrayOverflowResult::Overflowed
{
break;
}
ofs_buf[last_row_idx][col_idx] = PixelChar::PlainText {
display_char: ch,
style: status_style,
};
}
}
fn paint_buffer(&mut self, ofs_buf: &OffscreenBuffer, output_device: &OutputDevice) {
let mut crossterm_impl = OffscreenBufferPaintImplCrossterm {};
let render_ops = crossterm_impl.render(ofs_buf);
crossterm_impl.paint(
render_ops,
FlushKind::JustFlush,
self.terminal_size,
lock_output_device_as_mut!(output_device),
false, );
}
fn generate_status_text(&self, process_manager: &ProcessManager) -> String {
let mut status_parts = Vec::new();
let mut current_width = 0usize;
for (i, process) in process_manager.processes().iter().enumerate() {
let is_active = i == process_manager.active_index();
let status_indicator = if process.is_running() { "🟢" } else { "🔴" };
let tab_text = if is_active {
format!(" [{}:{}{}] ", i + 1, status_indicator, process.name)
} else {
format!(" {}:{}{} ", i + 1, status_indicator, process.name)
};
let tab_width = tab_text.chars().count();
let new_width = current_width + tab_width;
if self
.terminal_size
.col_width
.is_overflowed_by(col(new_width))
== ArrayOverflowResult::Overflowed
{
break;
}
status_parts.push(tab_text);
current_width += tab_width;
}
let process_count = process_manager.processes().len();
let shortcuts = Self::generate_shortcuts_text(process_count);
let shortcuts_width = shortcuts.chars().count();
let total_width = current_width + shortcuts_width;
if self
.terminal_size
.col_width
.is_overflowed_by(col(total_width))
== ArrayOverflowResult::Overflowed
{
return status_parts.join("");
}
status_parts.push(shortcuts);
status_parts.join("")
}
fn generate_shortcuts_text(process_count: usize) -> String {
if process_count <= 4 {
match process_count {
1 => " F1: Switch | Ctrl+Q: Quit".to_string(),
2 => " F1/F2: Switch | Ctrl+Q: Quit".to_string(),
3 => " F1/F2/F3: Switch | Ctrl+Q: Quit".to_string(),
4 => " F1/F2/F3/F4: Switch | Ctrl+Q: Quit".to_string(),
_ => " Ctrl+Q: Quit".to_string(),
}
} else {
format!(" F1-F{}: Switch | Ctrl+Q: Quit", {
let process_idx = idx(process_count);
let max_display = len(9);
process_idx.clamp_to_max_length(max_display).as_usize()
})
}
}
pub fn render_initial_status_bar(
&mut self,
output_device: &OutputDevice,
process_manager: &ProcessManager,
) -> miette::Result<()> {
self.render_from_active_buffer(output_device, process_manager)
}
pub fn update_terminal_size(&mut self, new_size: Size) {
self.terminal_size = new_size;
}
}