use crate::keymap::Keymap;
use crate::styles::theme;
use anyhow::Result;
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Clear, Paragraph, Wrap},
Frame,
};
pub struct HelpOverlay;
impl HelpOverlay {
pub fn render(frame: &mut Frame, area: Rect, keymap: &Keymap, config_path: &str) -> Result<()> {
let theme = theme();
let popup_width = (f32::from(area.width) * 0.90).min(100.0) as u16;
let popup_height = (f32::from(area.height) * 0.90).min(50.0) as u16;
let popup_x = (area.width.saturating_sub(popup_width)) / 2;
let popup_y = (area.height.saturating_sub(popup_height)) / 2;
let popup_area = Rect::new(popup_x, popup_y, popup_width, popup_height);
frame.render_widget(Clear, popup_area);
let title = format!(" Keyboard Shortcuts - {} Preset ", keymap.preset.name());
let block = Block::default()
.borders(Borders::ALL)
.title(format!(" {title} "))
.title_alignment(Alignment::Center)
.border_style(Style::default().fg(theme.primary));
let inner_area = block.inner(popup_area);
frame.render_widget(block, popup_area);
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(4), Constraint::Min(5), Constraint::Length(3), ])
.split(inner_area);
let current_preset = keymap.preset;
let preset_lines = vec![
Line::from(vec![
Span::styled("Current Preset: ", Style::default().fg(theme.text)),
Span::styled(
current_preset.name(),
Style::default()
.fg(theme.primary)
.add_modifier(Modifier::BOLD),
),
]),
Line::from(""),
Line::from(vec![
Span::styled("Switch preset: ", Style::default().fg(theme.text_dimmed)),
Span::styled(
if current_preset == crate::keymap::KeymapPreset::Standard {
"[Standard]"
} else {
"Standard"
},
Style::default()
.fg(if current_preset == crate::keymap::KeymapPreset::Standard {
theme.primary
} else {
theme.text_dimmed
})
.add_modifier(if current_preset == crate::keymap::KeymapPreset::Standard {
Modifier::BOLD
} else {
Modifier::empty()
}),
),
Span::raw(" "),
Span::styled(
if current_preset == crate::keymap::KeymapPreset::Vim {
"[Vim]"
} else {
"Vim"
},
Style::default()
.fg(if current_preset == crate::keymap::KeymapPreset::Vim {
theme.primary
} else {
theme.text_dimmed
})
.add_modifier(if current_preset == crate::keymap::KeymapPreset::Vim {
Modifier::BOLD
} else {
Modifier::empty()
}),
),
Span::raw(" "),
Span::styled(
if current_preset == crate::keymap::KeymapPreset::Emacs {
"[Emacs]"
} else {
"Emacs"
},
Style::default()
.fg(if current_preset == crate::keymap::KeymapPreset::Emacs {
theme.primary
} else {
theme.text_dimmed
})
.add_modifier(if current_preset == crate::keymap::KeymapPreset::Emacs {
Modifier::BOLD
} else {
Modifier::empty()
}),
),
]),
Line::from(vec![
Span::styled("Press ", Style::default().fg(theme.text_dimmed)),
Span::styled("1", Style::default().fg(theme.text_emphasis)),
Span::styled(" / ", Style::default().fg(theme.text_dimmed)),
Span::styled("2", Style::default().fg(theme.text_emphasis)),
Span::styled(" / ", Style::default().fg(theme.text_dimmed)),
Span::styled("3", Style::default().fg(theme.text_emphasis)),
Span::styled(
" to switch, or any other key to close",
Style::default().fg(theme.text_dimmed),
),
]),
];
let preset_paragraph = Paragraph::new(preset_lines)
.alignment(Alignment::Left)
.wrap(Wrap { trim: true });
frame.render_widget(preset_paragraph, chunks[0]);
let bindings = keymap.all_bindings();
let mut lines: Vec<Line> = Vec::new();
lines.push(Line::from(""));
let mut current_category = "";
for binding in &bindings {
let category = binding.action.category();
if category != current_category {
if !current_category.is_empty() {
lines.push(Line::from("")); }
lines.push(Line::from(vec![Span::styled(
format!(" {category} "),
Style::default()
.fg(theme.secondary)
.add_modifier(Modifier::BOLD),
)]));
current_category = category;
}
let key_display = binding.display();
let description = binding.get_description();
lines.push(Line::from(vec![
Span::styled(
format!(" {key_display:12}"),
Style::default().fg(theme.text_emphasis),
),
Span::raw(description),
]));
}
let bindings_paragraph = Paragraph::new(lines)
.wrap(Wrap { trim: false })
.alignment(Alignment::Left);
frame.render_widget(bindings_paragraph, chunks[1]);
let footer_text = format!(
"Edit keybindings in: {config_path}\nPress 1/2/3 to switch preset, any other key to close"
);
let footer = Paragraph::new(footer_text)
.style(Style::default().fg(theme.text_dimmed))
.alignment(Alignment::Center);
frame.render_widget(footer, chunks[2]);
Ok(())
}
}