use ansi_to_tui::IntoText;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Paragraph, Widget};
use ratatui::Frame;
use crate::app::AppState;
use crate::sidebar::{Location, VisibleKind};
use crate::ui::embed_terminal::EmbedTerminal;
use crate::ui::{banner, section_preview, tab_strip, Theme};
pub fn render(
frame: &mut Frame<'_>,
area: Rect,
state: &AppState,
theme: &Theme,
embed: Option<&EmbedTerminal>,
with_border: bool,
) {
reset_area(frame.buffer_mut(), area);
if state.sessions.is_empty() && state.sidebar.is_empty() {
let font = banner::canonical(&state.banner_font);
section_preview::render_empty(frame.buffer_mut(), area, font, theme);
return;
}
let mut working_area = area;
if let Some(container) = state
.sidebar
.visible()
.get(state.selected)
.and_then(|e| e.container())
{
if area.height > 0 && area.width > 0 {
let strip_area = Rect::new(area.x, area.y, area.width, 1);
let tab_views: Vec<Option<&crate::tmux::session::SessionView>> = container
.members
.iter()
.map(|m| state.session_by_name(m))
.collect();
tab_strip::render(frame.buffer_mut(), strip_area, container, &tab_views, theme);
working_area = Rect::new(
area.x,
area.y + 1,
area.width,
area.height.saturating_sub(1),
);
}
}
let area = working_area;
if let Some(VisibleKind::Header) = state.selected_kind() {
if let Some(Location::Header(si)) = state.selected_location() {
if let Some(sec) = state.sidebar.sections.get(si) {
let font = sec
.banner_font
.as_deref()
.map(banner::canonical)
.unwrap_or_else(|| banner::canonical(&state.banner_font));
let members: Vec<&crate::tmux::session::SessionView> = sec
.members
.iter()
.filter_map(|c| state.session_by_name(&c.active))
.collect();
section_preview::render_section(
frame.buffer_mut(),
area,
sec,
&members,
font,
theme,
);
return;
}
}
}
if let Some(embed) = embed {
if let Some(name) = state.selected_session_name() {
if embed.session() == name {
let render_area = if with_border && state.single_window_mode {
shrink_for_focus_border(area)
} else {
area
};
embed.render(frame.buffer_mut(), render_area);
return;
}
}
}
let text: Text<'_> = if state.sessions.is_empty() {
placeholder("", theme)
} else {
match state.selected_preview() {
Some(bytes) if !bytes.is_empty() => bytes
.into_text()
.unwrap_or_else(|_| placeholder("preview: (ansi parse failed)", theme)),
_ => placeholder("preview: capturing…", theme),
}
};
let text_lines = text.lines.len() as u16;
let scroll_y = text_lines.saturating_sub(area.height);
Paragraph::new(text)
.scroll((scroll_y, 0))
.render(area, frame.buffer_mut());
}
fn shrink_for_focus_border(area: Rect) -> Rect {
if area.width < 2 || area.height < 2 {
return Rect::new(area.x, area.y, 0, 0);
}
Rect::new(area.x + 1, area.y + 1, area.width - 2, area.height - 2)
}
fn reset_area(buf: &mut Buffer, area: Rect) {
let reset_style = Style::default().fg(Color::Reset).bg(Color::Reset);
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
let cell = &mut buf[(x, y)];
cell.set_char(' ');
cell.set_style(reset_style);
}
}
}
fn placeholder(msg: &str, theme: &Theme) -> Text<'static> {
Text::from(vec![
Line::from(""),
Line::from(Span::styled(
format!(" {}", msg),
Style::default().fg(theme.text_muted),
)),
])
}