use super::detail_pages;
use super::{app::ResultsApp, detail_page::DetailPage};
use crate::tui::theme::Theme;
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::Style,
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
Frame,
};
pub fn render(frame: &mut Frame, app: &ResultsApp) {
let theme = Theme::default();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), Constraint::Min(0), Constraint::Length(2), ])
.split(frame.area());
render_header(frame, app, chunks[0], &theme);
let content_area = apply_content_margins(chunks[1]);
if let Some(item) = app.selected_item() {
match app.nav().detail_page {
DetailPage::Overview => {
detail_pages::overview::render(frame, app, item, content_area, &theme)
}
DetailPage::ScoreBreakdown => {
detail_pages::score_breakdown::render(frame, app, item, content_area, &theme)
}
DetailPage::Context => {
detail_pages::context::render(frame, app, item, content_area, &theme)
}
DetailPage::Dependencies => {
detail_pages::dependencies::render(frame, app, item, content_area, &theme)
}
DetailPage::GitContext => {
detail_pages::git_context::render(frame, app, item, content_area, &theme)
}
DetailPage::Patterns => {
detail_pages::patterns::render(frame, app, item, content_area, &theme)
}
DetailPage::DataFlow => detail_pages::data_flow::render(
frame,
app,
item,
&app.analysis().data_flow_graph,
content_area,
&theme,
),
DetailPage::Responsibilities => {
detail_pages::responsibilities::render(frame, app, item, content_area, &theme)
}
}
} else {
let empty = Paragraph::new("No item selected").style(Style::default().fg(theme.muted));
frame.render_widget(empty, content_area);
}
render_footer(frame, app, chunks[2], &theme);
}
const HORIZONTAL_MARGIN: u16 = 1;
fn apply_content_margins(area: Rect) -> Rect {
const VERTICAL_MARGIN: u16 = 1;
Rect {
x: area.x.saturating_add(HORIZONTAL_MARGIN),
y: area.y.saturating_add(VERTICAL_MARGIN),
width: area.width.saturating_sub(HORIZONTAL_MARGIN * 2),
height: area.height.saturating_sub(VERTICAL_MARGIN * 2),
}
}
fn apply_horizontal_margin(area: Rect) -> Rect {
Rect {
x: area.x.saturating_add(HORIZONTAL_MARGIN),
y: area.y,
width: area.width.saturating_sub(HORIZONTAL_MARGIN * 2),
height: area.height,
}
}
fn render_header(frame: &mut Frame, app: &ResultsApp, area: Rect, theme: &Theme) {
use super::page_availability;
let available =
page_availability::available_pages(app.selected_item(), &app.analysis().data_flow_graph);
let current_page = available
.iter()
.position(|&p| p == app.nav().detail_page)
.map(|i| i + 1)
.unwrap_or(1); let total_pages = available.len();
let page_name = app.nav().detail_page.name();
let position = format!(
"Detail View ({}/{}) [Page {}/{}] {}",
app.list().selected_index() + 1,
app.item_count(),
current_page,
total_pages,
page_name
);
let header_area = apply_horizontal_margin(area);
let header = Paragraph::new(vec![Line::from(vec![Span::styled(
position,
Style::default().fg(theme.accent()),
)])])
.block(Block::default().borders(Borders::BOTTOM));
frame.render_widget(header, header_area);
}
fn render_footer(frame: &mut Frame, app: &ResultsApp, area: Rect, theme: &Theme) {
let shortcuts = Line::from(vec![
Span::styled("q/Esc", Style::default().fg(theme.accent())),
Span::raw(":Back "),
Span::styled("←/→", Style::default().fg(theme.accent())),
Span::raw(":Pages "),
Span::styled("j/k", Style::default().fg(theme.accent())),
Span::raw(":Items "),
Span::styled("^D/^U", Style::default().fg(theme.accent())),
Span::raw(":Scroll "),
Span::styled("?", Style::default().fg(theme.accent())),
Span::raw(":Help"),
]);
let lines = if let Some(status) = app.status_message() {
let status_color = if status.starts_with('✓') {
theme.success()
} else {
theme.warning()
};
vec![
Line::from(vec![Span::styled(
status,
Style::default().fg(status_color),
)]),
shortcuts,
]
} else {
vec![shortcuts]
};
let footer_area = apply_horizontal_margin(area);
let footer = Paragraph::new(lines).block(Block::default().borders(Borders::TOP));
frame.render_widget(footer, footer_area);
}