Skip to main content

max / goingson

2.6 KB · 76 lines History Blame Raw
1 //! The one-row footer: key hints along the bottom.
2 //!
3 //! Was `alloy_tui::AlloyStatusBar`. It moved here when `got` stopped depending
4 //! on Alloy's design system: the colour mapping it needs is family-level and
5 //! now lives in `makeover-tui`, but a footer's layout is this app's opinion
6 //! about its own chrome, not something two unrelated programs should share.
7 //!
8 //! Only the hint half came across. The original also carried a right-aligned
9 //! transient status slot; `got` never set one, and an unused builder method is
10 //! a worse thing to keep than fifteen lines are to rewrite if it is ever
11 //! wanted.
12
13 use makeover_tui::Theme;
14 use ratatui::buffer::Buffer;
15 use ratatui::layout::Rect;
16 use ratatui::style::Style;
17 use ratatui::text::{Line, Span};
18 use ratatui::widgets::{Paragraph, Widget};
19
20 /// A footer key hint: the key, and what it does.
21 pub(crate) struct Hint {
22 pub(crate) key: &'static str,
23 pub(crate) label: &'static str,
24 }
25
26 /// Terse constructor, so hint lists read as data at the call site:
27 /// `[hint("Tab", "group"), hint("q", "quit")]`.
28 pub(crate) const fn hint(key: &'static str, label: &'static str) -> Hint {
29 Hint { key, label }
30 }
31
32 pub(crate) struct StatusBar<'a> {
33 theme: &'a Theme,
34 hints: Vec<Hint>,
35 }
36
37 impl<'a> StatusBar<'a> {
38 pub(crate) fn new(theme: &'a Theme, hints: impl IntoIterator<Item = Hint>) -> Self {
39 Self {
40 theme,
41 hints: hints.into_iter().collect(),
42 }
43 }
44 }
45
46 impl Widget for StatusBar<'_> {
47 fn render(self, area: Rect, buf: &mut Buffer) {
48 // The bar reads as its own band, so the whole row takes the sunken
49 // surface first and every span below is drawn onto it.
50 let base = Style::default().bg(self.theme.surface_sunken);
51 Paragraph::new("").style(base).render(area, buf);
52
53 let mut spans: Vec<Span> = Vec::with_capacity(self.hints.len() * 3);
54 for (i, h) in self.hints.iter().enumerate() {
55 if i > 0 {
56 spans.push(Span::raw(" "));
57 }
58 // The key takes the action colour and the label is muted: a key
59 // hint IS the actionable element, which is the one place accent on
60 // text is not decoration.
61 spans.push(Span::styled(
62 h.key,
63 Style::default().fg(self.theme.action_primary),
64 ));
65 spans.push(Span::raw(" "));
66 spans.push(Span::styled(
67 h.label,
68 Style::default().fg(self.theme.content_muted),
69 ));
70 }
71 Paragraph::new(Line::from(spans))
72 .style(base)
73 .render(area, buf);
74 }
75 }
76