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}