mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 12:05:34 +00:00
fix: use the layout information from television.ui_state to resize with the optimal size
- add argument preview_window in those functions/methods: Previewer.preview -> Previewer.dispatch_request -> FilePreviewer.preview -> FilePreviewer.handle_preview_request -> try_preview -> Image.from_dynamic_image for try_preview, #[allow(clippy::too_many_arguments)] has been added since it has 8 arguments, pehraph would it be better to create a struct instead - resize image if possible with the size of the preview window - get_known_image_file_extensions function has been changed to follow the way of the text equivalent
This commit is contained in:
parent
32c4c042e7
commit
6e6ce28bf5
@ -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<Arc<Preview>> {
|
||||
fn dispatch_request(
|
||||
&mut self,
|
||||
entry: &Entry,
|
||||
preview_window: Option<Rect>,
|
||||
) -> Option<Arc<Preview>> {
|
||||
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<Arc<Preview>> {
|
||||
pub fn preview(
|
||||
&mut self,
|
||||
entry: &Entry,
|
||||
preview_window: Option<Rect>,
|
||||
) -> Option<Arc<Preview>> {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Arc<Preview>> {
|
||||
pub fn preview(
|
||||
&mut self,
|
||||
entry: &entry::Entry,
|
||||
preview_window: Option<Rect>,
|
||||
) -> Option<Arc<Preview>> {
|
||||
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<Arc<Preview>>,
|
||||
preview_window: Option<Rect>,
|
||||
) {
|
||||
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<Arc<Preview>>,
|
||||
@ -151,6 +162,7 @@ pub fn try_preview(
|
||||
syntax_theme: &Arc<Theme>,
|
||||
concurrent_tasks: &Arc<AtomicU8>,
|
||||
in_flight_previews: &Arc<Mutex<FxHashSet<String>>>,
|
||||
preview_window: Option<Rect>,
|
||||
) {
|
||||
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()
|
||||
|
@ -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(
|
||||
|
@ -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<FxHashSet<&'static str>> =
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
@ -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<u8> = Rgba([242, 242, 242, 255]);
|
||||
const WHITE: Rgba<u8> = 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
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user