basalt_tui/
help_modal.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::{Alignment, Constraint, Flex, Layout, Rect},
4    style::{Color, Style, Stylize},
5    text::Line,
6    widgets::{
7        Block, BorderType, Clear, Padding, Paragraph, Scrollbar, ScrollbarOrientation,
8        ScrollbarState, StatefulWidget, Widget, Wrap,
9    },
10};
11
12#[derive(Debug, Default, Clone, PartialEq)]
13pub struct HelpModalState {
14    pub scrollbar_state: ScrollbarState,
15    pub scrollbar_position: usize,
16    pub viewport_height: usize,
17    pub text: String,
18}
19
20impl HelpModalState {
21    pub fn new(text: &str) -> Self {
22        Self {
23            text: text.to_string(),
24            scrollbar_state: ScrollbarState::new(text.lines().count()),
25            ..Default::default()
26        }
27    }
28
29    pub fn scroll_up(self, amount: usize) -> Self {
30        let scrollbar_position = self.scrollbar_position.saturating_sub(amount);
31        let scrollbar_state = self.scrollbar_state.position(scrollbar_position);
32
33        Self {
34            scrollbar_state,
35            scrollbar_position,
36            ..self
37        }
38    }
39
40    pub fn scroll_down(self, amount: usize) -> Self {
41        let scrollbar_position = self
42            .scrollbar_position
43            .saturating_add(amount)
44            .min(self.text.lines().count());
45
46        let scrollbar_state = self.scrollbar_state.position(scrollbar_position);
47
48        Self {
49            scrollbar_state,
50            scrollbar_position,
51            ..self
52        }
53    }
54
55    pub fn reset_scrollbar(self) -> Self {
56        Self {
57            scrollbar_state: ScrollbarState::default(),
58            scrollbar_position: 0,
59            ..self
60        }
61    }
62}
63
64fn modal_area(area: Rect) -> Rect {
65    let vertical = Layout::vertical([Constraint::Percentage(50)]).flex(Flex::Center);
66    let horizontal = Layout::horizontal([Constraint::Length(83)]).flex(Flex::Center);
67    let [area] = vertical.areas(area);
68    let [area] = horizontal.areas(area);
69    area
70}
71
72pub struct HelpModal;
73
74impl StatefulWidget for HelpModal {
75    type State = HelpModalState;
76
77    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
78    where
79        Self: Sized,
80    {
81        let block = Block::bordered()
82            .black()
83            .border_type(BorderType::Rounded)
84            .padding(Padding::uniform(1))
85            .title_style(Style::default().italic().bold())
86            .title(" Help ")
87            .title(Line::from(" (?) ").alignment(Alignment::Right));
88
89        let area = modal_area(area);
90
91        Widget::render(Clear, area, buf);
92        Widget::render(
93            Paragraph::new(state.text.clone())
94                .wrap(Wrap::default())
95                .scroll((state.scrollbar_position as u16, 0))
96                .block(block)
97                .fg(Color::default()),
98            area,
99            buf,
100        );
101
102        StatefulWidget::render(
103            Scrollbar::new(ScrollbarOrientation::VerticalRight),
104            area,
105            buf,
106            &mut state.scrollbar_state,
107        );
108    }
109}