feat: support customizable alignment for image display

This commit is contained in:
gaesa 2024-12-07 15:24:33 +00:00
parent 62ac224cff
commit cf43e11ada
No known key found for this signature in database
15 changed files with 262 additions and 192 deletions

View file

@ -5,7 +5,7 @@ use ratatui::layout::Rect;
use tracing::warn; use tracing::warn;
use yazi_shared::env_exists; 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)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Adapter { pub enum Adapter {
@ -35,18 +35,18 @@ impl Display for Adapter {
} }
impl Adapter { impl Adapter {
pub async fn image_show(self, path: &Path, max: Rect) -> Result<Rect> { pub async fn image_show(self, path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
if max.is_empty() { if max.is_empty() {
return Ok(Rect::default()); return Ok(Rect::default());
} }
match self { match self {
Self::Kgp => drivers::Kgp::image_show(path, max).await, Self::Kgp => drivers::Kgp::image_show(path, max, offset).await,
Self::KgpOld => drivers::KgpOld::image_show(path, max).await, Self::KgpOld => drivers::KgpOld::image_show(path, max, offset).await,
Self::Iip => drivers::Iip::image_show(path, max).await, Self::Iip => drivers::Iip::image_show(path, max, offset).await,
Self::Sixel => drivers::Sixel::image_show(path, max).await, Self::Sixel => drivers::Sixel::image_show(path, max, offset).await,
Self::X11 | Self::Wayland => drivers::Ueberzug::image_show(path, max).await, Self::X11 | Self::Wayland => drivers::Ueberzug::image_show(path, max, offset).await,
Self::Chafa => drivers::Chafa::image_show(path, max).await, Self::Chafa => drivers::Chafa::image_show(path, max, offset).await,
} }
} }

View file

@ -3,15 +3,15 @@ use std::{io::Write, path::Path, process::Stdio};
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use crossterm::{cursor::MoveTo, queue}; use crossterm::{cursor::MoveTo, queue};
use ratatui::layout::Rect; use ratatui::layout::{Rect, Size};
use tokio::process::Command; use tokio::process::Command;
use crate::{Adapter, Emulator}; use crate::{Adapter, Emulator, Offset};
pub(crate) struct Chafa; pub(crate) struct Chafa;
impl Chafa { impl Chafa {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
let child = Command::new("chafa") let child = Command::new("chafa")
.args([ .args([
"-f", "-f",
@ -46,11 +46,13 @@ impl Chafa {
bail!("failed to parse chafa output"); bail!("failed to parse chafa output");
}; };
let area = Rect { let area = {
x: max.x, let width = first.width() as u16;
y: max.y, let height = lines.len() as u16;
width: first.width() as u16, let offset = offset.unwrap_or_else(|| {
height: lines.len() as u16, 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()?; Adapter::Chafa.image_hide()?;

View file

@ -7,14 +7,14 @@ use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEn
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::PREVIEW; 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; pub(crate) struct Iip;
impl Iip { impl Iip {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
let img = Image::downscale(path, max).await?; 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?; let b = Self::encode(img).await?;
Adapter::Iip.image_hide()?; Adapter::Iip.image_hide()?;

View file

@ -7,7 +7,7 @@ use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; 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] = [ static DIACRITICS: [char; 297] = [
'\u{0305}', '\u{0305}',
@ -312,9 +312,9 @@ static DIACRITICS: [char; 297] = [
pub(crate) struct Kgp; pub(crate) struct Kgp;
impl Kgp { impl Kgp {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
let img = Image::downscale(path, max).await?; 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 b1 = Self::encode(img).await?;
let b2 = Self::place(&area)?; let b2 = Self::place(&area)?;

View file

@ -6,14 +6,14 @@ use base64::{Engine, engine::general_purpose};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; 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; pub(crate) struct KgpOld;
impl KgpOld { impl KgpOld {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
let img = Image::downscale(path, max).await?; 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?; let b = Self::encode(img).await?;
Adapter::KgpOld.image_hide()?; Adapter::KgpOld.image_hide()?;

View file

@ -7,14 +7,14 @@ use image::DynamicImage;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::PREVIEW; 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; pub(crate) struct Sixel;
impl Sixel { impl Sixel {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
let img = Image::downscale(path, max).await?; 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?; let b = Self::encode(img).await?;
Adapter::Sixel.image_hide()?; Adapter::Sixel.image_hide()?;

View file

@ -1,13 +1,13 @@
use std::{path::{Path, PathBuf}, process::Stdio}; use std::{path::{Path, PathBuf}, process::Stdio};
use anyhow::{Result, bail}; 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 tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
use tracing::{debug, warn}; use tracing::{debug, warn};
use yazi_config::PREVIEW; use yazi_config::PREVIEW;
use yazi_shared::{RoCell, env_exists}; use yazi_shared::{RoCell, env_exists};
use crate::{Adapter, Dimension}; use crate::{Adapter, Dimension, Offset};
type Cmd = Option<(PathBuf, Rect)>; type Cmd = Option<(PathBuf, Rect)>;
@ -41,7 +41,7 @@ impl Ueberzug {
DEMON.init(Some(tx)) DEMON.init(Some(tx))
} }
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
let Some(tx) = &*DEMON else { let Some(tx) = &*DEMON else {
bail!("uninitialized ueberzugpp"); bail!("uninitialized ueberzugpp");
}; };
@ -50,11 +50,13 @@ impl Ueberzug {
let (w, h) = tokio::task::spawn_blocking(move || image::image_dimensions(p)).await??; let (w, h) = tokio::task::spawn_blocking(move || image::image_dimensions(p)).await??;
let area = Dimension::ratio() let area = Dimension::ratio()
.map(|(r1, r2)| Rect { .map(|(r1, r2)| {
x: max.x, let width = max.width.min((w.min(PREVIEW.max_width as _) as f64 / r1).ceil() as _);
y: max.y, let height = max.height.min((h.min(PREVIEW.max_height as _) as f64 / r2).ceil() as _);
width: max.width.min((w.min(PREVIEW.max_width as _) as f64 / r1).ceil() as _), let offset = offset.unwrap_or_else(|| {
height: max.height.min((h.min(PREVIEW.max_height as _) as f64 / r2).ceil() as _), 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); .unwrap_or(max);

View file

@ -2,10 +2,10 @@ use std::path::{Path, PathBuf};
use anyhow::Result; use anyhow::Result;
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation}; 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 yazi_config::{PREVIEW, TASKS};
use crate::Dimension; use crate::{Dimension, Offset};
pub struct Image; pub struct Image;
@ -73,13 +73,15 @@ impl Image {
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height)) .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<Offset>) -> Rect {
Dimension::ratio() Dimension::ratio()
.map(|(r1, r2)| Rect { .map(|(r1, r2)| {
x: rect.x, let width = (size.0 as f64 / r1).ceil() as u16;
y: rect.y, let height = (size.1 as f64 / r2).ceil() as u16;
width: (size.0 as f64 / r1).ceil() as u16, let offset = offset.unwrap_or_else(|| {
height: (size.1 as f64 / r2).ceil() as u16, 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) .unwrap_or(rect)
} }

View file

@ -2,7 +2,7 @@
yazi_macro::mod_pub!(drivers); 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}; use yazi_shared::{RoCell, SyncCell, env_exists, in_wsl};

View file

@ -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 }
}
}

View file

@ -3,214 +3,215 @@
"$schema" = "https://yazi-rs.github.io/schemas/yazi.json" "$schema" = "https://yazi-rs.github.io/schemas/yazi.json"
[manager] [manager]
ratio = [ 1, 4, 3 ] ratio = [1, 4, 3]
sort_by = "alphabetical" sort_by = "alphabetical"
sort_sensitive = false sort_sensitive = false
sort_reverse = false sort_reverse = false
sort_dir_first = true sort_dir_first = true
sort_translit = false sort_translit = false
linemode = "none" linemode = "none"
show_hidden = false show_hidden = false
show_symlink = true show_symlink = true
scrolloff = 5 scrolloff = 5
mouse_events = [ "click", "scroll" ] mouse_events = ["click", "scroll"]
title_format = "Yazi: {cwd}" title_format = "Yazi: {cwd}"
[preview] [preview]
wrap = "no" wrap = "no"
tab_size = 2 tab_size = 2
max_width = 600 max_width = 600
max_height = 900 max_height = 900
cache_dir = "" alignment = { horizontal = "center", vertical = "top" }
image_delay = 30 cache_dir = ""
image_filter = "triangle" image_delay = 30
image_quality = 75 image_filter = "triangle"
sixel_fraction = 15 image_quality = 75
ueberzug_scale = 1 sixel_fraction = 15
ueberzug_offset = [ 0, 0, 0, 0 ] ueberzug_scale = 1
ueberzug_offset = [0, 0, 0, 0]
[opener] [opener]
edit = [ edit = [
{ run = '${EDITOR:-vi} "$@"', desc = "$EDITOR", block = true, for = "unix" }, { run = '${EDITOR:-vi} "$@"', desc = "$EDITOR", block = true, for = "unix" },
{ run = 'code %*', orphan = true, desc = "code", for = "windows" }, { run = 'code %*', orphan = true, desc = "code", for = "windows" },
{ run = 'code -w %*', block = true, desc = "code (block)", for = "windows" }, { run = 'code -w %*', block = true, desc = "code (block)", for = "windows" },
] ]
open = [ open = [
{ run = 'xdg-open "$1"', desc = "Open", for = "linux" }, { run = 'xdg-open "$1"', desc = "Open", for = "linux" },
{ run = 'open "$@"', desc = "Open", for = "macos" }, { run = 'open "$@"', desc = "Open", for = "macos" },
{ run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, { run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" },
] ]
reveal = [ reveal = [
{ run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" },
{ run = 'open -R "$1"', desc = "Reveal", for = "macos" }, { run = 'open -R "$1"', desc = "Reveal", for = "macos" },
{ run = 'explorer /select,"%1"', orphan = true, desc = "Reveal", for = "windows" }, { 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 = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" },
] ]
extract = [ extract = [
{ run = 'ya pub extract --list "$@"', desc = "Extract here", for = "unix" }, { 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 = "windows" },
] ]
play = [ play = [
{ run = 'mpv --force-window "$@"', orphan = true, for = "unix" }, { run = 'mpv --force-window "$@"', orphan = true, for = "unix" },
{ run = 'mpv --force-window %*', orphan = true, for = "windows" }, { 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 = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" },
] ]
[open] [open]
rules = [ rules = [
# Folder # Folder
{ name = "*/", use = [ "edit", "open", "reveal" ] }, { name = "*/", use = ["edit", "open", "reveal"] },
# Text # Text
{ mime = "text/*", use = [ "edit", "reveal" ] }, { mime = "text/*", use = ["edit", "reveal"] },
# Image # Image
{ mime = "image/*", use = [ "open", "reveal" ] }, { mime = "image/*", use = ["open", "reveal"] },
# Media # Media
{ mime = "{audio,video}/*", use = [ "play", "reveal" ] }, { mime = "{audio,video}/*", use = ["play", "reveal"] },
# Archive # Archive
{ mime = "application/{,g}zip", use = [ "extract", "reveal" ] }, { mime = "application/{,g}zip", use = ["extract", "reveal"] },
{ mime = "application/{tar,bzip*,7z*,xz,rar}", use = [ "extract", "reveal" ] }, { mime = "application/{tar,bzip*,7z*,xz,rar}", use = ["extract", "reveal"] },
# JSON # JSON
{ mime = "application/{json,ndjson}", use = [ "edit", "reveal" ] }, { mime = "application/{json,ndjson}", use = ["edit", "reveal"] },
{ mime = "*/javascript", use = [ "edit", "reveal" ] }, { mime = "*/javascript", use = ["edit", "reveal"] },
# Empty file # Empty file
{ mime = "inode/empty", use = [ "edit", "reveal" ] }, { mime = "inode/empty", use = ["edit", "reveal"] },
# Fallback # Fallback
{ name = "*", use = [ "open", "reveal" ] }, { name = "*", use = ["open", "reveal"] },
] ]
[tasks] [tasks]
micro_workers = 10 micro_workers = 10
macro_workers = 25 macro_workers = 25
bizarre_retry = 5 bizarre_retry = 5
image_alloc = 536870912 # 512MB image_alloc = 536870912 # 512MB
image_bound = [ 0, 0 ] image_bound = [0, 0]
suppress_preload = false suppress_preload = false
[plugin] [plugin]
fetchers = [ fetchers = [
# Mimetype # Mimetype
{ id = "mime", name = "*", run = "mime", if = "!mime", prio = "high" }, { id = "mime", name = "*", run = "mime", if = "!mime", prio = "high" },
] ]
spotters = [ spotters = [
{ name = "*/", run = "folder" }, { name = "*/", run = "folder" },
# Code # Code
{ mime = "text/*", run = "code" }, { mime = "text/*", run = "code" },
{ mime = "*/{xml,javascript,wine-extension-ini}", run = "code" }, { mime = "*/{xml,javascript,wine-extension-ini}", run = "code" },
# Image # Image
{ mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
{ mime = "image/*", run = "image" }, { mime = "image/*", run = "image" },
# Video # Video
{ mime = "video/*", run = "video" }, { mime = "video/*", run = "video" },
# Fallback # Fallback
{ name = "*", run = "file" }, { name = "*", run = "file" },
] ]
preloaders = [ preloaders = [
# Image # Image
{ mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
{ mime = "image/*", run = "image" }, { mime = "image/*", run = "image" },
# Video # Video
{ mime = "video/*", run = "video" }, { mime = "video/*", run = "video" },
# PDF # PDF
{ mime = "application/pdf", run = "pdf" }, { mime = "application/pdf", run = "pdf" },
# Font # Font
{ mime = "font/*", run = "font" }, { mime = "font/*", run = "font" },
{ mime = "application/ms-opentype", run = "font" }, { mime = "application/ms-opentype", run = "font" },
] ]
previewers = [ previewers = [
{ name = "*/", run = "folder", sync = true }, { name = "*/", run = "folder", sync = true },
# Code # Code
{ mime = "text/*", run = "code" }, { mime = "text/*", run = "code" },
{ mime = "*/{xml,javascript,wine-extension-ini}", run = "code" }, { mime = "*/{xml,javascript,wine-extension-ini}", run = "code" },
# JSON # JSON
{ mime = "application/{json,ndjson}", run = "json" }, { mime = "application/{json,ndjson}", run = "json" },
# Image # Image
{ mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
{ mime = "image/*", run = "image" }, { mime = "image/*", run = "image" },
# Video # Video
{ mime = "video/*", run = "video" }, { mime = "video/*", run = "video" },
# PDF # PDF
{ mime = "application/pdf", run = "pdf" }, { mime = "application/pdf", run = "pdf" },
# Archive # Archive
{ mime = "application/{,g}zip", run = "archive" }, { mime = "application/{,g}zip", run = "archive" },
{ mime = "application/{tar,bzip*,7z*,xz,rar,iso9660-image}", run = "archive" }, { mime = "application/{tar,bzip*,7z*,xz,rar,iso9660-image}", run = "archive" },
# Font # Font
{ mime = "font/*", run = "font" }, { mime = "font/*", run = "font" },
{ mime = "application/ms-opentype", run = "font" }, { mime = "application/ms-opentype", run = "font" },
# Empty file # Empty file
{ mime = "inode/empty", run = "empty" }, { mime = "inode/empty", run = "empty" },
# Fallback # Fallback
{ name = "*", run = "file" }, { name = "*", run = "file" },
] ]
[input] [input]
cursor_blink = false cursor_blink = false
# cd # cd
cd_title = "Change directory:" cd_title = "Change directory:"
cd_origin = "top-center" cd_origin = "top-center"
cd_offset = [ 0, 2, 50, 3 ] cd_offset = [0, 2, 50, 3]
# create # create
create_title = [ "Create:", "Create (dir):" ] create_title = ["Create:", "Create (dir):"]
create_origin = "top-center" create_origin = "top-center"
create_offset = [ 0, 2, 50, 3 ] create_offset = [0, 2, 50, 3]
# rename # rename
rename_title = "Rename:" rename_title = "Rename:"
rename_origin = "hovered" rename_origin = "hovered"
rename_offset = [ 0, 1, 50, 3 ] rename_offset = [0, 1, 50, 3]
# filter # filter
filter_title = "Filter:" filter_title = "Filter:"
filter_origin = "top-center" filter_origin = "top-center"
filter_offset = [ 0, 2, 50, 3 ] filter_offset = [0, 2, 50, 3]
# find # find
find_title = [ "Find next:", "Find previous:" ] find_title = ["Find next:", "Find previous:"]
find_origin = "top-center" find_origin = "top-center"
find_offset = [ 0, 2, 50, 3 ] find_offset = [0, 2, 50, 3]
# search # search
search_title = "Search via {n}:" search_title = "Search via {n}:"
search_origin = "top-center" search_origin = "top-center"
search_offset = [ 0, 2, 50, 3 ] search_offset = [0, 2, 50, 3]
# shell # shell
shell_title = [ "Shell:", "Shell (block):" ] shell_title = ["Shell:", "Shell (block):"]
shell_origin = "top-center" shell_origin = "top-center"
shell_offset = [ 0, 2, 50, 3 ] shell_offset = [0, 2, 50, 3]
[confirm] [confirm]
# trash # trash
trash_title = "Trash {n} selected file{s}?" trash_title = "Trash {n} selected file{s}?"
trash_origin = "center" trash_origin = "center"
trash_offset = [ 0, 0, 70, 20 ] trash_offset = [0, 0, 70, 20]
# delete # delete
delete_title = "Permanently delete {n} selected file{s}?" delete_title = "Permanently delete {n} selected file{s}?"
delete_origin = "center" delete_origin = "center"
delete_offset = [ 0, 0, 70, 20 ] delete_offset = [0, 0, 70, 20]
# overwrite # overwrite
overwrite_title = "Overwrite file?" overwrite_title = "Overwrite file?"
overwrite_content = "Will overwrite the following file:" overwrite_content = "Will overwrite the following file:"
overwrite_origin = "center" overwrite_origin = "center"
overwrite_offset = [ 0, 0, 50, 15 ] overwrite_offset = [0, 0, 50, 15]
# quit # quit
quit_title = "Quit?" quit_title = "Quit?"
quit_content = "The following task is still running, are you sure you want to quit?" quit_content = "The following task is still running, are you sure you want to quit?"
quit_origin = "center" quit_origin = "center"
quit_offset = [ 0, 0, 50, 15 ] quit_offset = [0, 0, 50, 15]
[pick] [pick]
open_title = "Open with:" open_title = "Open with:"
open_origin = "hovered" open_origin = "hovered"
open_offset = [ 0, 1, 50, 7 ] open_offset = [0, 1, 50, 7]
[which] [which]
sort_by = "none" sort_by = "none"
sort_sensitive = false sort_sensitive = false
sort_reverse = false sort_reverse = false
sort_translit = false sort_translit = false

View file

@ -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,
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(preview wrap); yazi_macro::mod_flat!(alignment preview wrap);

View file

@ -6,7 +6,7 @@ use validator::Validate;
use yazi_fs::{Xdg, expand_path}; use yazi_fs::{Xdg, expand_path};
use yazi_shared::timestamp_us; use yazi_shared::timestamp_us;
use super::PreviewWrap; use super::{Alignment, PreviewWrap};
#[rustfmt::skip] #[rustfmt::skip]
const TABS: &[&str] = &["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]; const TABS: &[&str] = &["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "];
@ -17,6 +17,7 @@ pub struct Preview {
pub tab_size: u8, pub tab_size: u8,
pub max_width: u32, pub max_width: u32,
pub max_height: u32, pub max_height: u32,
pub alignment: Alignment,
pub cache_dir: PathBuf, pub cache_dir: PathBuf,
@ -72,6 +73,8 @@ impl<'de> Deserialize<'de> for Preview {
tab_size: u8, tab_size: u8,
max_width: u32, max_width: u32,
max_height: u32, max_height: u32,
#[serde(default)]
alignment: Alignment,
cache_dir: Option<String>, cache_dir: Option<String>,
@ -95,6 +98,7 @@ impl<'de> Deserialize<'de> for Preview {
tab_size: preview.tab_size, tab_size: preview.tab_size,
max_width: preview.max_width, max_width: preview.max_width,
max_height: preview.max_height, max_height: preview.max_height,
alignment: preview.alignment,
cache_dir: preview cache_dir: preview
.cache_dir .cache_dir

View file

@ -1,5 +1,5 @@
use mlua::{Function, IntoLua, Lua, Value}; use mlua::{Function, IntoLua, Lua, Table, Value};
use yazi_adapter::{ADAPTOR, Image}; use yazi_adapter::{ADAPTOR, Image, Offset};
use super::Utils; use super::Utils;
use crate::{bindings::ImageInfo, elements::Rect, url::UrlRef}; use crate::{bindings::ImageInfo, elements::Rect, url::UrlRef};
@ -16,13 +16,19 @@ impl Utils {
} }
pub(super) fn image_show(lua: &Lua) -> mlua::Result<Function> { pub(super) fn image_show(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (url, rect): (UrlRef, Rect)| async move { lua.create_async_function(
if let Ok(area) = ADAPTOR.image_show(&url, *rect).await { |lua, (url, rect, offset_table): (UrlRef, Rect, Option<Table>)| async move {
Rect::from(area).into_lua(&lua) let offset = offset_table.map(|lua_offset| Offset {
} else { x: lua_offset.get("x").unwrap_or(0),
Value::Nil.into_lua(&lua) 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<Function> { pub(super) fn image_precache(lua: &Lua) -> mlua::Result<Function> {