use crate::tui::app::dialog_keys::{DialogScope, dialog_keys};
use super::chrome;
use super::theme::{self, Role};
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Clear, Paragraph};
fn key_style() -> Style {
Style::default()
.fg(theme::role(Role::AccentTeal))
.add_modifier(Modifier::BOLD)
}
pub fn footer_line(
indent: &str,
keys: &[crate::tui::app::dialog_keys::DialogKey],
) -> Line<'static> {
let mut spans = Vec::new();
if !indent.is_empty() {
spans.push(Span::raw(indent.to_string()));
}
for (i, key) in keys.iter().enumerate() {
if i > 0 {
spans.push(Span::styled(
" ",
Style::default().fg(theme::role(Role::GrayDim)),
));
}
spans.push(Span::styled(key.label, key_style()));
spans.push(Span::styled(
format!(": {}", key.verb),
Style::default().fg(theme::role(Role::TextDim)),
));
}
Line::from(spans)
}
pub fn render_footer(
frame: &mut Frame,
area: Rect,
keys: &[crate::tui::app::dialog_keys::DialogKey],
) {
if area.width < 3 {
return;
}
frame.render_widget(Paragraph::new(footer_line("", keys)), area);
}
pub fn render_command_panel(frame: &mut Frame, area: Rect, scope: DialogScope) {
let keys = dialog_keys(scope);
let longest = keys
.iter()
.map(|k| k.label.chars().count() + k.verb.len() + 2)
.max()
.unwrap_or(10) as u16;
let width = (longest + 10).clamp(28, (area.width.saturating_sub(4)).max(28));
let height =
(keys.len() as u16 + 4).min(area.height.saturating_sub(2).max(keys.len() as u16 + 4));
let popup = chrome::centered(area, width, height);
frame.render_widget(Clear, popup);
let block = chrome::modal_block(
&format!(" {} commands ", scope.title()),
theme::role(Role::Accent),
);
let mut lines: Vec<Line<'static>> = Vec::with_capacity(keys.len() + 2);
for key in keys {
lines.push(Line::from(vec![
Span::raw(" "),
Span::styled(format!("{:<10}", key.label), key_style()),
Span::styled(key.verb, Style::default().fg(theme::role(Role::TextDim))),
]));
}
lines.push(Line::from(""));
lines.push(Line::from(vec![
Span::raw(" "),
Span::styled("Esc/Ctrl+C", key_style()),
Span::styled(": close", Style::default().fg(theme::role(Role::TextDim))),
]));
frame.render_widget(Paragraph::new(lines).block(block), popup);
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::Terminal;
use ratatui::backend::TestBackend;
#[test]
fn footer_keys_and_verbs_are_styled_differently() {
let keys = dialog_keys(DialogScope::RestartPending);
let terminal = TestBackend::new(80, 3);
let mut terminal = Terminal::new(terminal).unwrap();
terminal
.draw(|f| {
let area = Rect::new(0, 1, 80, 1);
render_footer(f, area, keys);
})
.unwrap();
let buffer = terminal.backend().buffer();
let key_cell = &buffer[(0, 1)];
let verb_cell = &buffer[(7, 1)];
let second_key_cell = &buffer[(16, 1)];
assert_eq!(key_cell.symbol(), "E");
assert_eq!(verb_cell.symbol(), "c");
assert_eq!(second_key_cell.symbol(), "E");
assert!(
key_cell.modifier.contains(Modifier::BOLD),
"footer keys must be bold"
);
assert!(
!verb_cell.modifier.contains(Modifier::BOLD),
"footer verbs must not be bold"
);
assert_ne!(key_cell.fg, verb_cell.fg, "key and verb colors must differ");
assert!(
second_key_cell.modifier.contains(Modifier::BOLD),
"every binding's key is bold, not just the first"
);
}
#[test]
fn command_panel_lists_every_binding() {
let scope = DialogScope::ThemePicker;
let keys = dialog_keys(scope);
let terminal = TestBackend::new(60, 20);
let mut terminal = Terminal::new(terminal).unwrap();
terminal
.draw(|f| {
let area = f.area();
render_command_panel(f, area, scope);
})
.unwrap();
let buffer = terminal.backend().buffer();
let content: String = buffer
.content()
.iter()
.map(|c| c.symbol().to_string())
.collect();
assert!(content.contains("Theme picker commands"), "title missing");
for key in keys {
assert!(
content.contains(key.verb),
"verb {} missing from panel",
key.verb
);
}
}
#[test]
fn footer_line_is_key_colon_verb() {
let line = footer_line("", dialog_keys(DialogScope::RestartPending));
let text: String = line.spans.iter().map(|s| s.content.clone()).collect();
assert_eq!(text, "Enter: confirm Esc: cancel");
}
}