1 use ::std::path::PathBuf; 2 use ::tui::backend::Backend; 3 use ::tui::layout::{Constraint, Direction, Layout, Rect}; 4 use ::tui::Terminal; 5 6 use crate::state::files::FileTree; 7 use crate::state::tiles::Board; 8 use crate::state::UiEffects; 9 use crate::ui::grid::RectangleGrid; 10 use crate::ui::modals::{ConfirmBox, ErrorBox, MessageBox, WarningBox}; 11 use crate::ui::title::TitleLine; 12 use crate::ui::{BottomLine, TermTooSmall}; 13 use crate::UiMode; 14 15 pub struct FolderInfo<'a> { 16 pub path: &'a PathBuf, 17 pub size: u128, 18 pub num_descendants: u64, 19 } 20 21 pub struct Display<B> 22 where 23 B: Backend, 24 { 25 terminal: Terminal<B>, 26 } 27 28 impl<B> Display<B> 29 where 30 B: Backend, 31 { new(terminal_backend: B) -> Self32 pub fn new(terminal_backend: B) -> Self { 33 let mut terminal = Terminal::new(terminal_backend).expect("failed to create terminal"); 34 terminal.clear().expect("failed to clear terminal"); 35 terminal.hide_cursor().expect("failed to hide cursor"); 36 Display { terminal } 37 } size(&self) -> Rect38 pub fn size(&self) -> Rect { 39 self.terminal.size().expect("could not get terminal size") 40 } render( &mut self, file_tree: &mut FileTree, board: &mut Board, ui_mode: &UiMode, ui_effects: &UiEffects, )41 pub fn render( 42 &mut self, 43 file_tree: &mut FileTree, 44 board: &mut Board, 45 ui_mode: &UiMode, 46 ui_effects: &UiEffects, 47 ) { 48 self.terminal 49 .draw(|f| { 50 let full_screen = f.size(); 51 let current_path = file_tree.get_current_path(); 52 let current_path_size = file_tree.get_current_folder_size(); 53 let current_path_descendants = file_tree.get_current_folder().num_descendants; 54 let base_path_size = file_tree.get_total_size(); 55 let base_path_descendants = file_tree.get_total_descendants(); 56 let current_path_info = FolderInfo { 57 path: ¤t_path, 58 size: current_path_size, 59 num_descendants: current_path_descendants, 60 }; 61 let path_in_filesystem = &file_tree.path_in_filesystem; 62 let base_path_info = FolderInfo { 63 path: &path_in_filesystem, 64 size: base_path_size, 65 num_descendants: base_path_descendants, 66 }; 67 let mut chunks = Layout::default() 68 .direction(Direction::Vertical) 69 .margin(0) 70 .constraints( 71 [ 72 Constraint::Length(1), 73 Constraint::Min(10), 74 Constraint::Length(2), 75 ] 76 .as_ref(), 77 ) 78 .split(full_screen); 79 80 // -1 cos we draw starting at offset 1 in both x and y directions 81 82 chunks[1].width -= 1; 83 chunks[1].height -= 1; 84 board.change_area(&chunks[1]); 85 match ui_mode { 86 UiMode::Loading => { 87 f.render_widget( 88 TitleLine::new( 89 base_path_info, 90 current_path_info, 91 file_tree.space_freed, 92 ) 93 .progress_indicator(ui_effects.loading_progress_indicator) 94 .path_error(ui_effects.current_path_is_red) 95 .read_errors(file_tree.failed_to_read) 96 .zoom_level(board.zoom_level) 97 .show_loading(), 98 chunks[0], 99 ); 100 f.render_widget( 101 RectangleGrid::new( 102 &board.tiles, 103 board.unrenderable_tile_coordinates, 104 board.selected_index, 105 ), 106 chunks[1], 107 ); 108 f.render_widget( 109 BottomLine::new() 110 .currently_selected(board.currently_selected()) 111 .last_read_path(ui_effects.last_read_path.as_ref()) 112 .hide_delete(), 113 chunks[2], 114 ); 115 } 116 UiMode::Normal => { 117 f.render_widget( 118 TitleLine::new( 119 base_path_info, 120 current_path_info, 121 file_tree.space_freed, 122 ) 123 .path_error(ui_effects.current_path_is_red) 124 .flash_space(ui_effects.flash_space_freed) 125 .zoom_level(board.zoom_level) 126 .read_errors(file_tree.failed_to_read), 127 chunks[0], 128 ); 129 f.render_widget( 130 RectangleGrid::new( 131 &board.tiles, 132 board.unrenderable_tile_coordinates, 133 board.selected_index, 134 ), 135 chunks[1], 136 ); 137 f.render_widget( 138 BottomLine::new().currently_selected(board.currently_selected()), 139 chunks[2], 140 ); 141 } 142 UiMode::ScreenTooSmall => { 143 f.render_widget(TermTooSmall::new(), full_screen); 144 } 145 UiMode::DeleteFile(file_to_delete) => { 146 f.render_widget( 147 TitleLine::new( 148 base_path_info, 149 current_path_info, 150 file_tree.space_freed, 151 ) 152 .path_error(ui_effects.current_path_is_red) 153 .zoom_level(board.zoom_level) 154 .read_errors(file_tree.failed_to_read), 155 chunks[0], 156 ); 157 f.render_widget( 158 RectangleGrid::new( 159 &board.tiles, 160 board.unrenderable_tile_coordinates, 161 board.selected_index, 162 ), 163 chunks[1], 164 ); 165 f.render_widget( 166 BottomLine::new().currently_selected(board.currently_selected()), 167 chunks[2], 168 ); 169 f.render_widget( 170 MessageBox::new(file_to_delete, ui_effects.deletion_in_progress), 171 full_screen, 172 ); 173 } 174 UiMode::ErrorMessage(message) => { 175 f.render_widget( 176 TitleLine::new( 177 base_path_info, 178 current_path_info, 179 file_tree.space_freed, 180 ) 181 .path_error(ui_effects.current_path_is_red) 182 .flash_space(ui_effects.flash_space_freed) 183 .zoom_level(board.zoom_level) 184 .read_errors(file_tree.failed_to_read), 185 chunks[0], 186 ); 187 f.render_widget( 188 RectangleGrid::new( 189 &board.tiles, 190 board.unrenderable_tile_coordinates, 191 board.selected_index, 192 ), 193 chunks[1], 194 ); 195 f.render_widget( 196 BottomLine::new().currently_selected(board.currently_selected()), 197 chunks[2], 198 ); 199 f.render_widget(ErrorBox::new(message), full_screen); 200 } 201 UiMode::Exiting { app_loaded } => { 202 if *app_loaded { 203 // render normal ui mode 204 f.render_widget( 205 TitleLine::new( 206 base_path_info, 207 current_path_info, 208 file_tree.space_freed, 209 ) 210 .path_error(ui_effects.current_path_is_red) 211 .flash_space(ui_effects.flash_space_freed) 212 .zoom_level(board.zoom_level) 213 .read_errors(file_tree.failed_to_read), 214 chunks[0], 215 ); 216 f.render_widget( 217 BottomLine::new().currently_selected(board.currently_selected()), 218 chunks[2], 219 ); 220 } else { 221 // render loading ui mode 222 f.render_widget( 223 TitleLine::new( 224 base_path_info, 225 current_path_info, 226 file_tree.space_freed, 227 ) 228 .progress_indicator(ui_effects.loading_progress_indicator) 229 .path_error(ui_effects.current_path_is_red) 230 .zoom_level(board.zoom_level) 231 .read_errors(file_tree.failed_to_read) 232 .show_loading(), 233 chunks[0], 234 ); 235 f.render_widget( 236 BottomLine::new() 237 .currently_selected(board.currently_selected()) 238 .last_read_path(ui_effects.last_read_path.as_ref()) 239 .hide_delete(), 240 chunks[2], 241 ); 242 } 243 // render common widgets 244 f.render_widget( 245 RectangleGrid::new( 246 &board.tiles, 247 board.unrenderable_tile_coordinates, 248 board.selected_index, 249 ), 250 chunks[1], 251 ); 252 f.render_widget(ConfirmBox::new(), full_screen); 253 } 254 UiMode::WarningMessage(_) => { 255 f.render_widget( 256 TitleLine::new( 257 base_path_info, 258 current_path_info, 259 file_tree.space_freed, 260 ) 261 .progress_indicator(ui_effects.loading_progress_indicator) 262 .path_error(ui_effects.current_path_is_red) 263 .read_errors(file_tree.failed_to_read) 264 .show_loading(), 265 chunks[0], 266 ); 267 f.render_widget( 268 RectangleGrid::new( 269 &board.tiles, 270 board.unrenderable_tile_coordinates, 271 board.selected_index, 272 ), 273 chunks[1], 274 ); 275 f.render_widget( 276 BottomLine::new() 277 .currently_selected(board.currently_selected()) 278 .last_read_path(ui_effects.last_read_path.as_ref()) 279 .hide_delete(), 280 chunks[2], 281 ); 282 f.render_widget(WarningBox::new(), full_screen); 283 } 284 }; 285 }) 286 .expect("failed to draw"); 287 } clear(&mut self)288 pub fn clear(&mut self) { 289 self.terminal.clear().expect("failed to clear terminal"); 290 self.terminal.show_cursor().expect("failed to show cursor"); 291 } 292 } 293