//! The one-row footer: key hints along the bottom. //! //! Was `alloy_tui::AlloyStatusBar`. It moved here when `got` stopped depending //! on Alloy's design system: the colour mapping it needs is family-level and //! now lives in `makeover-tui`, but a footer's layout is this app's opinion //! about its own chrome, not something two unrelated programs should share. //! //! Only the hint half came across. The original also carried a right-aligned //! transient status slot; `got` never set one, and an unused builder method is //! a worse thing to keep than fifteen lines are to rewrite if it is ever //! wanted. use makeover_tui::Theme; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::Style; use ratatui::text::{Line, Span}; use ratatui::widgets::{Paragraph, Widget}; /// A footer key hint: the key, and what it does. pub(crate) struct Hint { pub(crate) key: &'static str, pub(crate) label: &'static str, } /// Terse constructor, so hint lists read as data at the call site: /// `[hint("Tab", "group"), hint("q", "quit")]`. pub(crate) const fn hint(key: &'static str, label: &'static str) -> Hint { Hint { key, label } } pub(crate) struct StatusBar<'a> { theme: &'a Theme, hints: Vec, } impl<'a> StatusBar<'a> { pub(crate) fn new(theme: &'a Theme, hints: impl IntoIterator) -> Self { Self { theme, hints: hints.into_iter().collect(), } } } impl Widget for StatusBar<'_> { fn render(self, area: Rect, buf: &mut Buffer) { // The bar reads as its own band, so the whole row takes the sunken // surface first and every span below is drawn onto it. let base = Style::default().bg(self.theme.surface_sunken); Paragraph::new("").style(base).render(area, buf); let mut spans: Vec = Vec::with_capacity(self.hints.len() * 3); for (i, h) in self.hints.iter().enumerate() { if i > 0 { spans.push(Span::raw(" ")); } // The key takes the action colour and the label is muted: a key // hint IS the actionable element, which is the one place accent on // text is not decoration. spans.push(Span::styled( h.key, Style::default().fg(self.theme.action_primary), )); spans.push(Span::raw(" ")); spans.push(Span::styled( h.label, Style::default().fg(self.theme.content_muted), )); } Paragraph::new(Line::from(spans)) .style(base) .render(area, buf); } }