diff --git a/television/preview/mod.rs b/television/preview/mod.rs index e56c4c4..1188c7b 100644 --- a/television/preview/mod.rs +++ b/television/preview/mod.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use crate::channels::entry::{Entry, PreviewType}; use devicons::FileIcon; +use ratatui::layout::Rect; pub mod ansi; pub mod cache; @@ -208,11 +209,15 @@ impl Previewer { } } - fn dispatch_request(&mut self, entry: &Entry) -> Option> { + fn dispatch_request( + &mut self, + entry: &Entry, + preview_window: Option, + ) -> Option> { match &entry.preview_type { PreviewType::Basic => Some(self.basic.preview(entry)), PreviewType::EnvVar => Some(self.env_var.preview(entry)), - PreviewType::Files => self.file.preview(entry), + PreviewType::Files => self.file.preview(entry, preview_window), PreviewType::Command(cmd) => self.command.preview(entry, cmd), PreviewType::None => Some(Arc::new(Preview::default())), } @@ -222,17 +227,23 @@ impl Previewer { // faster, but since it's already running in the background and quite // fast for most standard file sizes, plus we're caching the previews, // I'm not sure the extra complexity is worth it. - pub fn preview(&mut self, entry: &Entry) -> Option> { + pub fn preview( + &mut self, + entry: &Entry, + preview_window: Option, + ) -> Option> { // if we haven't acknowledged the request yet, acknowledge it self.requests.push(entry.clone()); - if let Some(preview) = self.dispatch_request(entry) { + if let Some(preview) = self.dispatch_request(entry, preview_window) { return Some(preview); } // lookup request stack and return the most recent preview available for request in self.requests.back_to_front() { - if let Some(preview) = self.dispatch_request(&request) { + if let Some(preview) = + self.dispatch_request(&request, preview_window) + { return Some(preview); } } diff --git a/television/preview/previewers/files.rs b/television/preview/previewers/files.rs index 7269cee..34a65ab 100644 --- a/television/preview/previewers/files.rs +++ b/television/preview/previewers/files.rs @@ -2,6 +2,7 @@ use crate::utils::files::{read_into_lines_capped, ReadResult}; use crate::utils::syntax::HighlightedLines; use image::ImageReader; use parking_lot::Mutex; +use ratatui::layout::Rect; use rustc_hash::{FxBuildHasher, FxHashSet}; use std::collections::HashSet; use std::fs::File; @@ -11,7 +12,6 @@ use std::sync::{ atomic::{AtomicU8, Ordering}, Arc, }; - use syntect::{highlighting::Theme, parsing::SyntaxSet}; use tracing::{debug, trace, warn}; @@ -80,20 +80,28 @@ impl FilePreviewer { self.cache.lock().get(&entry.name) } - pub fn preview(&mut self, entry: &entry::Entry) -> Option> { + pub fn preview( + &mut self, + entry: &entry::Entry, + preview_window: Option, + ) -> Option> { if let Some(preview) = self.cached(entry) { trace!("Preview cache hit for {:?}", entry.name); if preview.partial_offset.is_some() { // preview is partial, spawn a task to compute the next chunk // and return the partial preview debug!("Spawning partial preview task for {:?}", entry.name); - self.handle_preview_request(entry, Some(preview.clone())); + self.handle_preview_request( + entry, + Some(preview.clone()), + preview_window, + ); } Some(preview) } else { // preview is not in cache, spawn a task to compute the preview trace!("Preview cache miss for {:?}", entry.name); - self.handle_preview_request(entry, None); + self.handle_preview_request(entry, None, preview_window); None } } @@ -102,6 +110,7 @@ impl FilePreviewer { &mut self, entry: &entry::Entry, partial_preview: Option>, + preview_window: Option, ) { if self.in_flight_previews.lock().contains(&entry.name) { trace!("Preview already in flight for {:?}", entry.name); @@ -128,6 +137,7 @@ impl FilePreviewer { &syntax_theme, &concurrent_tasks, &in_flight_previews, + preview_window, ); }); } @@ -143,6 +153,7 @@ impl FilePreviewer { /// This ends up being the max size of partial previews. const PARTIAL_BUFREAD_SIZE: usize = 5 * 1024 * 1024; +#[allow(clippy::too_many_arguments)] pub fn try_preview( entry: &entry::Entry, partial_preview: Option>, @@ -151,6 +162,7 @@ pub fn try_preview( syntax_theme: &Arc, concurrent_tasks: &Arc, in_flight_previews: &Arc>>, + preview_window: Option, ) { debug!("Computing preview for {:?}", entry.name); let path = PathBuf::from(&entry.name); @@ -246,8 +258,14 @@ pub fn try_preview( debug!("File {:?} is an image", entry.name); match ImageReader::open(path).unwrap().decode() { Ok(image) => { + let preview_window_dimension = preview_window.map(|rect| { + (u32::from(rect.width), u32::from(rect.height)) + }); let image_preview_widget = - ImagePreviewWidget::from_dynamic_image(image); + ImagePreviewWidget::from_dynamic_image( + image, + preview_window_dimension, + ); let total_lines = image_preview_widget .height() .try_into() diff --git a/television/television.rs b/television/television.rs index 07161e6..d3b9c3e 100644 --- a/television/television.rs +++ b/television/television.rs @@ -304,7 +304,10 @@ impl Television { && !matches!(selected_entry.preview_type, PreviewType::None) { // preview content - if let Some(preview) = self.previewer.preview(selected_entry) { + if let Some(preview) = self + .previewer + .preview(selected_entry, self.ui_state.layout.preview_window) + { // only update if the preview content has changed if self.preview_state.preview.title != preview.title { self.preview_state.update( diff --git a/television/utils/files.rs b/television/utils/files.rs index 4e97e02..17cf3df 100644 --- a/television/utils/files.rs +++ b/television/utils/files.rs @@ -499,31 +499,26 @@ where path.as_ref() .extension() .and_then(|ext| ext.to_str()) - .is_some_and(|ext| KNOWN_IMAGE_FILE_EXTENSIONS.contains(ext)) + .is_some_and(|ext| get_known_image_file_extensions().contains(ext)) } - -lazy_static! { - static ref KNOWN_IMAGE_FILE_EXTENSIONS: FxHashSet<&'static str> = [ - //"avif", - //"bmp", - //"dds", - //"farbfeld", - "gif", - //"hdr", - "ico", - "jpeg", - "jpg", - //"exr", - "png", - //"pnm", - //"qoi", - //"tga", - "tif", - "tiff", - "webp", - - ] - .iter() - .copied() - .collect(); +pub static KNOWN_IMAGE_FILE_EXTENSIONS: OnceLock> = + OnceLock::new(); +pub fn get_known_image_file_extensions() -> &'static FxHashSet<&'static str> { + KNOWN_IMAGE_FILE_EXTENSIONS.get_or_init(|| { + [ + //"avif", + //"bmp", + //"dds", + //"farbfeld", + "gif", //"hdr", + "ico", "jpeg", "jpg", //"exr", + "png", //"pnm", + //"qoi", + //"tga", + "tif", "tiff", "webp", + ] + .iter() + .copied() + .collect() + }) } diff --git a/television/utils/image.rs b/television/utils/image.rs index 196fe5b..d6b5e42 100644 --- a/television/utils/image.rs +++ b/television/utils/image.rs @@ -8,11 +8,11 @@ use std::fmt::Debug; use std::hash::Hash; static PIXEL_STRING: &str = "▀"; -const FILTER_TYPE: FilterType = FilterType::Lanczos3; +const FILTER_TYPE: FilterType = FilterType::Triangle; // use to reduce the size of the image before storing it -const CACHED_WIDTH: u32 = 50; -const CACHED_HEIGHT: u32 = 100; +const DEFAULT_CACHED_WIDTH: u32 = 50; +const DEFAULT_CACHED_HEIGHT: u32 = 100; const GRAY: Rgba = Rgba([242, 242, 242, 255]); const WHITE: Rgba = Rgba([255, 255, 255, 255]); @@ -70,24 +70,33 @@ impl ImagePreviewWidget { } } - pub fn from_dynamic_image(dynamic_image: DynamicImage) -> Self { + pub fn from_dynamic_image( + dynamic_image: DynamicImage, + dimension: Option<(u32, u32)>, + ) -> Self { // first quick resize - let big_resized_image = if dynamic_image.width() > CACHED_WIDTH * 4 - || dynamic_image.height() > CACHED_HEIGHT * 4 + let (window_width, window_height) = + dimension.unwrap_or((DEFAULT_CACHED_WIDTH, DEFAULT_CACHED_HEIGHT)); + let big_resized_image = if dynamic_image.width() > window_width * 4 + || dynamic_image.height() > window_height * 4 { dynamic_image.resize( - CACHED_WIDTH * 4, - CACHED_HEIGHT * 4, + window_width * 4, + window_height * 4, FilterType::Nearest, ) } else { dynamic_image }; // this time resize with the filter - let resized_image = if big_resized_image.width() > CACHED_WIDTH - || big_resized_image.height() > CACHED_HEIGHT + let resized_image = if big_resized_image.width() > window_width + || big_resized_image.height() > window_height { - big_resized_image.resize(CACHED_WIDTH, CACHED_HEIGHT, FILTER_TYPE) + big_resized_image.resize( + window_width, + DEFAULT_CACHED_HEIGHT, + FILTER_TYPE, + ) } else { big_resized_image };