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