From 5ba0b0ffb28fecf0c38c25d35ea7a78b25395d99 Mon Sep 17 00:00:00 2001 From: Jeff Zhao Date: Mon, 27 Dec 2021 22:04:24 -0500 Subject: refactor image preview via ueberzug (#115) * refactor image preview via ueberzug * fix area not correct and calculating with borders * remove area recalculation * only process previews if preview shown and remove scripts are present in config --- src/commands/numbered_command.rs | 4 +- src/event/app_event.rs | 2 - src/run.rs | 120 ++++++++++++++-------- src/ui/mod.rs | 17 ++-- src/ui/tui_backend.rs | 4 + src/ui/views/mod.rs | 2 +- src/ui/views/tui_command_menu.rs | 4 +- src/ui/views/tui_folder_view.rs | 199 ++++++++++++++++++++++++------------- src/ui/views/tui_textfield.rs | 4 +- src/ui/views/tui_view.rs | 7 +- src/ui/widgets/tui_file_preview.rs | 6 +- src/ui/widgets/tui_prompt.rs | 4 +- 12 files changed, 231 insertions(+), 142 deletions(-) diff --git a/src/commands/numbered_command.rs b/src/commands/numbered_command.rs index a98eb59..39775e6 100644 --- a/src/commands/numbered_command.rs +++ b/src/commands/numbered_command.rs @@ -7,7 +7,6 @@ use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult}; use crate::event::AppEvent; use crate::key_command::{CommandKeybind, NumberedExecute}; use crate::ui::views::TuiView; -use crate::ui::RenderResult; use crate::ui::TuiBackend; use crate::util::input; @@ -22,8 +21,7 @@ pub fn numbered_command( loop { context.message_queue_mut().push_info(prefix.clone()); - let mut render_result = RenderResult::new(); - backend.render(TuiView::new(context, &mut render_result)); + backend.render(TuiView::new(context)); context.message_queue_mut().pop_front(); let event = match context.poll_event() { diff --git a/src/event/app_event.rs b/src/event/app_event.rs index d6b77cb..d840daa 100644 --- a/src/event/app_event.rs +++ b/src/event/app_event.rs @@ -3,8 +3,6 @@ use std::path; use std::sync::mpsc; use std::thread; -use notify; - use signal_hook::consts::signal; use signal_hook::iterator::exfiltrator::SignalOnly; use signal_hook::iterator::SignalsInfo; diff --git a/src/run.rs b/src/run.rs index dd0eb25..7f4cc9f 100644 --- a/src/run.rs +++ b/src/run.rs @@ -2,18 +2,21 @@ use crate::commands::numbered_command; use crate::config::AppKeyMapping; use crate::context::{AppContext, QuitType}; use crate::event::AppEvent; -use crate::key_command::{AppExecute, Command, CommandKeybind}; +use crate::key_command::{AppExecute, CommandKeybind}; use crate::preview::preview_default; use crate::tab::JoshutoTab; use crate::ui; +use crate::ui::views; use crate::ui::views::TuiView; -use crate::ui::RenderResult; +use crate::ui::PreviewArea; use crate::util::input; use crate::util::to_string::ToString; -use std::path; + +use std::path::{Path, PathBuf}; use std::process; use std::thread; use termion::event::{Event, Key}; +use tui::layout::Rect; pub fn run( backend: &mut ui::TuiBackend, @@ -29,47 +32,24 @@ pub fn run( // trigger a preview of child preview_default::load_preview(context, backend); } - let mut last_preview_file_path: Option = None; + + let mut preview_area: Option = None; while context.quit == QuitType::DoNot { - let mut render_result = RenderResult::new(); - backend.render(TuiView::new(context, &mut render_result)); - if render_result.file_preview_path != last_preview_file_path { - match render_result.file_preview_path.clone() { - Some(path_buf) => { - if let Some(preview_shown_hook_script) = context - .config_ref() - .preview_options_ref() - .preview_shown_hook_script - .clone() - { - if let Some(preview_area) = render_result.preview_area { - let _ = thread::spawn(move || { - let _ = process::Command::new(preview_shown_hook_script.as_path()) - .arg(path_buf) - .arg(preview_area.x.to_string()) - .arg(preview_area.y.to_string()) - .arg(preview_area.width.to_string()) - .arg(preview_area.height.to_string()) - .status(); - }); - } - } - } - None => { - if let Some(preview_removed_hook_script) = context - .config_ref() - .preview_options_ref() - .preview_removed_hook_script - .clone() - { - let _ = thread::spawn(|| { - let _ = process::Command::new(preview_removed_hook_script).status(); - }); - } - } + backend.render(TuiView::new(context)); + + { + let config = context.config_ref(); + let preview_options = config.preview_options_ref(); + if let Ok(area) = backend.terminal_ref().size() { + preview_area = process_preview_on_change( + &context, + area, + preview_area, + preview_options.preview_shown_hook_script.as_ref(), + preview_options.preview_removed_hook_script.as_ref(), + ); } - last_preview_file_path = render_result.file_preview_path; } let event = match context.poll_event() { @@ -130,3 +110,61 @@ pub fn run( } Ok(()) } + +fn process_preview_on_change( + context: &AppContext, + area: Rect, + old_preview_area: Option, + preview_shown_hook_script: Option<&PathBuf>, + preview_removed_hook_script: Option<&PathBuf>, +) -> Option { + let area = Rect { + y: area.top() + 1, + height: area.height - 2, + ..area + }; + + let constraints = views::get_constraints(&context); + let config = context.config_ref(); + let display_options = config.display_options_ref(); + let layout = if display_options.show_borders() { + views::calculate_layout_with_borders(area, constraints) + } else { + views::calculate_layout(area, constraints) + }; + let new_preview_area = views::calculate_preview(&context, layout[2]); + + match new_preview_area.as_ref() { + Some(new) => { + let should_preview = if let Some(old) = old_preview_area { + new.file_preview_path != old.file_preview_path + } else { + true + }; + if should_preview { + if let Some(hook_script) = preview_shown_hook_script { + let hook_script = hook_script.to_path_buf(); + let new2 = new.clone(); + let _ = thread::spawn(move || { + let _ = process::Command::new(hook_script.as_path()) + .arg(new2.file_preview_path.as_path()) + .arg(new2.preview_area.x.to_string()) + .arg(new2.preview_area.y.to_string()) + .arg(new2.preview_area.width.to_string()) + .arg(new2.preview_area.height.to_string()) + .status(); + }); + } + } + } + None => { + if let Some(hook_script) = preview_shown_hook_script { + let hook_script = hook_script.to_path_buf(); + let _ = thread::spawn(|| { + let _ = process::Command::new(hook_script).status(); + }); + } + } + } + new_preview_area +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b782b1f..c71feb6 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -14,16 +14,17 @@ pub struct Rect { pub height: u16, } -pub struct RenderResult { - pub file_preview_path: Option, - pub preview_area: Option, +#[derive(Debug, Clone)] +pub struct PreviewArea { + pub file_preview_path: path::PathBuf, + pub preview_area: Rect, } -impl RenderResult { - pub fn new() -> RenderResult { - RenderResult { - file_preview_path: None, - preview_area: None, +impl PreviewArea { + pub fn new(file_preview_path: path::PathBuf, preview_area: Rect) -> Self { + Self { + file_preview_path, + preview_area, } } } diff --git a/src/ui/tui_backend.rs b/src/ui/tui_backend.rs index dc15f03..52afba4 100644 --- a/src/ui/tui_backend.rs +++ b/src/ui/tui_backend.rs @@ -69,6 +69,10 @@ impl TuiBackend { }); } + pub fn terminal_ref(&self) -> &JoshutoTerminal { + self.terminal.as_ref().unwrap() + } + pub fn terminal_mut(&mut self) -> &mut JoshutoTerminal { self.terminal.as_mut().unwrap() } diff --git a/src/ui/views/mod.rs b/src/ui/views/mod.rs index 70517cf..6af230c 100644 --- a/src/ui/views/mod.rs +++ b/src/ui/views/mod.rs @@ -5,7 +5,7 @@ mod tui_view; mod tui_worker_view; pub use self::tui_command_menu::TuiCommandMenu; -pub use self::tui_folder_view::TuiFolderView; +pub use self::tui_folder_view::*; pub use self::tui_textfield::TuiTextField; pub use self::tui_view::TuiView; pub use self::tui_worker_view::TuiWorkerView; diff --git a/src/ui/views/tui_command_menu.rs b/src/ui/views/tui_command_menu.rs index d62fa09..bc94752 100644 --- a/src/ui/views/tui_command_menu.rs +++ b/src/ui/views/tui_command_menu.rs @@ -8,7 +8,6 @@ use crate::config::AppKeyMapping; use crate::context::AppContext; use crate::ui::views::TuiView; use crate::ui::widgets::TuiMenu; -use crate::ui::RenderResult; use crate::util::to_string::ToString; const BORDER_HEIGHT: usize = 1; @@ -27,8 +26,7 @@ impl<'a> TuiCommandMenu<'a> { impl<'a> Widget for TuiCommandMenu<'a> { fn render(self, area: Rect, buf: &mut Buffer) { - let mut render_result = RenderResult::new(); - TuiView::new(self.context, &mut render_result).render(area, buf); + TuiView::new(self.context).render(area, buf); // draw menu let mut display_vec: Vec = self diff --git a/src/ui/views/tui_folder_view.rs b/src/ui/views/tui_folder_view.rs index 1a07ce0..38d5f4e 100644 --- a/src/ui/views/tui_folder_view.rs +++ b/src/ui/views/tui_folder_view.rs @@ -1,5 +1,3 @@ -use std::path; - use tui::buffer::Buffer; use tui::layout::{Constraint, Direction, Layout, Rect}; use tui::style::{Color, Style}; @@ -12,22 +10,20 @@ use crate::ui; use crate::ui::widgets::{ TuiDirList, TuiDirListDetailed, TuiFilePreview, TuiFooter, TuiTabBar, TuiTopBar, }; -use crate::ui::RenderResult; +use crate::ui::PreviewArea; const TAB_VIEW_WIDTH: u16 = 15; pub struct TuiFolderView<'a> { pub context: &'a AppContext, pub show_bottom_status: bool, - pub render_result: &'a mut RenderResult, } impl<'a> TuiFolderView<'a> { - pub fn new(context: &'a AppContext, render_result: &'a mut RenderResult) -> Self { + pub fn new(context: &'a AppContext) -> Self { Self { context, show_bottom_status: true, - render_result, } } } @@ -45,31 +41,18 @@ impl<'a> Widget for TuiFolderView<'a> { let config = self.context.config_ref(); let display_options = config.display_options_ref(); - let (default_layout, constraints): (bool, &[Constraint; 3]) = - if !display_options.collapse_preview() { - (true, &display_options.default_layout) - } else { - match child_list { - Some(_) => (true, &display_options.default_layout), - None => match curr_entry { - None => (false, &display_options.no_preview_layout), - Some(e) => match preview_context.get_preview_ref(e.file_path()) { - Some(Some(p)) if p.status.code() != Some(1) => { - (true, &display_options.default_layout) - } - _ => (false, &display_options.no_preview_layout), - }, - }, - } - }; + let constraints = get_constraints(self.context); + let is_default_layout = constraints == &display_options.default_layout; - let layout_rect = if config.display_options_ref().show_borders() { + let layout_rect = if display_options.show_borders() { let area = Rect { y: area.top() + 1, height: area.height - 2, ..area }; + let layout = calculate_layout_with_borders(area, constraints); + let block = Block::default().borders(Borders::ALL); let inner = block.inner(area); block.render(area, buf); @@ -79,6 +62,12 @@ impl<'a> Widget for TuiFolderView<'a> { .constraints(constraints.as_ref()) .split(inner); + let block = Block::default().borders(Borders::RIGHT); + block.render(layout_rect[0], buf); + + let block = Block::default().borders(Borders::LEFT); + block.render(layout_rect[2], buf); + // Render inner borders properly. { let top = area.top(); @@ -97,36 +86,18 @@ impl<'a> Widget for TuiFolderView<'a> { Constraint::Ratio(0, _) => (), _ => intersections.render_left(buf), } - if default_layout { + if is_default_layout { intersections.render_right(buf); } } - - let block = Block::default().borders(Borders::RIGHT); - let inner1 = block.inner(layout_rect[0]); - block.render(layout_rect[0], buf); - - let block = Block::default().borders(Borders::LEFT); - let inner3 = block.inner(layout_rect[2]); - block.render(layout_rect[2], buf); - - vec![inner1, layout_rect[1], inner3] + layout } else { - let mut layout_rect = Layout::default() - .direction(Direction::Horizontal) - .vertical_margin(1) - .constraints(constraints.as_ref()) - .split(area); - - layout_rect[0] = Rect { - width: layout_rect[0].width - 1, - ..layout_rect[0] - }; - layout_rect[1] = Rect { - width: layout_rect[1].width - 1, - ..layout_rect[1] + let area = Rect { + y: area.top() + 1, + height: area.height - 2, + ..area }; - layout_rect + calculate_layout(area, constraints) }; // render parent view @@ -168,31 +139,24 @@ impl<'a> Widget for TuiFolderView<'a> { } } - // render preview - let mut file_preview_path: Option = None; - let mut preview_area: Option = None; if let Some(list) = child_list.as_ref() { TuiDirList::new(list).render(layout_rect[2], buf); - } else if let Some(entry) = curr_entry { - if let Some(Some(preview)) = preview_context.get_preview_ref(entry.file_path()) { - match preview.status.code() { - Some(1) | None => {} - _ => { - let rect = layout_rect[2]; - TuiFilePreview::new(entry, preview).render(rect, buf); - file_preview_path = Some(entry.file_path_buf()); - preview_area = Some(ui::Rect { - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - }) - } + } else if curr_entry.is_some() { + let preview_area = calculate_preview(self.context, layout_rect[2]); + if let Some(preview_area) = preview_area { + let area = Rect { + x: preview_area.preview_area.x, + y: preview_area.preview_area.y, + width: preview_area.preview_area.width, + height: preview_area.preview_area.height, + }; + if let Some(Some(preview)) = + preview_context.get_preview_ref(&preview_area.file_preview_path) + { + TuiFilePreview::new(preview).render(area, buf); } } } - self.render_result.file_preview_path = file_preview_path; - self.render_result.preview_area = preview_area; let topbar_width = area.width; let rect = Rect { @@ -252,3 +216,100 @@ impl Intersections { .set_symbol(HORIZONTAL_UP); } } + +pub fn get_constraints(context: &AppContext) -> &[Constraint; 3] { + let preview_context = context.preview_context_ref(); + let curr_tab = context.tab_context_ref().curr_tab_ref(); + + let curr_list = curr_tab.curr_list_ref(); + let curr_entry = curr_list.and_then(|c| c.curr_entry_ref()); + + let child_list = curr_tab.child_list_ref(); + + let config = context.config_ref(); + let display_options = config.display_options_ref(); + + if !display_options.collapse_preview() { + &display_options.default_layout + } else { + match child_list { + Some(_) => &display_options.default_layout, + None => match curr_entry { + None => &display_options.no_preview_layout, + Some(e) => match preview_context.get_preview_ref(e.file_path()) { + Some(Some(p)) if p.status.code() != Some(1) => &display_options.default_layout, + _ => &display_options.no_preview_layout, + }, + }, + } + } +} + +pub fn calculate_layout(area: Rect, constraints: &[Constraint; 3]) -> Vec { + let mut layout_rect = Layout::default() + .direction(Direction::Horizontal) + .constraints(constraints.as_ref()) + .split(area); + + layout_rect[0] = Rect { + width: layout_rect[0].width - 1, + ..layout_rect[0] + }; + layout_rect[1] = Rect { + width: layout_rect[1].width - 1, + ..layout_rect[1] + }; + layout_rect +} + +pub fn calculate_layout_with_borders(area: Rect, constraints: &[Constraint; 3]) -> Vec { + let block = Block::default().borders(Borders::ALL); + let inner = block.inner(area); + + let layout_rect = Layout::default() + .direction(Direction::Horizontal) + .constraints(constraints.as_ref()) + .split(inner); + + let block = Block::default().borders(Borders::RIGHT); + let inner1 = block.inner(layout_rect[0]); + + let block = Block::default().borders(Borders::LEFT); + let inner3 = block.inner(layout_rect[2]); + + vec![inner1, layout_rect[1], inner3] +} + +pub fn calculate_preview(context: &AppContext, rect: Rect) -> Option { + let preview_context = context.preview_context_ref(); + let curr_tab = context.tab_context_ref().curr_tab_ref(); + + let child_list = curr_tab.child_list_ref(); + + let curr_list = curr_tab.curr_list_ref(); + let curr_entry = curr_list.and_then(|c| c.curr_entry_ref()); + + if child_list.as_ref().is_some() { + None + } else if let Some(entry) = curr_entry { + if let Some(Some(preview)) = preview_context.get_preview_ref(entry.file_path()) { + match preview.status.code() { + Some(1) | None => None, + _ => { + let file_preview_path = entry.file_path_buf(); + let preview_area = ui::Rect { + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + }; + Some(PreviewArea::new(file_preview_path, preview_area)) + } + } + } else { + None + } + } else { + None + } +} diff --git a/src/ui/views/tui_textfield.rs b/src/ui/views/tui_textfield.rs index b9426db..d829852 100644 --- a/src/ui/views/tui_textfield.rs +++ b/src/ui/views/tui_textfield.rs @@ -10,7 +10,6 @@ use crate::context::AppContext; use crate::event::AppEvent; use crate::ui::views::TuiView; use crate::ui::widgets::{TuiMenu, TuiMultilineText}; -use crate::ui::RenderResult; use crate::ui::TuiBackend; use crate::util::input; @@ -97,8 +96,7 @@ impl<'a> TuiTextField<'a> { return; } { - let mut render_result = RenderResult::new(); - let mut view = TuiView::new(context, &mut render_result); + let mut view = TuiView::new(context); view.show_bottom_status = false; frame.render_widget(view, area); } diff --git a/src/ui/views/tui_view.rs b/src/ui/views/tui_view.rs index a5f093c..2178475 100644 --- a/src/ui/views/tui_view.rs +++ b/src/ui/views/tui_view.rs @@ -4,26 +4,23 @@ use tui::widgets::Widget; use super::TuiFolderView; use crate::context::AppContext; -use crate::ui::RenderResult; pub struct TuiView<'a> { pub context: &'a AppContext, pub show_bottom_status: bool, - pub render_result: &'a mut RenderResult, } impl<'a> TuiView<'a> { - pub fn new(context: &'a AppContext, render_result: &'a mut RenderResult) -> Self { + pub fn new(context: &'a AppContext) -> Self { Self { context, show_bottom_status: true, - render_result, } } } impl<'a> Widget for TuiView<'a> { fn render(self, area: Rect, buf: &mut Buffer) { - TuiFolderView::new(self.context, self.render_result).render(area, buf); + TuiFolderView::new(self.context).render(area, buf); } } diff --git a/src/ui/widgets/tui_file_preview.rs b/src/ui/widgets/tui_file_preview.rs index cfd6812..de0d3ec 100644 --- a/src/ui/widgets/tui_file_preview.rs +++ b/src/ui/widgets/tui_file_preview.rs @@ -4,17 +4,15 @@ use tui::layout::Rect; use tui::text::Text; use tui::widgets::Widget; -use crate::fs::JoshutoDirEntry; use crate::preview::preview_file::FilePreview; pub struct TuiFilePreview<'a> { - _entry: &'a JoshutoDirEntry, preview: &'a FilePreview, } impl<'a> TuiFilePreview<'a> { - pub fn new(_entry: &'a JoshutoDirEntry, preview: &'a FilePreview) -> Self { - Self { _entry, preview } + pub fn new(preview: &'a FilePreview) -> Self { + Self { preview } } } diff --git a/src/ui/widgets/tui_prompt.rs b/src/ui/widgets/tui_prompt.rs index f1bd0d4..bc1b210 100644 --- a/src/ui/widgets/tui_prompt.rs +++ b/src/ui/widgets/tui_prompt.rs @@ -7,7 +7,6 @@ use tui::widgets::{Clear, Paragraph, Wrap}; use crate::context::AppContext; use crate::event::AppEvent; use crate::ui::views::TuiView; -use crate::ui::RenderResult; use crate::ui::TuiBackend; use crate::util::input; @@ -32,8 +31,7 @@ impl<'a> TuiPrompt<'a> { } { - let mut render_result = RenderResult::new(); - let mut view = TuiView::new(context, &mut render_result); + let mut view = TuiView::new(context); view.show_bottom_status = false; frame.render_widget(view, f_size); } -- cgit v1.2.3