use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::text::{Line, Span};
use ratatui::widgets::Widget;
use crate::keyboard::{Binding, KeyHandler};
use crate::theme::Theme;
pub struct HelpBar<'a, T: Theme> {
theme: &'a T,
bindings: &'a [Binding],
separator: &'a str,
bracket_keys: bool,
}
impl<'a, T: Theme> HelpBar<'a, T> {
pub fn new(theme: &'a T) -> Self {
Self {
theme,
bindings: KeyHandler::default_bindings(),
separator: " ",
bracket_keys: true,
}
}
#[must_use]
pub fn bindings(mut self, bindings: &'a [Binding]) -> Self {
self.bindings = bindings;
self
}
#[must_use]
pub fn separator(mut self, separator: &'a str) -> Self {
self.separator = separator;
self
}
#[must_use]
pub fn bracket_keys(mut self, enabled: bool) -> Self {
self.bracket_keys = enabled;
self
}
}
impl<T: Theme> Widget for HelpBar<'_, T> {
fn render(self, area: Rect, buf: &mut Buffer) {
if area.height == 0 || area.width == 0 {
return;
}
buf.set_style(area, self.theme.base());
let key_style = self.theme.title();
let label_style = self.theme.disabled();
let mut spans: Vec<Span> = Vec::with_capacity(self.bindings.len() * 4);
for (i, binding) in self.bindings.iter().enumerate() {
if i > 0 {
spans.push(Span::styled(self.separator, label_style));
}
if self.bracket_keys {
spans.push(Span::styled("[", label_style));
spans.push(Span::styled(binding.keys, key_style));
spans.push(Span::styled("]", label_style));
} else {
spans.push(Span::styled(binding.keys, key_style));
}
spans.push(Span::styled(" ", label_style));
spans.push(Span::styled(binding.label, label_style));
}
Line::from(spans).render(Rect::new(area.x, area.y, area.width, 1), buf);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keyboard::Action;
use crate::theme::EddaCraftTheme;
#[test]
fn empty_bindings_renders_blank_line() {
let theme = EddaCraftTheme;
let area = Rect::new(0, 0, 20, 1);
let mut buf = Buffer::empty(area);
HelpBar::new(&theme).bindings(&[]).render(area, &mut buf);
for x in 0..20 {
assert_eq!(buf[(x, 0)].symbol(), " ");
}
}
#[test]
fn renders_bracketed_key_and_label() {
let theme = EddaCraftTheme;
let area = Rect::new(0, 0, 20, 1);
let mut buf = Buffer::empty(area);
let bindings = [Binding {
keys: "q",
action: Action::Quit,
label: "Quit",
}];
HelpBar::new(&theme)
.bindings(&bindings)
.render(area, &mut buf);
let text: String = (0..10).map(|x| buf[(x, 0)].symbol().to_string()).collect();
assert!(text.starts_with("[q] Quit"), "got: {text:?}");
}
#[test]
fn separator_appears_between_bindings() {
let theme = EddaCraftTheme;
let area = Rect::new(0, 0, 40, 1);
let mut buf = Buffer::empty(area);
let bindings = [
Binding {
keys: "a",
action: Action::Up,
label: "A",
},
Binding {
keys: "b",
action: Action::Down,
label: "B",
},
];
HelpBar::new(&theme)
.bindings(&bindings)
.separator(" | ")
.render(area, &mut buf);
let text: String = (0..40).map(|x| buf[(x, 0)].symbol().to_string()).collect();
assert!(text.contains("[a] A | [b] B"), "got: {text:?}");
}
#[test]
fn bracket_keys_false_renders_keys_without_brackets() {
let theme = EddaCraftTheme;
let area = Rect::new(0, 0, 20, 1);
let mut buf = Buffer::empty(area);
let bindings = [Binding {
keys: "q",
action: Action::Quit,
label: "Quit",
}];
HelpBar::new(&theme)
.bindings(&bindings)
.bracket_keys(false)
.render(area, &mut buf);
let text: String = (0..10).map(|x| buf[(x, 0)].symbol().to_string()).collect();
assert!(text.starts_with("q Quit"), "got: {text:?}");
}
#[test]
fn key_span_uses_title_style() {
let theme = EddaCraftTheme;
let area = Rect::new(0, 0, 20, 1);
let mut buf = Buffer::empty(area);
let bindings = [Binding {
keys: "q",
action: Action::Quit,
label: "Quit",
}];
HelpBar::new(&theme)
.bindings(&bindings)
.render(area, &mut buf);
let expected_fg = theme.title().fg.unwrap();
assert_eq!(buf[(1, 0)].fg, expected_fg);
}
#[test]
fn default_bindings_render_without_panic() {
let theme = EddaCraftTheme;
let area = Rect::new(0, 0, 80, 1);
let mut buf = Buffer::empty(area);
HelpBar::new(&theme).render(area, &mut buf);
}
#[test]
fn zero_height_area_is_a_noop() {
let theme = EddaCraftTheme;
let area = Rect::new(0, 0, 20, 0);
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
HelpBar::new(&theme).render(area, &mut buf);
}
}