1 use crate::interactive::CursorDirection;
2 use crosstermion::{input::Key, input::Key::*};
3 use std::{borrow::Borrow, cell::RefCell};
4 use tui::{
5     buffer::Buffer,
6     layout::Rect,
7     style::{Color, Modifier, Style},
8     text::{Span, Spans, Text},
9     widgets::{Block, Borders, Paragraph, Widget},
10 };
11 use tui_react::{
12     draw_text_nowrap_fn,
13     util::{block_width, rect},
14 };
15 
16 #[derive(Default, Clone)]
17 pub struct HelpPane {
18     pub scroll: u16,
19 }
20 
21 pub struct HelpPaneProps {
render(&self, props: impl Borrow<FooterProps>, area: Rect, buf: &mut Buffer)22     pub border_style: Style,
23     pub has_focus: bool,
24 }
25 
26 fn margin(r: Rect, margin: u16) -> Rect {
27     Rect {
28         x: r.x + margin,
29         y: r.y + margin,
30         width: r.width - 2 * margin,
31         height: r.height - 2 * margin,
32     }
33 }
34 
35 impl HelpPane {
36     pub fn process_events(&mut self, key: Key) {
37         match key {
38             Char('H') => self.scroll_help(CursorDirection::ToTop),
39             Char('G') => self.scroll_help(CursorDirection::ToBottom),
40             Ctrl('u') | PageUp => self.scroll_help(CursorDirection::PageUp),
41             Char('k') | Up => self.scroll_help(CursorDirection::Up),
42             Char('j') | Down => self.scroll_help(CursorDirection::Down),
43             Ctrl('d') | PageDown => self.scroll_help(CursorDirection::PageDown),
44             _ => {}
45         };
46     }
47     fn scroll_help(&mut self, direction: CursorDirection) {
48         self.scroll = direction.move_cursor(self.scroll as usize) as u16;
49     }
50 
51     pub fn render(&mut self, props: impl Borrow<HelpPaneProps>, area: Rect, buf: &mut Buffer) {
52         let lines = {
53             let lines = RefCell::new(Vec::<Spans>::with_capacity(30));
54             let add_newlines = |n| {
55                 for _ in 0..n {
56                     lines.borrow_mut().push(Spans::from(Span::raw("")))
57                 }
58             };
59 
60             let spacer = || add_newlines(2);
61             let title = |name: &str| {
62                 lines.borrow_mut().push(Spans::from(Span::styled(
63                     name.to_string(),
64                     Style {
65                         add_modifier: Modifier::BOLD | Modifier::UNDERLINED,
66                         ..Default::default()
67                     },
68                 )));
69                 add_newlines(1);
70             };
71             let hotkey = |keys, description, other_line: Option<&str>| {
72                 let separator_size = 3;
73                 let column_size = 11 + separator_size;
74                 lines.borrow_mut().push(Spans::from(vec![
75                     Span::styled(
76                         format!(
77                             "{:>column_size$}",
78                             keys,
79                             column_size = column_size - separator_size
80                         ),
81                         Style {
82                             fg: Color::Green.into(),
83                             ..Default::default()
84                         },
85                     ),
86                     Span::from(format!(" => {}", description)),
87                 ]));
88                 if let Some(second_line) = other_line {
89                     lines.borrow_mut().push(Spans::from(Span::from(format!(
90                         "{:>column_size$}{}",
91                         "",
92                         second_line,
93                         column_size = column_size + 1
94                     ))));
95                 }
96             };
97 
98             title("Keys for pane control");
99             {
100                 hotkey(
101                     "q/<Esc>",
102                     "Close the current pane. Closes the program if no",
103                     Some("pane is open"),
104                 );
105                 hotkey("<Tab>", "Cycle between all open panes", None);
106                 hotkey("?", "Show or hide the help pane", None);
107                 spacer();
108             }
109             title("Keys for Navigation");
110             {
111                 hotkey("j/<Down>", "move down an entry", None);
112                 hotkey("k/<Up>", "move up an entry", None);
113                 hotkey("o/l/<Enter>", "descent into the selected directory", None);
114                 hotkey("<Right>", "^", None);
115                 hotkey(
116                     "u/h/<Left>",
117                     "ascent one level into the parent directory",
118                     None,
119                 );
120                 hotkey("<Backspace>", "^", None);
121                 hotkey("Ctrl + d", "move down 10 entries at once", None);
122                 hotkey("<Page Down>", "^", None);
123                 hotkey("Ctrl + u", "move up 10 entries at once", None);
124                 hotkey("<Page Up>", "^", None);
125                 hotkey("H/<Home>", "Move to the top of the entries list", None);
126                 hotkey("G/<End>", "Move to the bottom of the entries list", None);
127                 spacer();
128             }
129             title("Keys for display");
130             {
131                 hotkey("s", "toggle sort by size ascending/descending", None);
132                 hotkey(
133                     "g",
134                     "cycle through percentage display and bar options",
135                     None,
136                 );
137                 spacer();
138             }
139             title("Keys for entry operations");
140             {
141                 hotkey(
142                     "Shift + o",
143                     "Open the entry with the associated program",
144                     None,
145                 );
146                 hotkey(
147                     "d",
148                     "Toggle the currently selected entry and move down",
149                     None,
150                 );
151                 hotkey(
152                     "x",
153                     "Mark for the currently selected entry for deletion and move down",
154                     None,
155                 );
156                 hotkey("<Space>", "Toggle the currently selected entry", None);
157                 spacer();
158             }
159             title("Keys in the Mark pane");
160             {
161                 hotkey(
162                     "x/d/<Space>",
163                     "remove the selected entry from the list",
164                     None,
165                 );
166                 hotkey(
167                     "Ctrl + r",
168                     "Permanently delete all marked entries without prompt!",
169                     Some("This operation cannot be undone!"),
170                 );
171                 #[cfg(feature = "trash-move")]
172                 hotkey(
173                     "Ctrl + t",
174                     "Move all marked entries to the trash bin",
175                     Some("The entries can be restored from the trash bin"),
176                 );
177                 spacer();
178             }
179             title("Keys for application control");
180             {
181                 hotkey(
182                     "Ctrl + c",
183                     "close the application. No questions asked!",
184                     None,
185                 );
186                 spacer();
187             }
188             lines.into_inner()
189         };
190 
191         let HelpPaneProps {
192             border_style,
193             has_focus,
194         } = props.borrow();
195 
196         let title = "Help";
197         let block = Block::default()
198             .title(title)
199             .border_style(*border_style)
200             .borders(Borders::ALL);
201         let inner_block_area = block.inner(area);
202         block.render(area, buf);
203 
204         if *has_focus {
205             let help_text = " . = o|.. = u ── ⇊ = Ctrl+d|↓ = j|⇈ = Ctrl+u|↑ = k ";
206             let help_text_block_width = block_width(help_text);
207             let bound = Rect {
208                 width: area.width.saturating_sub(1),
209                 ..area
210             };
211             if block_width(title) + help_text_block_width <= bound.width {
212                 draw_text_nowrap_fn(
213                     rect::snap_to_right(bound, help_text_block_width),
214                     buf,
215                     help_text,
216                     |_, _, _| Style::default(),
217                 );
218             }
219         }
220 
221         let area = margin(inner_block_area, 1);
222         self.scroll = self
223             .scroll
224             .min(lines.len().saturating_sub(area.height as usize) as u16);
225         Paragraph::new(Text::from(lines))
226             .scroll((self.scroll, 0))
227             .render(area, buf);
228     }
229 }
230