use ratatui::Frame;
use ratatui::layout::{Alignment, Rect};
use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Padding, Paragraph, Wrap};
use usize_cast::IntoUsize as _;
use crate::ui::rendering::scrollbar::{Scroll, render_vscrollbar};
use crate::ui::state::{App, ViewMode};
use crate::ui::{
CLR_BAD_WINDING, CLR_CONTEXT_FEATURE, CLR_CONTEXT_LAYER, CLR_DIMMED, CLR_EXTENT, CLR_HOVERED,
CLR_INNER_RING, CLR_INNER_RING_SEL, CLR_LINE, CLR_MULTI_LINE, CLR_MULTI_POINT,
CLR_MULTI_POLYGON, CLR_POINT, CLR_POLYGON, CLR_SELECTED, CLR_TRIANGLE, STYLE_LABEL,
STYLE_SELECTED, block_with_title,
};
const CLR_ERROR: Color = Color::Red;
pub fn render_error_popup(f: &mut Frame<'_>, app: &App) {
let Some((ref filename, ref msg)) = app.error_popup else {
return;
};
let area = f.area();
let lines: Vec<Line<'_>> = msg
.trim()
.lines()
.map(|s| Line::from(s.to_string()))
.collect();
let line_count = lines.len();
let height = u16::try_from(line_count)
.unwrap_or(u16::MAX)
.saturating_add(5)
.min(28)
.min(area.height.saturating_sub(4));
let width = 80.min(area.width.saturating_sub(8));
let popup = Rect::new(
area.x + (area.width.saturating_sub(width)) / 2,
area.y + (area.height.saturating_sub(height)) / 2,
width,
height,
);
f.render_widget(ratatui::widgets::Clear, popup);
let error_block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(CLR_ERROR))
.title_style(
Style::default()
.fg(CLR_ERROR)
.add_modifier(ratatui::style::Modifier::BOLD),
)
.title_top(format!(" Unable to open {filename} "))
.title_bottom(Line::from("any key to close").right_aligned())
.padding(Padding::uniform(1));
let para = Paragraph::new(lines)
.block(error_block)
.alignment(Alignment::Center)
.wrap(Wrap { trim: true });
f.render_widget(para, popup);
}
pub fn render_help_overlay(f: &mut Frame<'_>, app: &mut App) {
let area = f.area();
let lines = match app.mode {
ViewMode::FileBrowser => help_file_browser(),
ViewMode::LayerOverview => help_layer_overview(),
ViewMode::MbtilesMap => help_mbtiles_map(),
};
let total = lines.len();
let height = u16::try_from(total)
.unwrap_or(u16::MAX)
.saturating_add(2)
.min(area.height.saturating_sub(2));
let width = 62.min(area.width.saturating_sub(4));
let popup = Rect::new(
area.x + (area.width.saturating_sub(width)) / 2,
area.y + (area.height.saturating_sub(height)) / 2,
width,
height,
);
let inner = height.saturating_sub(2).into_usize();
app.help_scroll = app.help_scroll.min(Scroll::max_pos(total, inner));
f.render_widget(ratatui::widgets::Clear, popup);
let para = Paragraph::new(lines)
.block(block_with_title(
"Help (↑/↓/scroll to navigate, any other key to close)",
))
.scroll((app.help_scroll, 0));
f.render_widget(para, popup);
render_vscrollbar(f, popup, Scroll::new(total, inner, app.help_scroll));
}
fn heading(text: &str) -> Line<'static> {
Line::from(Span::styled(text.to_string(), STYLE_SELECTED))
}
fn key(k: &str, desc: &str) -> Line<'static> {
Line::from(vec![
Span::styled(format!(" {k:<20}"), STYLE_LABEL),
Span::raw(desc.to_string()),
])
}
fn color(c: Color, label: &str, desc: &str) -> Line<'static> {
Line::from(vec![
Span::raw(" "),
Span::styled(format!("{label:<20}"), Style::default().fg(c)),
Span::raw(desc.to_string()),
])
}
fn help_file_browser() -> Vec<Line<'static>> {
vec![
heading("Keyboard"),
key("? h F1", "Toggle this help"),
key("q Ctrl+c", "Quit"),
key("Up/Down j/k", "Navigate file list"),
key("PageUp/PageDown", "Scroll by page"),
key("Home/End", "Jump to first/last"),
key("Enter", "Open selected MLT file"),
key("Esc", "Quit"),
Line::from(""),
heading("Mouse"),
key("Click row", "Select file"),
key("Double-click row", "Open file"),
key("Click header", "Sort by column (once the scan is done)"),
key("Scroll", "Navigate file list"),
key("Drag divider", "Resize panels"),
Line::from(""),
heading("Filter Panel"),
key("Click checkbox", "Toggle geometry/algorithm filter"),
key("Click [Reset]", "Clear all filters"),
]
}
fn help_mbtiles_map() -> Vec<Line<'static>> {
vec![
heading("Keyboard"),
key("? h F1", "Toggle this help"),
key("q Ctrl+c Esc", "Quit"),
Line::from(""),
heading("Mouse"),
key("Scroll on map", "Zoom ±0.5 levels (centred on cursor)"),
key("Left-drag on map", "Pan"),
key("Hover over map", "Inspect feature properties in left panel"),
Line::from(""),
heading("Map Colors"),
color(CLR_POINT, "Magenta", "Point"),
color(CLR_LINE, "Cyan", "LineString"),
color(CLR_POLYGON, "Blue", "Polygon"),
color(CLR_EXTENT, "Dark gray", "Tile boundaries"),
color(CLR_HOVERED, "White", "Hovered feature"),
]
}
fn help_layer_overview() -> Vec<Line<'static>> {
vec![
heading("Keyboard"),
key("? h F1", "Toggle this help"),
key("q Ctrl+c", "Quit"),
key("Esc", "Back to file browser"),
key("Up/Down j/k", "Navigate feature tree"),
key("PageUp/PageDown", "Scroll by page"),
key("Home/End", "Jump to first/last"),
key("Enter", "Expand/collapse layer or feature"),
key("+ = Right", "Expand selected node"),
key("-", "Collapse (or jump to parent)"),
key("*", "Expand/collapse all layers"),
key("Left", "Jump to parent node"),
key("< >", "Scroll the tree sideways"),
key("Shift+Left/Right", "Scroll the tree sideways"),
key("Ctrl+h / Ctrl+l", "Resize left/right split"),
key("Shift+J / Shift+K", "Resize top/bottom split"),
Line::from(""),
heading("Mouse"),
key("Click tree item", "Select (drill into level)"),
key("Double-click", "Expand/collapse"),
key("Hover tree/map", "Highlight the layer, feature, or part"),
key("", "at the current level"),
key("Click on map", "Select the hovered item"),
key("Scroll panels", "Scroll tree/properties/geometry"),
key("Scroll sideways", "Scroll the tree horizontally"),
key("Drag dividers", "Resize panels"),
Line::from(""),
heading("Map Colors"),
color(CLR_POINT, "Magenta", "Point"),
color(CLR_MULTI_POINT, "Light magenta", "MultiPoint"),
color(CLR_LINE, "Cyan", "LineString"),
color(CLR_MULTI_LINE, "Light cyan", "MultiLineString"),
color(CLR_POLYGON, "Blue", "Polygon (outer ring, CCW)"),
color(CLR_MULTI_POLYGON, "Light blue", "MultiPolygon"),
color(CLR_INNER_RING, "Red", "Inner ring (hole, CW)"),
color(CLR_BAD_WINDING, "Light red", "Non-standard winding"),
color(CLR_EXTENT, "Dark gray", "Tile extent boundary"),
color(CLR_TRIANGLE, "Dark gray", "Tessellation edges"),
Line::from(""),
heading("Selection Colors"),
color(CLR_SELECTED, "Yellow", "Selected feature/part"),
color(CLR_HOVERED, "White", "Hovered layer/feature/part"),
color(CLR_INNER_RING_SEL, "Salmon", "Inner ring (selected)"),
color(CLR_DIMMED, "Dark gray", "Sibling parts (dimmed)"),
color(CLR_CONTEXT_FEATURE, "Gray", "Other features in the layer"),
color(CLR_CONTEXT_LAYER, "Dark gray", "Other layers"),
]
}