From 2a54572fb783ca1465454fa1e17eaae89d524577 Mon Sep 17 00:00:00 2001 From: gaesa <71256557+gaesa@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:42:34 +0000 Subject: [PATCH] feat: support customizable alignment for image display --- yazi-adapter/src/adapter.rs | 16 +- yazi-adapter/src/drivers/chafa.rs | 18 +- yazi-adapter/src/drivers/iip.rs | 6 +- yazi-adapter/src/drivers/kgp.rs | 6 +- yazi-adapter/src/drivers/kgp_old.rs | 6 +- yazi-adapter/src/drivers/sixel.rs | 6 +- yazi-adapter/src/drivers/ueberzug.rs | 18 +- yazi-adapter/src/image.rs | 18 +- yazi-adapter/src/lib.rs | 2 +- yazi-adapter/src/offset.rs | 26 +++ yazi-config/preset/yazi.toml | 273 ++++++++++++++------------- yazi-config/src/preview/alignment.rs | 27 +++ yazi-config/src/preview/mod.rs | 2 +- yazi-config/src/preview/preview.rs | 6 +- yazi-plugin/src/utils/image.rs | 24 ++- 15 files changed, 262 insertions(+), 192 deletions(-) create mode 100644 yazi-adapter/src/offset.rs create mode 100644 yazi-config/src/preview/alignment.rs diff --git a/yazi-adapter/src/adapter.rs b/yazi-adapter/src/adapter.rs index a3537ea7d..ac286f01e 100644 --- a/yazi-adapter/src/adapter.rs +++ b/yazi-adapter/src/adapter.rs @@ -5,7 +5,7 @@ use ratatui::layout::Rect; use tracing::warn; use yazi_shared::env_exists; -use crate::{Brand, Emulator, SHOWN, TMUX, WSL, drivers}; +use crate::{Brand, Emulator, Offset, SHOWN, TMUX, WSL, drivers}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Adapter { @@ -35,18 +35,18 @@ impl Display for Adapter { } impl Adapter { - pub async fn image_show(self, path: &Path, max: Rect) -> Result { + pub async fn image_show(self, path: &Path, max: Rect, offset: Option) -> Result { if max.is_empty() { return Ok(Rect::default()); } match self { - Self::Kgp => drivers::Kgp::image_show(path, max).await, - Self::KgpOld => drivers::KgpOld::image_show(path, max).await, - Self::Iip => drivers::Iip::image_show(path, max).await, - Self::Sixel => drivers::Sixel::image_show(path, max).await, - Self::X11 | Self::Wayland => drivers::Ueberzug::image_show(path, max).await, - Self::Chafa => drivers::Chafa::image_show(path, max).await, + Self::Kgp => drivers::Kgp::image_show(path, max, offset).await, + Self::KgpOld => drivers::KgpOld::image_show(path, max, offset).await, + Self::Iip => drivers::Iip::image_show(path, max, offset).await, + Self::Sixel => drivers::Sixel::image_show(path, max, offset).await, + Self::X11 | Self::Wayland => drivers::Ueberzug::image_show(path, max, offset).await, + Self::Chafa => drivers::Chafa::image_show(path, max, offset).await, } } diff --git a/yazi-adapter/src/drivers/chafa.rs b/yazi-adapter/src/drivers/chafa.rs index 63bc9ff26..da578033c 100644 --- a/yazi-adapter/src/drivers/chafa.rs +++ b/yazi-adapter/src/drivers/chafa.rs @@ -3,15 +3,15 @@ use std::{io::Write, path::Path, process::Stdio}; use ansi_to_tui::IntoText; use anyhow::{Result, bail}; use crossterm::{cursor::MoveTo, queue}; -use ratatui::layout::Rect; +use ratatui::layout::{Rect, Size}; use tokio::process::Command; -use crate::{Adapter, Emulator}; +use crate::{Adapter, Emulator, Offset}; pub(crate) struct Chafa; impl Chafa { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option) -> Result { let output = Command::new("chafa") .args([ "-f", @@ -46,11 +46,13 @@ impl Chafa { bail!("failed to parse chafa output"); }; - let area = Rect { - x: max.x, - y: max.y, - width: first.width() as u16, - height: lines.len() as u16, + let area = { + let width = first.width() as u16; + let height = lines.len() as u16; + let offset = offset.unwrap_or_else(|| { + Offset::from((Size { width, height }, Size { width: max.width, height: max.height })) + }); + Rect { x: max.x + offset.x, y: max.y + offset.y, width, height } }; Adapter::Chafa.image_hide()?; diff --git a/yazi-adapter/src/drivers/iip.rs b/yazi-adapter/src/drivers/iip.rs index 406570acf..589dd2207 100644 --- a/yazi-adapter/src/drivers/iip.rs +++ b/yazi-adapter/src/drivers/iip.rs @@ -7,14 +7,14 @@ use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEn use ratatui::layout::Rect; use yazi_config::PREVIEW; -use crate::{CLOSE, Emulator, Image, START, adapter::Adapter}; +use crate::{CLOSE, Emulator, Image, Offset, START, adapter::Adapter}; pub(crate) struct Iip; impl Iip { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option) -> Result { let img = Image::downscale(path, max).await?; - let area = Image::pixel_area((img.width(), img.height()), max); + let area = Image::pixel_area((img.width(), img.height()), max, offset); let b = Self::encode(img).await?; Adapter::Iip.image_hide()?; diff --git a/yazi-adapter/src/drivers/kgp.rs b/yazi-adapter/src/drivers/kgp.rs index cbae32b8a..e7b9c750d 100644 --- a/yazi-adapter/src/drivers/kgp.rs +++ b/yazi-adapter/src/drivers/kgp.rs @@ -7,7 +7,7 @@ use crossterm::{cursor::MoveTo, queue}; use image::DynamicImage; use ratatui::layout::Rect; -use crate::{CLOSE, ESCAPE, Emulator, START, adapter::Adapter, image::Image}; +use crate::{CLOSE, ESCAPE, Emulator, Offset, START, adapter::Adapter, image::Image}; static DIACRITICS: [char; 297] = [ '\u{0305}', @@ -312,9 +312,9 @@ static DIACRITICS: [char; 297] = [ pub(crate) struct Kgp; impl Kgp { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option) -> Result { let img = Image::downscale(path, max).await?; - let area = Image::pixel_area((img.width(), img.height()), max); + let area = Image::pixel_area((img.width(), img.height()), max, offset); let b1 = Self::encode(img).await?; let b2 = Self::place(&area)?; diff --git a/yazi-adapter/src/drivers/kgp_old.rs b/yazi-adapter/src/drivers/kgp_old.rs index 86c88159e..f7db0c17c 100644 --- a/yazi-adapter/src/drivers/kgp_old.rs +++ b/yazi-adapter/src/drivers/kgp_old.rs @@ -6,14 +6,14 @@ use base64::{Engine, engine::general_purpose}; use image::DynamicImage; use ratatui::layout::Rect; -use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter}; +use crate::{CLOSE, ESCAPE, Emulator, Image, Offset, START, adapter::Adapter}; pub(crate) struct KgpOld; impl KgpOld { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option) -> Result { let img = Image::downscale(path, max).await?; - let area = Image::pixel_area((img.width(), img.height()), max); + let area = Image::pixel_area((img.width(), img.height()), max, offset); let b = Self::encode(img).await?; Adapter::KgpOld.image_hide()?; diff --git a/yazi-adapter/src/drivers/sixel.rs b/yazi-adapter/src/drivers/sixel.rs index 859fea38c..715ef14c4 100644 --- a/yazi-adapter/src/drivers/sixel.rs +++ b/yazi-adapter/src/drivers/sixel.rs @@ -7,14 +7,14 @@ use image::DynamicImage; use ratatui::layout::Rect; use yazi_config::PREVIEW; -use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter}; +use crate::{CLOSE, ESCAPE, Emulator, Image, Offset, START, adapter::Adapter}; pub(crate) struct Sixel; impl Sixel { - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option) -> Result { let img = Image::downscale(path, max).await?; - let area = Image::pixel_area((img.width(), img.height()), max); + let area = Image::pixel_area((img.width(), img.height()), max, offset); let b = Self::encode(img).await?; Adapter::Sixel.image_hide()?; diff --git a/yazi-adapter/src/drivers/ueberzug.rs b/yazi-adapter/src/drivers/ueberzug.rs index 7620af006..0c8da88a6 100644 --- a/yazi-adapter/src/drivers/ueberzug.rs +++ b/yazi-adapter/src/drivers/ueberzug.rs @@ -1,13 +1,13 @@ use std::{path::{Path, PathBuf}, process::Stdio}; use anyhow::{Result, bail}; -use ratatui::layout::Rect; +use ratatui::layout::{Rect, Size}; use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}}; use tracing::{debug, warn}; use yazi_config::PREVIEW; use yazi_shared::{RoCell, env_exists}; -use crate::{Adapter, Dimension}; +use crate::{Adapter, Dimension, Offset}; type Cmd = Option<(PathBuf, Rect)>; @@ -41,7 +41,7 @@ impl Ueberzug { DEMON.init(Some(tx)) } - pub(crate) async fn image_show(path: &Path, max: Rect) -> Result { + pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option) -> Result { let Some(tx) = &*DEMON else { bail!("uninitialized ueberzugpp"); }; @@ -50,11 +50,13 @@ impl Ueberzug { let (w, h) = tokio::task::spawn_blocking(move || image::image_dimensions(p)).await??; let area = Dimension::ratio() - .map(|(r1, r2)| Rect { - x: max.x, - y: max.y, - width: max.width.min((w.min(PREVIEW.max_width as _) as f64 / r1).ceil() as _), - height: max.height.min((h.min(PREVIEW.max_height as _) as f64 / r2).ceil() as _), + .map(|(r1, r2)| { + let width = max.width.min((w.min(PREVIEW.max_width as _) as f64 / r1).ceil() as _); + let height = max.height.min((h.min(PREVIEW.max_height as _) as f64 / r2).ceil() as _); + let offset = offset.unwrap_or_else(|| { + Offset::from((Size { width, height }, Size { width: max.width, height: max.height })) + }); + Rect { x: max.x + offset.x, y: max.y + offset.y, width, height } }) .unwrap_or(max); diff --git a/yazi-adapter/src/image.rs b/yazi-adapter/src/image.rs index a2ba06e4d..4227761e2 100644 --- a/yazi-adapter/src/image.rs +++ b/yazi-adapter/src/image.rs @@ -2,10 +2,10 @@ use std::path::{Path, PathBuf}; use anyhow::Result; use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation}; -use ratatui::layout::Rect; +use ratatui::layout::{Rect, Size}; use yazi_config::{PREVIEW, TASKS}; -use crate::Dimension; +use crate::{Dimension, Offset}; pub struct Image; @@ -73,13 +73,15 @@ impl Image { .unwrap_or((PREVIEW.max_width, PREVIEW.max_height)) } - pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect { + pub(super) fn pixel_area(size: (u32, u32), rect: Rect, offset: Option) -> Rect { Dimension::ratio() - .map(|(r1, r2)| Rect { - x: rect.x, - y: rect.y, - width: (size.0 as f64 / r1).ceil() as u16, - height: (size.1 as f64 / r2).ceil() as u16, + .map(|(r1, r2)| { + let width = (size.0 as f64 / r1).ceil() as u16; + let height = (size.1 as f64 / r2).ceil() as u16; + let offset = offset.unwrap_or_else(|| { + Offset::from((Size { width, height }, Size { width: rect.width, height: rect.height })) + }); + Rect { x: rect.x + offset.x, y: rect.y + offset.y, width, height } }) .unwrap_or(rect) } diff --git a/yazi-adapter/src/lib.rs b/yazi-adapter/src/lib.rs index 1b177e54e..b2111365c 100644 --- a/yazi-adapter/src/lib.rs +++ b/yazi-adapter/src/lib.rs @@ -2,7 +2,7 @@ yazi_macro::mod_pub!(drivers); -yazi_macro::mod_flat!(adapter brand dimension emulator image info mux unknown); +yazi_macro::mod_flat!(adapter brand dimension emulator image info mux offset unknown); use yazi_shared::{RoCell, SyncCell, env_exists, in_wsl}; diff --git a/yazi-adapter/src/offset.rs b/yazi-adapter/src/offset.rs new file mode 100644 index 000000000..c47e1903e --- /dev/null +++ b/yazi-adapter/src/offset.rs @@ -0,0 +1,26 @@ +use ratatui::layout::Size; +use yazi_config::{PREVIEW, preview::{HorizontalAlignment, VerticalAlignment}}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Offset { + pub x: u16, + pub y: u16, +} + +impl From<(Size, Size)> for Offset { + fn from(value: (Size, Size)) -> Self { + let inner = value.0; + let outer = value.1; + let offset_x = match PREVIEW.alignment.horizontal { + HorizontalAlignment::Left => 0, + HorizontalAlignment::Center => (outer.width - inner.width) / 2, + HorizontalAlignment::Right => outer.width - inner.width, + }; + let offset_y = match PREVIEW.alignment.vertical { + VerticalAlignment::Top => 0, + VerticalAlignment::Center => (outer.height - inner.height) / 2, + VerticalAlignment::Bottom => outer.height - inner.height, + }; + Self { x: offset_x, y: offset_y } + } +} diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index 52cd6e342..55dec2c79 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -3,214 +3,215 @@ "$schema" = "https://yazi-rs.github.io/schemas/yazi.json" [manager] -ratio = [ 1, 4, 3 ] -sort_by = "alphabetical" +ratio = [1, 4, 3] +sort_by = "alphabetical" sort_sensitive = false -sort_reverse = false +sort_reverse = false sort_dir_first = true -sort_translit = false -linemode = "none" -show_hidden = false -show_symlink = true -scrolloff = 5 -mouse_events = [ "click", "scroll" ] -title_format = "Yazi: {cwd}" +sort_translit = false +linemode = "none" +show_hidden = false +show_symlink = true +scrolloff = 5 +mouse_events = ["click", "scroll"] +title_format = "Yazi: {cwd}" [preview] -wrap = "no" -tab_size = 2 -max_width = 600 -max_height = 900 -cache_dir = "" -image_delay = 30 -image_filter = "triangle" -image_quality = 75 -sixel_fraction = 15 -ueberzug_scale = 1 -ueberzug_offset = [ 0, 0, 0, 0 ] +wrap = "no" +tab_size = 2 +max_width = 600 +max_height = 900 +alignment = { horizontal = "center", vertical = "top" } +cache_dir = "" +image_delay = 30 +image_filter = "triangle" +image_quality = 75 +sixel_fraction = 15 +ueberzug_scale = 1 +ueberzug_offset = [0, 0, 0, 0] [opener] edit = [ - { run = '${EDITOR:-vi} "$@"', desc = "$EDITOR", block = true, for = "unix" }, - { run = 'code %*', orphan = true, desc = "code", for = "windows" }, - { run = 'code -w %*', block = true, desc = "code (block)", for = "windows" }, + { run = '${EDITOR:-vi} "$@"', desc = "$EDITOR", block = true, for = "unix" }, + { run = 'code %*', orphan = true, desc = "code", for = "windows" }, + { run = 'code -w %*', block = true, desc = "code (block)", for = "windows" }, ] open = [ - { run = 'xdg-open "$1"', desc = "Open", for = "linux" }, - { run = 'open "$@"', desc = "Open", for = "macos" }, - { run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, + { run = 'xdg-open "$1"', desc = "Open", for = "linux" }, + { run = 'open "$@"', desc = "Open", for = "macos" }, + { run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, ] reveal = [ - { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, - { run = 'open -R "$1"', desc = "Reveal", for = "macos" }, - { run = 'explorer /select,"%1"', orphan = true, desc = "Reveal", for = "windows" }, - { run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" }, + { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, + { run = 'open -R "$1"', desc = "Reveal", for = "macos" }, + { run = 'explorer /select,"%1"', orphan = true, desc = "Reveal", for = "windows" }, + { run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" }, ] extract = [ - { run = 'ya pub extract --list "$@"', desc = "Extract here", for = "unix" }, - { run = 'ya pub extract --list %*', desc = "Extract here", for = "windows" }, + { run = 'ya pub extract --list "$@"', desc = "Extract here", for = "unix" }, + { run = 'ya pub extract --list %*', desc = "Extract here", for = "windows" }, ] play = [ - { run = 'mpv --force-window "$@"', orphan = true, for = "unix" }, - { run = 'mpv --force-window %*', orphan = true, for = "windows" }, - { run = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" }, + { run = 'mpv --force-window "$@"', orphan = true, for = "unix" }, + { run = 'mpv --force-window %*', orphan = true, for = "windows" }, + { run = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" }, ] [open] rules = [ - # Folder - { name = "*/", use = [ "edit", "open", "reveal" ] }, - # Text - { mime = "text/*", use = [ "edit", "reveal" ] }, - # Image - { mime = "image/*", use = [ "open", "reveal" ] }, - # Media - { mime = "{audio,video}/*", use = [ "play", "reveal" ] }, - # Archive - { mime = "application/{,g}zip", use = [ "extract", "reveal" ] }, - { mime = "application/{tar,bzip*,7z*,xz,rar}", use = [ "extract", "reveal" ] }, - # JSON - { mime = "application/{json,ndjson}", use = [ "edit", "reveal" ] }, - { mime = "*/javascript", use = [ "edit", "reveal" ] }, - # Empty file - { mime = "inode/empty", use = [ "edit", "reveal" ] }, - # Fallback - { name = "*", use = [ "open", "reveal" ] }, + # Folder + { name = "*/", use = ["edit", "open", "reveal"] }, + # Text + { mime = "text/*", use = ["edit", "reveal"] }, + # Image + { mime = "image/*", use = ["open", "reveal"] }, + # Media + { mime = "{audio,video}/*", use = ["play", "reveal"] }, + # Archive + { mime = "application/{,g}zip", use = ["extract", "reveal"] }, + { mime = "application/{tar,bzip*,7z*,xz,rar}", use = ["extract", "reveal"] }, + # JSON + { mime = "application/{json,ndjson}", use = ["edit", "reveal"] }, + { mime = "*/javascript", use = ["edit", "reveal"] }, + # Empty file + { mime = "inode/empty", use = ["edit", "reveal"] }, + # Fallback + { name = "*", use = ["open", "reveal"] }, ] [tasks] -micro_workers = 10 -macro_workers = 25 -bizarre_retry = 5 -image_alloc = 536870912 # 512MB -image_bound = [ 0, 0 ] +micro_workers = 10 +macro_workers = 25 +bizarre_retry = 5 +image_alloc = 536870912 # 512MB +image_bound = [0, 0] suppress_preload = false [plugin] fetchers = [ - # Mimetype - { id = "mime", name = "*", run = "mime", if = "!mime", prio = "high" }, + # Mimetype + { id = "mime", name = "*", run = "mime", if = "!mime", prio = "high" }, ] spotters = [ - { name = "*/", run = "folder" }, - # Code - { mime = "text/*", run = "code" }, - { mime = "*/{xml,javascript,wine-extension-ini}", run = "code" }, - # Image - { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, - { mime = "image/*", run = "image" }, - # Video - { mime = "video/*", run = "video" }, - # Fallback - { name = "*", run = "file" }, + { name = "*/", run = "folder" }, + # Code + { mime = "text/*", run = "code" }, + { mime = "*/{xml,javascript,wine-extension-ini}", run = "code" }, + # Image + { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, + { mime = "image/*", run = "image" }, + # Video + { mime = "video/*", run = "video" }, + # Fallback + { name = "*", run = "file" }, ] preloaders = [ - # Image - { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, - { mime = "image/*", run = "image" }, - # Video - { mime = "video/*", run = "video" }, - # PDF - { mime = "application/pdf", run = "pdf" }, - # Font - { mime = "font/*", run = "font" }, - { mime = "application/ms-opentype", run = "font" }, + # Image + { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, + { mime = "image/*", run = "image" }, + # Video + { mime = "video/*", run = "video" }, + # PDF + { mime = "application/pdf", run = "pdf" }, + # Font + { mime = "font/*", run = "font" }, + { mime = "application/ms-opentype", run = "font" }, ] previewers = [ - { name = "*/", run = "folder", sync = true }, - # Code - { mime = "text/*", run = "code" }, - { mime = "*/{xml,javascript,wine-extension-ini}", run = "code" }, - # JSON - { mime = "application/{json,ndjson}", run = "json" }, - # Image - { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, - { mime = "image/*", run = "image" }, - # Video - { mime = "video/*", run = "video" }, - # PDF - { mime = "application/pdf", run = "pdf" }, - # Archive - { mime = "application/{,g}zip", run = "archive" }, - { mime = "application/{tar,bzip*,7z*,xz,rar,iso9660-image}", run = "archive" }, - # Font - { mime = "font/*", run = "font" }, - { mime = "application/ms-opentype", run = "font" }, - # Empty file - { mime = "inode/empty", run = "empty" }, - # Fallback - { name = "*", run = "file" }, + { name = "*/", run = "folder", sync = true }, + # Code + { mime = "text/*", run = "code" }, + { mime = "*/{xml,javascript,wine-extension-ini}", run = "code" }, + # JSON + { mime = "application/{json,ndjson}", run = "json" }, + # Image + { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, + { mime = "image/*", run = "image" }, + # Video + { mime = "video/*", run = "video" }, + # PDF + { mime = "application/pdf", run = "pdf" }, + # Archive + { mime = "application/{,g}zip", run = "archive" }, + { mime = "application/{tar,bzip*,7z*,xz,rar,iso9660-image}", run = "archive" }, + # Font + { mime = "font/*", run = "font" }, + { mime = "application/ms-opentype", run = "font" }, + # Empty file + { mime = "inode/empty", run = "empty" }, + # Fallback + { name = "*", run = "file" }, ] [input] cursor_blink = false # cd -cd_title = "Change directory:" +cd_title = "Change directory:" cd_origin = "top-center" -cd_offset = [ 0, 2, 50, 3 ] +cd_offset = [0, 2, 50, 3] # create -create_title = [ "Create:", "Create (dir):" ] +create_title = ["Create:", "Create (dir):"] create_origin = "top-center" -create_offset = [ 0, 2, 50, 3 ] +create_offset = [0, 2, 50, 3] # rename -rename_title = "Rename:" +rename_title = "Rename:" rename_origin = "hovered" -rename_offset = [ 0, 1, 50, 3 ] +rename_offset = [0, 1, 50, 3] # filter -filter_title = "Filter:" +filter_title = "Filter:" filter_origin = "top-center" -filter_offset = [ 0, 2, 50, 3 ] +filter_offset = [0, 2, 50, 3] # find -find_title = [ "Find next:", "Find previous:" ] +find_title = ["Find next:", "Find previous:"] find_origin = "top-center" -find_offset = [ 0, 2, 50, 3 ] +find_offset = [0, 2, 50, 3] # search -search_title = "Search via {n}:" +search_title = "Search via {n}:" search_origin = "top-center" -search_offset = [ 0, 2, 50, 3 ] +search_offset = [0, 2, 50, 3] # shell -shell_title = [ "Shell:", "Shell (block):" ] +shell_title = ["Shell:", "Shell (block):"] shell_origin = "top-center" -shell_offset = [ 0, 2, 50, 3 ] +shell_offset = [0, 2, 50, 3] [confirm] # trash -trash_title = "Trash {n} selected file{s}?" -trash_origin = "center" -trash_offset = [ 0, 0, 70, 20 ] +trash_title = "Trash {n} selected file{s}?" +trash_origin = "center" +trash_offset = [0, 0, 70, 20] # delete -delete_title = "Permanently delete {n} selected file{s}?" -delete_origin = "center" -delete_offset = [ 0, 0, 70, 20 ] +delete_title = "Permanently delete {n} selected file{s}?" +delete_origin = "center" +delete_offset = [0, 0, 70, 20] # overwrite -overwrite_title = "Overwrite file?" +overwrite_title = "Overwrite file?" overwrite_content = "Will overwrite the following file:" -overwrite_origin = "center" -overwrite_offset = [ 0, 0, 50, 15 ] +overwrite_origin = "center" +overwrite_offset = [0, 0, 50, 15] # quit -quit_title = "Quit?" +quit_title = "Quit?" quit_content = "The following task is still running, are you sure you want to quit?" -quit_origin = "center" -quit_offset = [ 0, 0, 50, 15 ] +quit_origin = "center" +quit_offset = [0, 0, 50, 15] [pick] -open_title = "Open with:" +open_title = "Open with:" open_origin = "hovered" -open_offset = [ 0, 1, 50, 7 ] +open_offset = [0, 1, 50, 7] [which] -sort_by = "none" +sort_by = "none" sort_sensitive = false -sort_reverse = false -sort_translit = false +sort_reverse = false +sort_translit = false diff --git a/yazi-config/src/preview/alignment.rs b/yazi-config/src/preview/alignment.rs new file mode 100644 index 000000000..6d5d571d8 --- /dev/null +++ b/yazi-config/src/preview/alignment.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum HorizontalAlignment { + Left, + #[default] + Center, + Right, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum VerticalAlignment { + #[default] + Top, + Center, + Bottom, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Alignment { + #[serde(default)] + pub horizontal: HorizontalAlignment, + #[serde(default)] + pub vertical: VerticalAlignment, +} diff --git a/yazi-config/src/preview/mod.rs b/yazi-config/src/preview/mod.rs index 71bc68abb..8c54d01ac 100644 --- a/yazi-config/src/preview/mod.rs +++ b/yazi-config/src/preview/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(preview wrap); +yazi_macro::mod_flat!(alignment preview wrap); diff --git a/yazi-config/src/preview/preview.rs b/yazi-config/src/preview/preview.rs index a127dddda..19dd927d8 100644 --- a/yazi-config/src/preview/preview.rs +++ b/yazi-config/src/preview/preview.rs @@ -6,7 +6,7 @@ use validator::Validate; use yazi_fs::{Xdg, expand_path}; use yazi_shared::timestamp_us; -use super::PreviewWrap; +use super::{Alignment, PreviewWrap}; #[rustfmt::skip] const TABS: &[&str] = &["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]; @@ -17,6 +17,7 @@ pub struct Preview { pub tab_size: u8, pub max_width: u32, pub max_height: u32, + pub alignment: Alignment, pub cache_dir: PathBuf, @@ -72,6 +73,8 @@ impl<'de> Deserialize<'de> for Preview { tab_size: u8, max_width: u32, max_height: u32, + #[serde(default)] + alignment: Alignment, cache_dir: Option, @@ -95,6 +98,7 @@ impl<'de> Deserialize<'de> for Preview { tab_size: preview.tab_size, max_width: preview.max_width, max_height: preview.max_height, + alignment: preview.alignment, cache_dir: preview .cache_dir diff --git a/yazi-plugin/src/utils/image.rs b/yazi-plugin/src/utils/image.rs index 4b80dc59d..c0b43c1d4 100644 --- a/yazi-plugin/src/utils/image.rs +++ b/yazi-plugin/src/utils/image.rs @@ -1,5 +1,5 @@ -use mlua::{Function, IntoLua, Lua, Value}; -use yazi_adapter::{ADAPTOR, Image}; +use mlua::{Function, IntoLua, Lua, Table, Value}; +use yazi_adapter::{ADAPTOR, Image, Offset}; use super::Utils; use crate::{bindings::ImageInfo, elements::Rect, url::UrlRef}; @@ -16,13 +16,19 @@ impl Utils { } pub(super) fn image_show(lua: &Lua) -> mlua::Result { - lua.create_async_function(|lua, (url, rect): (UrlRef, Rect)| async move { - if let Ok(area) = ADAPTOR.image_show(&url, *rect).await { - Rect::from(area).into_lua(&lua) - } else { - Value::Nil.into_lua(&lua) - } - }) + lua.create_async_function( + |lua, (url, rect, offset_table): (UrlRef, Rect, Option)| async move { + let offset = offset_table.map(|lua_offset| Offset { + x: lua_offset.get("x").unwrap_or(0), + y: lua_offset.get("y").unwrap_or(0), + }); + if let Ok(area) = ADAPTOR.image_show(&url, *rect, offset).await { + Rect::from(area).into_lua(&lua) + } else { + Value::Nil.into_lua(&lua) + } + }, + ) } pub(super) fn image_precache(lua: &Lua) -> mlua::Result {