summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorqkzk <qu3nt1n@gmail.com>2022-12-14 07:12:22 +0100
committerqkzk <qu3nt1n@gmail.com>2022-12-14 07:12:22 +0100
commit33ad655dd8d734d12dfcdcdff1ac0986cc3a6605 (patch)
tree9f3be751440c4697968b813a783788e439acc670
parent894d3b30ece9c7a5e4fcf6b3600b103587e4e908 (diff)
image: exif by default, char('T') for thumbnailimage-preview
-rw-r--r--src/action_map.rs2
-rw-r--r--src/event_exec.rs11
-rw-r--r--src/fm_error.rs6
-rw-r--r--src/help.rs1
-rw-r--r--src/keybindings.rs77
-rw-r--r--src/preview.rs153
-rw-r--r--src/term_manager.rs24
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 => (),
}