use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
use ratatui::Frame;
use crate::app::App;
pub fn render(app: &App, frame: &mut Frame, area: Rect) {
let theme = app.theme();
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.border_active))
.style(Style::default().bg(theme.bg))
.title(" GitKraft ")
.title_alignment(Alignment::Center);
let mut lines: Vec<Line> = vec![
Line::from(""),
Line::from(""),
Line::from(vec![Span::styled(
" ╔═╗╦╔╦╗╦╔═╦═╗╔═╗╔═╗╔╦╗",
Style::default().fg(theme.accent),
)])
.alignment(Alignment::Center),
Line::from(vec![Span::styled(
" ║ ╦║ ║ ╠╩╗╠╦╝╠═╣╠╣ ║ ",
Style::default().fg(theme.accent),
)])
.alignment(Alignment::Center),
Line::from(vec![Span::styled(
" ╚═╝╩ ╩ ╩ ╩╩╚═╩ ╩╚ ╩ ",
Style::default().fg(theme.accent),
)])
.alignment(Alignment::Center),
Line::from(""),
Line::from(Span::styled(
"A modern Git IDE for the terminal",
Style::default().fg(theme.text_secondary),
))
.alignment(Alignment::Center),
Line::from(""),
Line::from(""),
Line::from(Span::styled(
"─── Actions ───",
Style::default().fg(theme.text_muted),
))
.alignment(Alignment::Center),
Line::from(""),
Line::from(vec![
Span::styled(
" [o]",
Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD),
),
Span::styled(
" Browse & Open Repository",
Style::default().fg(theme.text_primary),
),
])
.alignment(Alignment::Center),
Line::from(vec![
Span::styled(
" [i]",
Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD),
),
Span::styled(
" Init New Repository (cwd)",
Style::default().fg(theme.text_primary),
),
])
.alignment(Alignment::Center),
Line::from(vec![
Span::styled(
" [q]",
Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD),
),
Span::styled(" Quit", Style::default().fg(theme.text_primary)),
])
.alignment(Alignment::Center),
Line::from(""),
];
if !app.recent_repos.is_empty() {
lines.push(
Line::from(Span::styled(
"─── Recent Repositories ───",
Style::default().fg(theme.text_muted),
))
.alignment(Alignment::Center),
);
lines.push(Line::from(""));
for (i, entry) in app.recent_repos.iter().take(9).enumerate() {
let path_str = entry.path.display().to_string();
let repo_name = entry
.path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&path_str);
let parent = entry
.path
.parent()
.map(|p| format!("{}/", p.display()))
.unwrap_or_default();
lines.push(
Line::from(vec![
Span::styled(
format!(" [{}] ", i + 1),
Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD),
),
Span::styled(parent, Style::default().fg(theme.text_muted)),
Span::styled(
repo_name.to_string(),
Style::default()
.fg(theme.accent)
.add_modifier(Modifier::BOLD),
),
])
.alignment(Alignment::Center),
);
}
}
let paragraph = Paragraph::new(lines)
.alignment(Alignment::Center)
.block(block);
let centered = centered_rect(60, 70, area);
frame.render_widget(paragraph, centered);
}
pub fn render_browser(app: &mut App, frame: &mut Frame, area: Rect) {
let theme = app.theme();
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.border_active))
.style(Style::default().bg(theme.bg))
.title(format!(" 📂 {} ", app.browser_dir.display()))
.title_alignment(Alignment::Left);
let items: Vec<ListItem> = app
.browser_entries
.iter()
.map(|path| {
let name = path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let is_git = path.join(".git").exists();
let (icon, style) = if is_git {
(
"⊙ ",
Style::default()
.fg(theme.success)
.add_modifier(Modifier::BOLD),
)
} else if name.starts_with('.') {
(" ", Style::default().fg(theme.text_muted))
} else {
("📁 ", Style::default().fg(theme.text_primary))
};
ListItem::new(Line::from(vec![
Span::styled(icon, style),
Span::styled(name, style),
]))
})
.collect();
let list = List::new(items)
.block(block)
.highlight_style(
Style::default()
.fg(theme.accent)
.add_modifier(Modifier::BOLD)
.bg(theme.sel_bg),
)
.highlight_symbol("▸ ");
let help = Paragraph::new(Line::from(vec![
Span::styled(
"↑↓",
Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD),
),
Span::styled(" navigate ", Style::default().fg(theme.text_muted)),
Span::styled(
"Enter",
Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD),
),
Span::styled(" open ", Style::default().fg(theme.text_muted)),
Span::styled(
"Backspace",
Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD),
),
Span::styled(" go up ", Style::default().fg(theme.text_muted)),
Span::styled(
"o",
Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD),
),
Span::styled(" open as repo ", Style::default().fg(theme.text_muted)),
Span::styled(
"Esc",
Style::default()
.fg(theme.warning)
.add_modifier(Modifier::BOLD),
),
Span::styled(" cancel", Style::default().fg(theme.text_muted)),
]));
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(3), Constraint::Length(1)])
.split(area);
frame.render_stateful_widget(list, chunks[0], &mut app.browser_list_state);
frame.render_widget(help, chunks[1]);
}
fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect {
let vertical = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(area);
let horizontal = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(vertical[1]);
horizontal[1]
}