use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Clear, Paragraph};
use crate::app::{App, StartupPickerAction};
use crate::ui::theme;
pub fn draw(frame: &mut Frame, app: &App, screen: Rect) {
let Some(picker) = app.startup_picker.as_ref() else {
return;
};
let t = theme::cur();
let mut rows: Vec<(String, String)> = vec![
(
"1".to_string(),
"New file (in current workspace)".to_string(),
),
("2".to_string(), "Open file…".to_string()),
("3".to_string(), "Open folder…".to_string()),
];
for (i, w) in app.config.workspaces.iter().enumerate() {
if i >= 6 {
break; }
let key = (b'4' + i as u8) as char;
rows.push((key.to_string(), format!("Open: {}", w.name)));
}
let configured_count = app.config.workspaces.len().min(6);
for (i, sub) in project_subdirs(app).into_iter().enumerate() {
let row_idx = 3 + configured_count + i;
let key = if row_idx < 9 {
((b'1' + row_idx as u8) as char).to_string()
} else {
" ".to_string()
};
let name = sub
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("?")
.to_string();
rows.push((key, format!("Open project: {name}")));
}
let title = " Open mnml — Esc to skip ";
let label_w = rows
.iter()
.map(|(_, v)| v.chars().count())
.max()
.unwrap_or(40);
let inner_w = (4 + label_w).max(title.chars().count() + 4);
let w = (inner_w as u16 + 4).min(screen.width);
let n_lines = rows.len() as u16 + 2; let h = (n_lines + 2).min(screen.height);
let x = screen
.x
.saturating_add((screen.width.saturating_sub(w)) / 2);
let y = screen
.y
.saturating_add((screen.height.saturating_sub(h)) / 3);
let area = Rect {
x,
y,
width: w,
height: h,
};
frame.render_widget(Clear, area);
let block = crate::ui::design_tokens::modal_panel(title);
let mut lines: Vec<Line<'static>> = Vec::with_capacity(rows.len() + 2);
lines.push(Line::from(Span::styled(
" Pick a workspace or action: ".to_string(),
Style::default().fg(t.fg).add_modifier(Modifier::BOLD),
)));
for (i, (k, v)) in rows.iter().enumerate() {
let marker = if i == picker.selected { "▸" } else { " " };
let row_style = if i == picker.selected {
Style::default().fg(t.cyan).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(t.fg)
};
lines.push(Line::from(vec![
Span::raw(format!(" {marker} ")),
Span::styled(
format!("[{k}] "),
Style::default().fg(t.yellow).add_modifier(Modifier::BOLD),
),
Span::styled(v.to_string(), row_style),
]));
}
lines.push(Line::from(Span::styled(
" ↑↓ move · Enter select · Esc skip ".to_string(),
Style::default()
.fg(t.comment)
.add_modifier(Modifier::ITALIC),
)));
frame.render_widget(Paragraph::new(lines).block(block), area);
}
pub(crate) fn project_subdirs(app: &App) -> Vec<std::path::PathBuf> {
const CAP: usize = 6;
let root = app.config.ui.projects_dir.trim();
if root.is_empty() {
return Vec::new();
}
let Ok(entries) = std::fs::read_dir(root) else {
return Vec::new();
};
let mut out: Vec<std::path::PathBuf> = entries
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_ok_and(|t| t.is_dir()))
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| !n.starts_with('.') && n != "node_modules" && n != "target")
})
.map(|e| e.path())
.collect();
out.sort();
out.truncate(CAP);
out
}
pub fn row_count(app: &App) -> usize {
let configured = app.config.workspaces.len().min(6);
let projects = project_subdirs(app).len();
3 + configured + projects
}
pub fn action_for(app: &App, idx: usize) -> Option<StartupPickerAction> {
let configured = app.config.workspaces.len();
let configured_capped = configured.min(6);
match idx {
0 => Some(StartupPickerAction::NewFile),
1 => Some(StartupPickerAction::OpenFile),
2 => Some(StartupPickerAction::OpenFolder),
n if n < 3 + configured_capped => {
let ws = n - 3;
Some(StartupPickerAction::SwitchWorkspace(ws + 1))
}
n => {
let proj_idx = n - 3 - configured_capped;
let subs = project_subdirs(app);
subs.get(proj_idx)
.cloned()
.map(StartupPickerAction::OpenProject)
}
}
}
pub fn row_for_key(app: &App, ch: char) -> Option<usize> {
if !ch.is_ascii_digit() || ch == '0' {
return None;
}
let idx = (ch as u8 - b'1') as usize;
if idx < row_count(app) {
Some(idx)
} else {
None
}
}