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