diff options
author | qkzk <qu3nt1n@gmail.com> | 2022-12-14 07:12:22 +0100 |
---|---|---|
committer | qkzk <qu3nt1n@gmail.com> | 2022-12-14 07:12:22 +0100 |
commit | 33ad655dd8d734d12dfcdcdff1ac0986cc3a6605 (patch) | |
tree | 9f3be751440c4697968b813a783788e439acc670 | |
parent | 894d3b30ece9c7a5e4fcf6b3600b103587e4e908 (diff) |
image: exif by default, char('T') for thumbnailimage-preview
-rw-r--r-- | src/action_map.rs | 2 | ||||
-rw-r--r-- | src/event_exec.rs | 11 | ||||
-rw-r--r-- | src/fm_error.rs | 6 | ||||
-rw-r--r-- | src/help.rs | 1 | ||||
-rw-r--r-- | src/keybindings.rs | 77 | ||||
-rw-r--r-- | src/preview.rs | 153 | ||||
-rw-r--r-- | src/term_manager.rs | 24 |
7 files changed, 154 insertions, 120 deletions
diff --git a/src/action_map.rs b/src/action_map.rs index c51ed94..eb94dfd 100644 --- a/src/action_map.rs +++ b/src/action_map.rs @@ -58,6 +58,7 @@ pub enum ActionMap { Sort, Symlink, Tab, + Thumbnail, ToggleFlag, ToggleHidden, } @@ -67,6 +68,7 @@ impl ActionMap { let current_tab = status.selected(); match *self { ActionMap::ToggleHidden => EventExec::event_toggle_hidden(current_tab), + ActionMap::Thumbnail => EventExec::event_thumbnail(current_tab), ActionMap::CopyPaste => EventExec::event_copy_paste(current_tab), ActionMap::CutPaste => EventExec::event_cut_paste(current_tab), ActionMap::NewDir => EventExec::event_new_dir(current_tab), diff --git a/src/event_exec.rs b/src/event_exec.rs index 19b7ae3..fbdc88a 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -1074,6 +1074,17 @@ impl EventExec { Self::refresh_selected_view(status) } + pub fn event_thumbnail(tab: &mut Tab) -> FmResult<()> { + if let Mode::Normal = tab.mode { + tab.mode = Mode::Preview; + if let Some(file_info) = tab.path_content.selected_file() { + tab.preview = Preview::thumbnail(file_info.path.to_owned())?; + tab.window.reset(tab.preview.len()); + } + } + Ok(()) + } + pub fn event_toggle_display_full(status: &mut Status) -> FmResult<()> { status.display_full = !status.display_full; Ok(()) diff --git a/src/fm_error.rs b/src/fm_error.rs index a7fc549..59d2ab2 100644 --- a/src/fm_error.rs +++ b/src/fm_error.rs @@ -24,6 +24,7 @@ pub enum ErrorVariant { FMT, STRUM, COMPRESSTOOLS, + IMAGEERROR, CUSTOM(String), } @@ -144,4 +145,9 @@ impl From<compress_tools::Error> for FmError { } } +impl From<image::error::ImageError> for FmError { + fn from(error: image::error::ImageError) -> Self { + Self::new(ErrorVariant::IMAGEERROR, &error.to_string()) + } +} pub type FmResult<T> = Result<T, FmError>; diff --git a/src/help.rs b/src/help.rs index f2ea411..bf1626d 100644 --- a/src/help.rs +++ b/src/help.rs @@ -32,6 +32,7 @@ impl Help { {OpenFile}: open this file {NvimFilepicker}: open in current nvim session {Preview}: preview this file +{Thumbnail}: display a thumbnail of an image {DisplayFull}: toggle details on files {FuzzyFind}: fuzzy finder {RefreshView}: refresh view diff --git a/src/keybindings.rs b/src/keybindings.rs index 4df0b21..9002a71 100644 --- a/src/keybindings.rs +++ b/src/keybindings.rs @@ -21,47 +21,12 @@ impl Default for Bindings { impl Bindings { fn new() -> Self { let binds = HashMap::from([ - (Key::Char('a'), ActionMap::ToggleHidden), - (Key::Char('c'), ActionMap::CopyPaste), - (Key::Char('p'), ActionMap::CutPaste), - (Key::Char('d'), ActionMap::NewDir), - (Key::Char('n'), ActionMap::NewFile), - (Key::Char('m'), ActionMap::Chmod), - (Key::Char('e'), ActionMap::Exec), - (Key::Char('g'), ActionMap::Goto), - (Key::Char('r'), ActionMap::Rename), - (Key::Char('u'), ActionMap::ClearFlags), - (Key::Char(' '), ActionMap::ToggleFlag), - (Key::Char('s'), ActionMap::Shell), - (Key::Char('x'), ActionMap::DeleteFile), - (Key::Char('o'), ActionMap::OpenFile), - (Key::Char('h'), ActionMap::Help), - (Key::Char('/'), ActionMap::Search), - (Key::Char('w'), ActionMap::RegexMatch), - (Key::Char('q'), ActionMap::Quit), - (Key::Char('*'), ActionMap::FlagAll), - (Key::Char('v'), ActionMap::ReverseFlags), - (Key::Char('j'), ActionMap::Jump), - (Key::Char('H'), ActionMap::History), - (Key::Char('i'), ActionMap::NvimFilepicker), - (Key::Char('O'), ActionMap::Sort), - (Key::Char('l'), ActionMap::Symlink), - (Key::Char('P'), ActionMap::Preview), - (Key::Char('G'), ActionMap::Shortcut), - (Key::Char('B'), ActionMap::Bulkrename), - (Key::Char('M'), ActionMap::MarksNew), - (Key::Char('\''), ActionMap::MarksJump), - (Key::Char('F'), ActionMap::Filter), - (Key::Char('-'), ActionMap::Back), - (Key::Char('~'), ActionMap::Home), (Key::ESC, ActionMap::ModeNormal), (Key::Up, ActionMap::MoveUp), (Key::Down, ActionMap::MoveDown), (Key::Left, ActionMap::MoveLeft), (Key::Right, ActionMap::MoveRight), (Key::Backspace, ActionMap::Backspace), - (Key::Ctrl('d'), ActionMap::Delete), - (Key::Ctrl('q'), ActionMap::ModeNormal), (Key::Home, ActionMap::KeyHome), (Key::End, ActionMap::End), (Key::PageDown, ActionMap::PageDown), @@ -69,12 +34,48 @@ impl Bindings { (Key::Enter, ActionMap::Enter), (Key::Tab, ActionMap::Tab), (Key::BackTab, ActionMap::BackTab), - (Key::Ctrl('f'), ActionMap::FuzzyFind), + (Key::Char(' '), ActionMap::ToggleFlag), + (Key::Char('/'), ActionMap::Search), + (Key::Char('*'), ActionMap::FlagAll), + (Key::Char('\''), ActionMap::MarksJump), + (Key::Char('-'), ActionMap::Back), + (Key::Char('~'), ActionMap::Home), + (Key::Char('B'), ActionMap::Bulkrename), + (Key::Char('F'), ActionMap::Filter), + (Key::Char('G'), ActionMap::Shortcut), + (Key::Char('H'), ActionMap::History), + (Key::Char('M'), ActionMap::MarksNew), + (Key::Char('O'), ActionMap::Sort), + (Key::Char('P'), ActionMap::Preview), + (Key::Char('T'), ActionMap::Thumbnail), + (Key::Char('a'), ActionMap::ToggleHidden), + (Key::Char('c'), ActionMap::CopyPaste), + (Key::Char('d'), ActionMap::NewDir), + (Key::Char('e'), ActionMap::Exec), + (Key::Char('g'), ActionMap::Goto), + (Key::Char('h'), ActionMap::Help), + (Key::Char('i'), ActionMap::NvimFilepicker), + (Key::Char('j'), ActionMap::Jump), + (Key::Char('l'), ActionMap::Symlink), + (Key::Char('m'), ActionMap::Chmod), + (Key::Char('n'), ActionMap::NewFile), + (Key::Char('o'), ActionMap::OpenFile), + (Key::Char('p'), ActionMap::CutPaste), + (Key::Char('q'), ActionMap::Quit), + (Key::Char('r'), ActionMap::Rename), + (Key::Char('s'), ActionMap::Shell), + (Key::Char('u'), ActionMap::ClearFlags), + (Key::Char('v'), ActionMap::ReverseFlags), + (Key::Char('w'), ActionMap::RegexMatch), + (Key::Char('x'), ActionMap::DeleteFile), + (Key::Alt('d'), ActionMap::DragNDrop), (Key::Ctrl('c'), ActionMap::CopyFilename), + (Key::Ctrl('d'), ActionMap::Delete), + (Key::Ctrl('e'), ActionMap::DisplayFull), + (Key::Ctrl('f'), ActionMap::FuzzyFind), (Key::Ctrl('p'), ActionMap::CopyFilepath), + (Key::Ctrl('q'), ActionMap::ModeNormal), (Key::Ctrl('r'), ActionMap::RefreshView), - (Key::Ctrl('e'), ActionMap::DisplayFull), - (Key::Alt('d'), ActionMap::DragNDrop), ]); Self { binds } } diff --git a/src/preview.rs b/src/preview.rs index 8cf4141..0f38600 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -26,45 +26,15 @@ pub enum Preview { Binary(BinaryContent), Pdf(PdfContent), Compressed(CompressedContent), - // Image(ExifContent), - Image(Pixels), + Exif(ExifContent), + Thumbnail(Pixels), Media(MediainfoContent), Empty, } -fn is_ext_image(ext: &str) -> bool { - matches!(ext, "png" | "jpg" | "jpeg" | "tiff" | "heif") -} - -fn is_ext_media(ext: &str) -> bool { - matches!( - ext, - "mkv" - | "ogg" - | "ogm" - | "riff" - | "mpeg" - | "mp2" - | "mp3" - | "mp4" - | "wm" - | "qt" - | "ac3" - | "dts" - | "aac" - | "mac" - | "flac" - | "avi" - ) -} - impl Preview { const CONTENT_INSPECTOR_MIN_SIZE: usize = 1024; - pub fn empty() -> Self { - Self::Empty - } - pub fn new(path_content: &PathContent) -> FmResult<Self> { match path_content.selected_file() { Some(file_info) => match file_info.extension.to_lowercase().as_str() { @@ -72,7 +42,7 @@ impl Preview { file_info.path.clone(), )?)), "pdf" => Ok(Self::Pdf(PdfContent::new(file_info.path.clone()))), - e if is_ext_image(e) => Ok(Self::Image(Pixels::new(file_info.path.clone())?)), + e if is_ext_image(e) => Ok(Self::Exif(ExifContent::new(file_info.path.clone())?)), e if is_ext_media(e) => { Ok(Self::Media(MediainfoContent::new(file_info.path.clone())?)) } @@ -100,10 +70,18 @@ impl Preview { } } + pub fn thumbnail(path: PathBuf) -> FmResult<Self> { + Ok(Self::Thumbnail(Pixels::new(path)?)) + } + pub fn help(help: String) -> Self { Self::Text(TextContent::help(help)) } + pub fn empty() -> Self { + Self::Empty + } + pub fn len(&self) -> usize { match self { Self::Empty => 0, @@ -112,7 +90,8 @@ impl Preview { Self::Binary(binary) => binary.len(), Self::Pdf(pdf) => pdf.len(), Self::Compressed(zip) => zip.len(), - Self::Image(_img) => 0, + Self::Thumbnail(_img) => 0, + Self::Exif(exif_content) => exif_content.len(), Self::Media(media) => media.len(), } } @@ -405,42 +384,42 @@ impl CompressedContent { } } -// #[derive(Clone)] -// pub struct ExifContent { -// length: usize, -// pub content: Vec<String>, -// } -// -// impl ExifContent { -// fn new(path: PathBuf) -> FmResult<Self> { -// let mut bufreader = std::io::BufReader::new(std::fs::File::open(path)?); -// let content: Vec<String> = -// if let Ok(exif) = exif::Reader::new().read_from_container(&mut bufreader) { -// exif.fields() -// .map(|f| Self::format_exif_field(f, &exif)) -// .collect() -// } else { -// vec![] -// }; -// Ok(Self { -// length: content.len(), -// content, -// }) -// } -// -// fn format_exif_field(f: &exif::Field, exif: &exif::Exif) -> String { -// format!( -// "{} {} {}", -// f.tag, -// f.ifd_num, -// f.display_value().with_unit(exif) -// ) -// } -// -// fn len(&self) -> usize { -// self.length -// } -// } +#[derive(Clone)] +pub struct ExifContent { + length: usize, + pub content: Vec<String>, +} + +impl ExifContent { + fn new(path: PathBuf) -> FmResult<Self> { + let mut bufreader = std::io::BufReader::new(std::fs::File::open(path)?); + let content: Vec<String> = + if let Ok(exif) = exif::Reader::new().read_from_container(&mut bufreader) { + exif.fields() + .map(|f| Self::format_exif_field(f, &exif)) + .collect() + } else { + vec![] + }; + Ok(Self { + length: content.len(), + content, + }) + } + + fn format_exif_field(f: &exif::Field, exif: &exif::Exif) -> String { + format!( + "{} {} {}", + f.tag, + f.ifd_num, + f.display_value().with_unit(exif) + ) + } + + fn len(&self) -> usize { + self.length + } +} #[derive(Clone)] pub struct MediainfoContent { @@ -478,9 +457,9 @@ impl Pixels { Ok(Self { img_path }) } - pub fn resized_rgb8(&self, width: u32, height: u32) -> ImageBuffer<Rgb<u8>, Vec<u8>> { - let img = image::open(&self.img_path).unwrap(); - img.resize(width, height, FilterType::Nearest).to_rgb8() + pub fn resized_rgb8(&self, width: u32, height: u32) -> FmResult<ImageBuffer<Rgb<u8>, Vec<u8>>> { + let img = image::open(&self.img_path)?; + Ok(img.resize(width, height, FilterType::Nearest).to_rgb8()) } } @@ -531,9 +510,35 @@ impl_window!(TextContent, String); impl_window!(BinaryContent, Line); impl_window!(PdfContent, String); impl_window!(CompressedContent, String); -// impl_window!(ExifContent, String); +impl_window!(ExifContent, String); impl_window!(MediainfoContent, String); +fn is_ext_image(ext: &str) -> bool { + matches!(ext, "png" | "jpg" | "jpeg" | "tiff" | "heif") +} + +fn is_ext_media(ext: &str) -> bool { + matches!( + ext, + "mkv" + | "ogg" + | "ogm" + | "riff" + | "mpeg" + | "mp2" + | "mp3" + | "mp4" + | "wm" + | "qt" + | "ac3" + | "dts" + | "aac" + | "mac" + | "flac" + | "avi" + ) +} + fn catch_unwind_silent<F: FnOnce() -> R + panic::UnwindSafe, R>(f: F) -> std::thread::Result<R> { let prev_hook = panic::take_hook(); panic::set_hook(Box::new(|_| {})); diff --git a/src/term_manager.rs b/src/term_manager.rs index e612089..fa92b0b 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -392,20 +392,28 @@ impl<'a> WinTab<'a> { canvas.print(row, line_number_width + 3, line)?; } } - Preview::Image(image) => { + Preview::Thumbnail(image) => { let (width, height) = canvas.size()?; - let scaled_image = (*image).resized_rgb8(width as u32 / 2, height as u32 - 3); - let (width, _) = scaled_image.dimensions(); - for (i, pixel) in scaled_image.pixels().enumerate() { - let (r, g, b) = pixel_values(pixel); - let (row, col) = pixel_position(i, width); - print_pixel(canvas, row, col, r, g, b)?; + if let Ok(scaled_image) = (*image).resized_rgb8(width as u32 / 2, height as u32 - 3) + { + let (width, _) = scaled_image.dimensions(); + for (i, pixel) in scaled_image.pixels().enumerate() { + let (r, g, b) = pixel_values(pixel); + let (row, col) = pixel_position(i, width); + print_pixel(canvas, row, col, r, g, b)?; + } + } else { + canvas.print( + 3, + 3, + &format!("Not a displayable image: {:?}", image.img_path), + )?; } } Preview::Text(text) => impl_preview!(text, tab, length, canvas, line_number_width), Preview::Pdf(text) => impl_preview!(text, tab, length, canvas, line_number_width), - // Preview::Image(text) => impl_preview!(text, tab, length, canvas, line_number_width), + Preview::Exif(text) => impl_preview!(text, tab, length, canvas, line_number_width), Preview::Media(text) => impl_preview!(text, tab, length, canvas, line_number_width), Preview::Empty => (), } |