use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph, Sparkline, Wrap},
Frame,
};
use crate::process::info::ProcessInfo;
use super::format::{format_duration_full, format_memory};
use super::styles::{
BORDER_STYLE, CLAUDE_SPARKLINE_STYLE, CODEX_SPARKLINE_STYLE, TITLE_STYLE,
};
const KEY_STYLE: Style = Style::new().fg(Color::Yellow);
const VAL_STYLE: Style = Style::new().fg(Color::White);
fn render_sparkline(f: &mut Frame, area: Rect, title: &str, data: &[u64], style: Style) {
let block = Block::default()
.title(title)
.title_style(TITLE_STYLE)
.borders(Borders::ALL)
.border_style(BORDER_STYLE);
let sparkline = Sparkline::default().block(block).data(data).style(style);
f.render_widget(sparkline, area);
}
fn kv<'a>(key: &'a str, val: &'a str) -> Line<'a> {
Line::from(vec![
Span::styled(key, KEY_STYLE),
Span::styled(val, VAL_STYLE),
])
}
fn render_info_table(f: &mut Frame, area: Rect, info: &ProcessInfo) {
let parent = info.parent_pid.map(|p| p.to_string()).unwrap_or_else(|| "—".into());
let exe = info.exe_path.as_deref().unwrap_or("—");
let cwd = info.cwd.as_deref().unwrap_or("—");
let cpu_str = format!("{:.1}%", info.cpu_usage);
let mem_str = format_memory(info.memory_bytes);
let env_str = info.environ_count.to_string();
let pid_str = info.pid.to_string();
let started_str = format!("epoch {}", info.start_time);
let runtime_str = format_duration_full(info.run_time);
let lines = vec![
kv("PID: ", &pid_str),
kv("Parent PID: ", &parent),
kv("Name: ", &info.name),
kv("Status: ", &info.status),
kv("CPU: ", &cpu_str),
kv("Memory: ", &mem_str),
kv("Exe Path: ", exe),
kv("Working Dir: ", cwd),
kv("Env Vars: ", &env_str),
kv("Started: ", &started_str),
kv("Run Time: ", &runtime_str),
];
let block = Block::default()
.title(" Info ")
.title_style(TITLE_STYLE)
.borders(Borders::ALL)
.border_style(BORDER_STYLE);
f.render_widget(Paragraph::new(lines).block(block), area);
}
pub fn render_detail_view(
f: &mut Frame,
area: Rect,
info: &ProcessInfo,
cpu_history: &[f32],
mem_history: &[u64],
) {
let sections = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), Constraint::Length(13), Constraint::Length(4), Constraint::Length(4), Constraint::Min(3), ])
.split(area);
render_header(f, sections[0], info);
render_info_table(f, sections[1], info);
let cpu_data: Vec<u64> = cpu_history.iter().map(|&v| (v * 10.0) as u64).collect();
render_sparkline(f, sections[2], " CPU Usage % ", &cpu_data, CLAUDE_SPARKLINE_STYLE);
let mem_data: Vec<u64> = mem_history.iter().map(|&b| b / (1024 * 1024)).collect();
render_sparkline(
f,
sections[3],
" Memory Usage (MB) ",
&mem_data,
CODEX_SPARKLINE_STYLE,
);
render_command(f, sections[4], info);
}
fn render_header(f: &mut Frame, area: Rect, info: &ProcessInfo) {
let title = format!(" {} — PID {} — {} ", info.name, info.pid, info.status);
let block = Block::default()
.title(title)
.title_style(TITLE_STYLE.add_modifier(Modifier::BOLD))
.borders(Borders::ALL)
.border_style(BORDER_STYLE);
f.render_widget(block, area);
}
fn render_command(f: &mut Frame, area: Rect, info: &ProcessInfo) {
let cmd_text = info.cmd.join(" ");
let block = Block::default()
.title(" Command ")
.title_style(TITLE_STYLE)
.borders(Borders::ALL)
.border_style(BORDER_STYLE);
f.render_widget(
Paragraph::new(cmd_text)
.block(block)
.style(Style::new().fg(Color::White))
.wrap(Wrap { trim: false }),
area,
);
}