use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Clear, Paragraph};
use crate::DiscoveryCategory;
use crate::app::{App, DISCOVERY_FLASH_MS};
use crate::ui::theme;
fn rows(app: &App) -> Vec<(DiscoveryCategory, &'static str, &'static str, usize)> {
use DiscoveryCategory::*;
vec![
(
StatuslineMode,
"Mode chip",
"click: toggle vim/standard · right-click: input menu",
usize::from(app.rects.statusline_mode_chip.is_some()),
),
(
StatuslineBranch,
"Branch chip",
"click: commit graph · right-click: git ops menu",
usize::from(app.rects.statusline_branch_chip.is_some()),
),
(
StatuslineWorkspace,
"Workspace chip",
"click: switch repo · right-click: workspace menu",
usize::from(app.rects.statusline_workspace_chip.is_some()),
),
(
StatuslineClock,
"Clock chip",
"click: local↔UTC · right-click: clock menu",
usize::from(app.rects.statusline_clock_chip.is_some()),
),
(
BufferlineTabs,
"Bufferline tabs",
"click: focus · middle: close · right-click: tab menu",
app.rects.bufferline_tabs.len(),
),
(
RailGitHeader,
"> GIT rail header",
"Fetch / Pull / Push / Stage all / Commit / Graph",
app.rects.rail_git_header_buttons.len(),
),
(
EditorGutter,
"Editor gutter",
"right-click line: breakpoint / goto def / refs / blame…",
app.rects.editor_gutters.len(),
),
(
DiffToolbar,
"Diff toolbar",
"Hunk / Inline / Split / Wrap / Close chips",
app.rects.diff_toolbar_buttons.len(),
),
(
FoldChips,
"Fold chips (⋯)",
"click to expand the folded block",
app.rects.fold_chips.len(),
),
(
CodeLensChips,
"Code-lens chips (⚡)",
"click to run the lens command",
app.rects.code_lens_chips.len(),
),
(
SplitDividers,
"Split dividers",
"hover turns yellow · drag to resize",
app.rects.split_dividers.len(),
),
]
}
pub fn draw(frame: &mut Frame, app: &mut App, screen: Rect) {
if !app.show_discovery_overlay {
app.rects.discovery_rows.clear();
return;
}
let t = theme::cur();
let rows = rows(app);
let title = " Click Discovery — F1 / Esc to close · click row to flash ";
let inner_w = rows
.iter()
.map(|(_, label, detail, _)| label.chars().count() + 2 + detail.chars().count() + 6)
.max()
.unwrap_or(50)
.max(title.chars().count() + 4);
let w = (inner_w as u16 + 4).min(screen.width);
let h = (rows.len() as u16 + 2 + 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);
app.rects.discovery_rows.clear();
let inner_x = area.x + 1;
let inner_w_cells = area.width.saturating_sub(2);
for (i, (cat, _, _, _)) in rows.iter().enumerate() {
let row_y = area.y + 1 + i as u16;
if row_y >= area.y + area.height.saturating_sub(2) {
break;
}
app.rects.discovery_rows.push((
Rect {
x: inner_x,
y: row_y,
width: inner_w_cells,
height: 1,
},
*cat,
));
}
let block = crate::ui::design_tokens::modal_panel(title);
let mut lines: Vec<Line<'static>> = Vec::with_capacity(rows.len() + 1);
let active_flash = app
.discovery_flash
.filter(|(_, since)| since.elapsed().as_millis() < DISCOVERY_FLASH_MS as u128)
.map(|(c, _)| c);
for (cat, label, detail, count) in rows.iter() {
let live = *count > 0;
let is_flashing = active_flash == Some(*cat);
let count_chip = if live {
format!("[{count}]")
} else {
"[ ]".into()
};
let count_style = if is_flashing {
Style::default()
.fg(t.bg_darker)
.bg(t.yellow)
.add_modifier(Modifier::BOLD)
} else if live {
Style::default()
.fg(t.bg_darker)
.bg(t.green)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(t.comment).bg(t.bg2)
};
let label_style = if is_flashing {
Style::default()
.fg(t.yellow)
.bg(t.bg2)
.add_modifier(Modifier::BOLD | Modifier::UNDERLINED)
} else if live {
Style::default().fg(t.fg).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(t.comment)
};
let detail_style = if live {
Style::default().fg(t.comment)
} else {
Style::default().fg(t.bg3)
};
let spans = vec![
Span::styled(" ", Style::default().bg(t.bg2)),
Span::styled(count_chip, count_style),
Span::styled(" ", Style::default().bg(t.bg2)),
Span::styled(label.to_string(), label_style),
Span::styled(" ", Style::default().bg(t.bg2)),
Span::styled(detail.to_string(), detail_style),
];
lines.push(Line::from(spans));
}
lines.push(Line::from(Span::styled(
" green count = visible now · click row to flash rects ".to_string(),
Style::default()
.fg(t.comment)
.bg(t.bg2)
.add_modifier(Modifier::ITALIC),
)));
frame.render_widget(Paragraph::new(lines).block(block), area);
}
pub fn draw_flash(frame: &mut Frame, app: &App, _screen: Rect) {
let Some((cat, since)) = app.discovery_flash else {
return;
};
if since.elapsed().as_millis() >= DISCOVERY_FLASH_MS as u128 {
return;
}
let t = theme::cur();
let highlight = Style::default()
.fg(t.bg_darker)
.bg(t.yellow)
.add_modifier(Modifier::BOLD);
let mut targets: Vec<Rect> = Vec::new();
use DiscoveryCategory::*;
match cat {
StatuslineMode => targets.extend(app.rects.statusline_mode_chip),
StatuslineBranch => targets.extend(app.rects.statusline_branch_chip),
StatuslineWorkspace => targets.extend(app.rects.statusline_workspace_chip),
StatuslineClock => targets.extend(app.rects.statusline_clock_chip),
BufferlineTabs => targets.extend(app.rects.bufferline_tabs.iter().map(|(r, _)| *r)),
RailGitHeader => targets.extend(app.rects.rail_git_header_buttons.iter().map(|(r, _)| *r)),
EditorGutter => targets.extend(app.rects.editor_gutters.iter().map(|(r, _)| *r)),
DiffToolbar => targets.extend(app.rects.diff_toolbar_buttons.iter().map(|(r, _, _)| *r)),
FoldChips => targets.extend(app.rects.fold_chips.iter().map(|(r, _, _)| *r)),
CodeLensChips => targets.extend(app.rects.code_lens_chips.iter().map(|(r, _, _)| *r)),
SplitDividers => targets.extend(app.rects.split_dividers.iter().map(|d| d.rect)),
}
for r in targets {
if r.width == 0 || r.height == 0 {
continue;
}
frame.render_widget(Clear, r);
frame.render_widget(
Paragraph::new(Span::styled(" ".repeat(r.width as usize), highlight)),
r,
);
}
}