use super::*;
use crate::app::{FolderPicker, Row};
use crate::i18n::Catalog;
use ratatui::widgets::{Borders, Clear};
pub(super) fn draw_picker(
f: &mut RenderTarget,
area: Rect,
p: &FolderPicker,
cat: &Catalog,
t: &Theme,
) -> Vec<(usize, Rect)> {
dim_backdrop(f, area, t);
let w = area.width.saturating_sub(6).clamp(46, 76).min(area.width);
let h = area.height.saturating_sub(4).clamp(14, 26).min(area.height);
let modal = centered_rect(area, w, h);
f.render_widget(Clear, modal);
let block = Block::new()
.borders(Borders::ALL)
.border_style(Style::new().fg(t.border_focus).bg(t.surface0))
.style(Style::new().bg(t.surface0));
let inner = block.inner(modal);
f.render_widget(block, modal);
f.render_widget(
Paragraph::new(Span::styled(
format!(" {}", cat.open_workspace),
Style::new().fg(t.text).bold(),
)),
Rect::new(inner.x, inner.y, inner.width, 1),
);
let path = p.path.display().to_string();
let path = trunc_tail(&path, inner.width.saturating_sub(2) as usize);
f.render_widget(
Paragraph::new(Span::styled(format!(" {path}"), Style::new().fg(t.accent))),
Rect::new(inner.x, inner.y + 1, inner.width, 1),
);
hline(f, inner.x, inner.y + 2, inner.width, t);
let footer_y = inner.bottom().saturating_sub(1);
hline(f, inner.x, footer_y.saturating_sub(1), inner.width, t);
if let Some(buf) = &p.creating {
f.render_widget(
Paragraph::new(Line::from(vec![
Span::styled(
format!(" {}: ", cat.act_new_folder),
Style::new().fg(t.subtext0),
),
Span::styled(buf.clone(), Style::new().fg(t.accent).bold()),
Span::styled("▏", Style::new().fg(t.accent)),
])),
Rect::new(inner.x, footer_y, inner.width, 1),
);
} else if let Some(e) = &p.error {
f.render_widget(
Paragraph::new(Span::styled(
format!(" error: {e}"),
Style::new().fg(t.coral),
)),
Rect::new(inner.x, footer_y, inner.width, 1),
);
} else {
f.render_widget(
Paragraph::new(hint_line(
&[
("↑↓", cat.act_move),
("⏎", cat.act_select),
("←", cat.act_up),
("n", cat.act_new_folder),
("esc", cat.act_cancel),
],
t,
)),
Rect::new(inner.x, footer_y, inner.width, 1),
);
}
let list = Rect::new(
inner.x + 1,
inner.y + 3,
inner.width.saturating_sub(2),
footer_y.saturating_sub(inner.y + 4),
);
let avail = list.height.max(1) as usize;
let scroll = p.cursor.saturating_sub(avail.saturating_sub(1));
let mut rects = Vec::new();
for (vi, i) in (scroll..p.row_count()).take(avail).enumerate() {
let y = list.y + vi as u16;
let row_rect = Rect::new(list.x, y, list.width, 1);
let sel = i == p.cursor;
if sel {
fill_bg(f, row_rect, t.sel_bg);
}
let (icon, label, fg) = match p.row(i) {
Row::OpenFolder => ("✓", cat.open_this_folder.to_string(), t.accent),
Row::OpenWorktree => ("⎇", cat.open_with_worktree.to_string(), t.accent),
Row::Up => ("↑", "..".to_string(), t.subtext0),
Row::Entry(idx) => {
let e = &p.entries[idx];
if e.is_dir {
("▪", format!("{}/", e.name), t.text)
} else {
("·", e.name.clone(), t.overlay0)
}
}
};
f.render_widget(
Paragraph::new(Line::from(vec![
Span::styled(if sel { "▸ " } else { " " }, Style::new().fg(t.accent)),
Span::styled(format!("{icon} "), Style::new().fg(fg)),
Span::styled(
trunc_tail(&label, list.width.saturating_sub(5) as usize),
Style::new().fg(fg),
),
])),
Rect::new(list.x, y, list.width, 1),
);
rects.push((i, row_rect));
}
rects
}
fn trunc_tail(s: &str, max: usize) -> String {
let n = s.chars().count();
if n <= max || max == 0 {
return s.to_string();
}
let tail: String = s.chars().skip(n - max.saturating_sub(1)).collect();
format!("…{tail}")
}
pub(super) fn draw_worktree_prompt(
f: &mut RenderTarget,
area: Rect,
buf: &str,
error: Option<&str>,
hover: Option<(u16, u16)>,
cat: &Catalog,
t: &Theme,
) -> (Option<Rect>, Option<Rect>) {
dim_backdrop(f, area, t);
let w = area.width.saturating_sub(6).clamp(36, 64).min(area.width);
let modal = centered_rect(area, w, 6);
f.render_widget(Clear, modal);
let block = Block::new()
.borders(Borders::ALL)
.border_style(Style::new().fg(t.border_focus).bg(t.surface0))
.style(Style::new().bg(t.surface0));
let inner = block.inner(modal);
f.render_widget(block, modal);
f.render_widget(
Paragraph::new(Span::styled(
format!(" {}", cat.new_git_worktree),
Style::new().fg(t.text).bold(),
)),
Rect::new(inner.x, inner.y, inner.width, 1),
);
f.render_widget(
Paragraph::new(Line::from(vec![
Span::styled(format!(" {}: ", cat.branch), Style::new().fg(t.subtext0)),
Span::styled(buf.to_string(), Style::new().fg(t.accent).bold()),
Span::styled("▏", Style::new().fg(t.accent)),
])),
Rect::new(inner.x, inner.y + 2, inner.width, 1),
);
let bottom = Rect::new(inner.x, inner.bottom().saturating_sub(1), inner.width, 1);
if let Some(e) = error {
let e = trunc_tail(e, inner.width.saturating_sub(2) as usize);
f.render_widget(
Paragraph::new(Span::styled(format!(" {e}"), Style::new().fg(t.coral))),
bottom,
);
(None, None) } else {
let (c, x) = footer_hints(f, bottom, cat.act_create, cat.act_cancel, hover, t);
(Some(c), Some(x))
}
}
pub(super) fn draw_tab_rename(
f: &mut RenderTarget,
area: Rect,
buf: &str,
hover: Option<(u16, u16)>,
cat: &Catalog,
t: &Theme,
) -> (Option<Rect>, Option<Rect>) {
draw_rename(f, area, cat.rename_tab, buf, hover, cat, t)
}
pub(super) fn draw_ws_rename(
f: &mut RenderTarget,
area: Rect,
buf: &str,
hover: Option<(u16, u16)>,
cat: &Catalog,
t: &Theme,
) -> (Option<Rect>, Option<Rect>) {
draw_rename(f, area, cat.menu_rename, buf, hover, cat, t)
}
fn draw_rename(
f: &mut RenderTarget,
area: Rect,
title: &str,
buf: &str,
hover: Option<(u16, u16)>,
cat: &Catalog,
t: &Theme,
) -> (Option<Rect>, Option<Rect>) {
dim_backdrop(f, area, t);
let w = area.width.saturating_sub(6).clamp(36, 64).min(area.width);
let modal = centered_rect(area, w, 6);
f.render_widget(Clear, modal);
let block = Block::new()
.borders(Borders::ALL)
.border_style(Style::new().fg(t.border_focus).bg(t.surface0))
.style(Style::new().bg(t.surface0));
let inner = block.inner(modal);
f.render_widget(block, modal);
f.render_widget(
Paragraph::new(Span::styled(
format!(" {title}"),
Style::new().fg(t.text).bold(),
)),
Rect::new(inner.x, inner.y, inner.width, 1),
);
f.render_widget(
Paragraph::new(Line::from(vec![
Span::raw(" "),
Span::styled(buf.to_string(), Style::new().fg(t.accent).bold()),
Span::styled("▏", Style::new().fg(t.accent)),
])),
Rect::new(inner.x, inner.y + 2, inner.width, 1),
);
let footer = Rect::new(inner.x, inner.bottom().saturating_sub(1), inner.width, 1);
let (c, x) = footer_hints(f, footer, cat.act_save, cat.act_cancel, hover, t);
(Some(c), Some(x))
}
fn footer_hints(
f: &mut RenderTarget,
row: Rect,
commit: &str,
cancel: &str,
hover: Option<(u16, u16)>,
t: &Theme,
) -> (Rect, Rect) {
let cw = super::display_width(&format!("⏎ {commit}")) as u16 + 2;
let xw = super::display_width(&format!("esc {cancel}")) as u16 + 2;
let commit_rect = Rect::new(row.x, row.y, cw.min(row.width), 1);
let sep_x = row.x + cw; let cancel_x = (sep_x + 1).min(row.right());
let cancel_rect = Rect::new(
cancel_x,
row.y,
xw.min(row.right().saturating_sub(cancel_x)),
1,
);
let over = |r: Rect| hover.is_some_and(|(c, hr)| c >= r.x && c < r.right() && hr == r.y);
draw_hint(f, commit_rect, "⏎", commit, over(commit_rect), t);
if sep_x < row.right() {
f.render_widget(
Paragraph::new(Span::styled("·", Style::new().fg(t.overlay0))),
Rect::new(sep_x, row.y, 1, 1),
);
}
draw_hint(f, cancel_rect, "esc", cancel, over(cancel_rect), t);
(commit_rect, cancel_rect)
}
fn draw_hint(f: &mut RenderTarget, rect: Rect, key: &str, label: &str, hot: bool, t: &Theme) {
if hot {
fill_bg(f, rect, t.accent);
}
let (kfg, lfg) = if hot {
(t.crust, t.crust)
} else {
(t.accent, t.subtext1)
};
f.render_widget(
Paragraph::new(Line::from(vec![
Span::raw(" "),
Span::styled(key.to_string(), Style::new().fg(kfg).bold()),
Span::styled(format!(" {label} "), Style::new().fg(lfg)),
])),
rect,
);
}
fn centered_rect(area: Rect, w: u16, h: u16) -> Rect {
let w = w.min(area.width);
let h = h.min(area.height);
Rect::new(
area.x + (area.width - w) / 2,
area.y + (area.height - h) / 2,
w,
h,
)
}
fn dim_backdrop(f: &mut RenderTarget, area: Rect, t: &Theme) {
let buf = f.buffer_mut();
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
let cell = &mut buf[(x, y)];
cell.set_fg(t.overlay0);
cell.set_bg(t.crust);
}
}
}
fn hline(f: &mut RenderTarget, x: u16, y: u16, w: u16, t: &Theme) {
let buf = f.buffer_mut();
for i in 0..w {
buf[(x + i, y)]
.set_symbol("─")
.set_style(Style::new().fg(t.surface1).bg(t.surface0));
}
}
fn fill_bg(f: &mut RenderTarget, rect: Rect, color: Color) {
let buf = f.buffer_mut();
for y in rect.y..rect.bottom() {
for x in rect.x..rect.right() {
buf[(x, y)].set_bg(color);
}
}
}