use super::super::app::App;
use super::super::app::dialog_keys::{DialogScope, dialog_keys};
use super::super::render::{chrome, hints};
use super::theme::{self, Role};
use ratatui::{
Frame,
layout::Rect,
style::{Modifier, Style},
text::{Line, Span},
widgets::{Clear, Paragraph, Wrap},
};
pub(super) fn render_file_picker(f: &mut Frame, app: &App, area: Rect) {
let mut lines: Vec<Line> = Vec::new();
lines.push(Line::from(vec![
Span::styled(
"📁 File Picker",
Style::default()
.fg(theme::role(Role::Gray))
.add_modifier(Modifier::BOLD),
),
Span::styled(" │ ", Style::default().fg(theme::role(Role::GrayDim))),
Span::styled(
app.file_picker_current_dir.to_string_lossy().to_string(),
Style::default().fg(theme::role(Role::Accent)),
),
]));
if app.file_picker_search.is_empty() {
lines.push(Line::from(vec![Span::styled(
" Type to filter...",
Style::default().fg(theme::role(Role::GrayDim)),
)]));
} else {
lines.push(Line::from(vec![
Span::styled(" 🔍 ", Style::default().fg(theme::role(Role::GrayDim))),
Span::styled(
&app.file_picker_search,
Style::default().fg(theme::role(Role::Accent)),
),
]));
}
lines.push(Line::from(""));
let filtered = &app.file_picker_filtered;
let visible_items = (area.height as usize).saturating_sub(7);
let start = app.file_picker_scroll_offset;
let end = (start + visible_items).min(filtered.len());
for (display_idx, &file_idx) in filtered.iter().enumerate().skip(start).take(end - start) {
let path = &app.file_picker_files[file_idx];
let is_selected = display_idx == app.file_picker_selected;
let is_dir = path.is_dir();
let icon = if path.ends_with("..") {
"📂 .."
} else if is_dir {
"📂"
} else {
"📄"
};
let label = if app.file_picker_recursive {
path.strip_prefix(&app.working_directory)
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| {
path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("?")
.to_string()
})
} else {
path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("?")
.to_string()
};
let style = if is_selected {
Style::default()
.fg(theme::role(Role::Ink))
.bg(theme::role(Role::Gray))
.add_modifier(Modifier::BOLD)
} else if is_dir {
Style::default().fg(theme::role(Role::Gray))
} else {
Style::default().fg(theme::role(Role::TextPrimary))
};
let prefix = if is_selected { "▶ " } else { " " };
lines.push(Line::from(vec![
Span::styled(prefix, style),
Span::styled(format!("{} {}", icon, label), style),
]));
}
if filtered.len() > visible_items {
lines.push(Line::from(""));
lines.push(Line::from(vec![Span::styled(
format!("Showing {}-{} of {} files", start + 1, end, filtered.len()),
Style::default().fg(theme::role(Role::GrayDim)),
)]));
}
f.render_widget(Clear, area);
let widget = Paragraph::new(lines)
.block(chrome::modal_block(
" Select a file ",
theme::role(Role::Accent),
))
.wrap(Wrap { trim: false });
f.render_widget(widget, area);
hints::render_footer(
f,
chrome::footer_row(area),
dialog_keys(DialogScope::FilePicker),
);
}
pub(super) fn render_directory_picker(f: &mut Frame, app: &App, area: Rect) {
let mut lines: Vec<Line> = Vec::new();
lines.push(Line::from(vec![
Span::styled(
"📂 Directory Picker",
Style::default()
.fg(theme::role(Role::Gray))
.add_modifier(Modifier::BOLD),
),
Span::styled(" │ ", Style::default().fg(theme::role(Role::GrayDim))),
Span::styled(
app.file_picker_current_dir.to_string_lossy().to_string(),
Style::default().fg(theme::role(Role::Accent)),
),
]));
lines.push(Line::from(""));
let visible_items = (area.height as usize).saturating_sub(6);
let start = app.file_picker_scroll_offset;
let end = (start + visible_items).min(app.file_picker_files.len());
for (idx, path) in app
.file_picker_files
.iter()
.enumerate()
.skip(start)
.take(end - start)
{
let is_selected = idx == app.file_picker_selected;
let icon = if path.ends_with("..") {
"📂 .."
} else {
"📂"
};
let filename = if path.ends_with("..") {
".."
} else {
path.file_name().and_then(|n| n.to_str()).unwrap_or("?")
};
let style = if is_selected {
Style::default()
.fg(theme::role(Role::Ink))
.bg(theme::role(Role::Gray))
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(theme::role(Role::Gray))
};
let prefix = if is_selected { "▶ " } else { " " };
let display = if path.ends_with("..") {
icon.to_string()
} else {
format!("{} {}", icon, filename)
};
lines.push(Line::from(vec![
Span::styled(prefix, style),
Span::styled(display, style),
]));
}
if app.file_picker_files.len() > visible_items {
lines.push(Line::from(""));
lines.push(Line::from(vec![Span::styled(
format!(
"Showing {}-{} of {}",
start + 1,
end,
app.file_picker_files.len()
),
Style::default().fg(theme::role(Role::GrayDim)),
)]));
}
f.render_widget(Clear, area);
let widget = Paragraph::new(lines)
.block(chrome::modal_block(
" Change working directory ",
theme::role(Role::Accent),
))
.wrap(Wrap { trim: false });
f.render_widget(widget, area);
hints::render_footer(
f,
chrome::footer_row(area),
dialog_keys(DialogScope::DirectoryPicker),
);
}
pub(super) fn render_restart_dialog(f: &mut Frame, area: Rect) {
let status = "Build successful";
let dialog_width = 50u16.min(area.width.saturating_sub(4));
let dialog_area = chrome::centered(area, dialog_width, 8);
f.render_widget(Clear, dialog_area);
let lines = vec![
Line::from(""),
Line::from(Span::styled(
format!(" {}", status),
Style::default()
.fg(theme::role(Role::AccentTeal))
.add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(" Restart with new binary?"),
Line::from(""),
];
let dialog = Paragraph::new(lines).block(chrome::modal_block(
" Rebuild Complete ",
theme::role(Role::AccentTeal),
));
f.render_widget(dialog, dialog_area);
hints::render_footer(
f,
chrome::footer_row(dialog_area),
dialog_keys(DialogScope::RestartPending),
);
}
pub(super) fn render_update_dialog(f: &mut Frame, app: &App, area: Rect) {
let version = app.update_available_version.as_deref().unwrap_or("unknown");
let dialog_width = 55u16.min(area.width.saturating_sub(4));
let dialog_area = chrome::centered(area, dialog_width, 8);
f.render_widget(Clear, dialog_area);
let lines = vec![
Line::from(""),
Line::from(Span::styled(
format!(" v{} -> v{}", crate::VERSION, version),
Style::default()
.fg(theme::role(Role::AccentTeal))
.add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(" Update now?"),
Line::from(""),
];
let dialog = Paragraph::new(lines).block(chrome::modal_block(
" Update Available ",
theme::role(Role::AccentTeal),
));
f.render_widget(dialog, dialog_area);
hints::render_footer(
f,
chrome::footer_row(dialog_area),
dialog_keys(DialogScope::UpdatePrompt),
);
}