gitv_tui/ui/components/
help.rs1use ratatui::{
2 style::{Color, Modifier, Style},
3 text::{Line, Span, Text},
4 widgets::{BlockExt, Clear, Widget},
5};
6use tracing::trace;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum HelpElementKind {
10 Keybind(&'static str, &'static str),
11 Text(&'static str),
12}
13
14#[macro_export]
15macro_rules! help_keybind {
16 ($key:expr, $description:expr) => {
17 $crate::ui::components::help::HelpElementKind::Keybind($key, $description)
18 };
19}
20
21#[macro_export]
22macro_rules! help_text {
23 ($text:expr) => {
24 $crate::ui::components::help::HelpElementKind::Text($text)
25 };
26}
27
28pub fn help_elements_to_text(elements: &[HelpElementKind], width: u16) -> Text<'static> {
29 let mut lines = Vec::with_capacity(elements.len());
30 for element in elements {
31 match element {
32 HelpElementKind::Keybind(key, description) => {
33 let total_length = (key.len() + description.len()) as u16; let padding = if total_length < width {
35 width - total_length
36 } else {
37 1 };
39 lines.push(Line::from(vec![
40 Span::styled(
41 *key,
42 Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD),
43 ),
44 Span::raw(" ".repeat(padding as usize)),
45 Span::raw(*description),
46 ]));
47 }
48 HelpElementKind::Text(text) => {
49 let wrapped = textwrap::wrap(text, width as usize);
50 lines.extend(wrapped.into_iter().map(|line| Line::from(line).centered()));
51 }
52 }
53 }
54 Text::from(lines)
55}
56
57pub struct HelpComponent<'a> {
59 constraint: u16,
60 content: &'a [HelpElementKind],
61 block: Option<ratatui::widgets::Block<'a>>,
62 width: u16,
63}
64
65impl<'a> HelpComponent<'a> {
66 pub fn new(content: &'a [HelpElementKind]) -> Self {
68 Self {
69 content,
70 width: 0,
71 constraint: 0,
72 block: None,
73 }
74 }
75 pub fn set_constraint(self, constraint: u16) -> Self {
77 Self { constraint, ..self }
78 }
79 pub fn block(self, block: ratatui::widgets::Block<'a>) -> Self {
81 Self {
82 block: Some(block),
83 ..self
84 }
85 }
86}
87
88impl<'a> Widget for HelpComponent<'a> {
89 fn render(mut self, area: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
90 use ratatui::layout::Constraint::{Length, Percentage};
91 trace!(content = ?self.content, "Rendering HelpComponent");
92 trace!(content_length = ?self.content.len(), "Content length");
93 let mut centered_area = if self.constraint != 0 {
94 area.centered(Percentage(self.constraint), Length(self.constraint))
95 } else {
96 area
97 };
98 let mut inner = self.block.inner_if_some(centered_area);
99 self.width = inner.width;
100 let text = help_elements_to_text(self.content, self.width);
101 let text_height = text.height() as u16;
102 let y_offset = |h: u16| {
103 if text_height < h {
104 (h - text_height) / 2
105 } else {
106 0
107 }
108 };
109 inner.y += y_offset(inner.height) + 1;
110 inner.height = text.height() as u16;
111 let inner_height = inner.height;
112 centered_area.y += y_offset(centered_area.height);
113 centered_area.height = inner_height + 2;
114 Clear.render(centered_area, buf);
115 self.block.render(centered_area, buf);
116 text.render(inner, buf);
117 }
118}