Skip to content

Commit 2fad7d8

Browse files
committed
feat: support customizable alignment for image display
1 parent fd8871d commit 2fad7d8

File tree

15 files changed

+126
-55
lines changed

15 files changed

+126
-55
lines changed

yazi-adapter/src/adapter.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use tracing::warn;
66
use yazi_shared::env_exists;
77

88
use super::{Iip, Kgp, KgpOld};
9-
use crate::{Chafa, Emulator, SHOWN, Sixel, TMUX, Ueberzug, WSL};
9+
use crate::{Chafa, Emulator, Offset, SHOWN, Sixel, TMUX, Ueberzug, WSL};
1010

1111
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1212
pub enum Adapter {
@@ -36,18 +36,18 @@ impl Display for Adapter {
3636
}
3737

3838
impl Adapter {
39-
pub async fn image_show(self, path: &Path, max: Rect) -> Result<Rect> {
39+
pub async fn image_show(self, path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
4040
if max.is_empty() {
4141
return Ok(Rect::default());
4242
}
4343

4444
match self {
45-
Self::Kgp => Kgp::image_show(path, max).await,
46-
Self::KgpOld => KgpOld::image_show(path, max).await,
47-
Self::Iip => Iip::image_show(path, max).await,
48-
Self::Sixel => Sixel::image_show(path, max).await,
49-
Self::X11 | Self::Wayland => Ueberzug::image_show(path, max).await,
50-
Self::Chafa => Chafa::image_show(path, max).await,
45+
Self::Kgp => Kgp::image_show(path, max, offset).await,
46+
Self::KgpOld => KgpOld::image_show(path, max, offset).await,
47+
Self::Iip => Iip::image_show(path, max, offset).await,
48+
Self::Sixel => Sixel::image_show(path, max, offset).await,
49+
Self::X11 | Self::Wayland => Ueberzug::image_show(path, max, offset).await,
50+
Self::Chafa => Chafa::image_show(path, max, offset).await,
5151
}
5252
}
5353

yazi-adapter/src/chafa.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ use std::{io::Write, path::Path, process::Stdio};
33
use ansi_to_tui::IntoText;
44
use anyhow::{Result, bail};
55
use crossterm::{cursor::MoveTo, queue};
6-
use ratatui::layout::Rect;
6+
use ratatui::layout::{Rect, Size};
77
use tokio::process::Command;
88

9-
use crate::{Adapter, Emulator};
9+
use crate::{Adapter, Emulator, Offset};
1010

1111
pub(super) struct Chafa;
1212

1313
impl Chafa {
14-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
14+
pub(super) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
1515
let output = Command::new("chafa")
1616
.args([
1717
"-f",
@@ -46,11 +46,13 @@ impl Chafa {
4646
bail!("failed to parse chafa output");
4747
};
4848

49-
let area = Rect {
50-
x: max.x,
51-
y: max.y,
52-
width: first.width() as u16,
53-
height: lines.len() as u16,
49+
let area = {
50+
let width = first.width() as u16;
51+
let height = lines.len() as u16;
52+
let offset = offset.unwrap_or_else(|| {
53+
Offset::from((Size { width, height }, Size { width: max.width, height: max.height }))
54+
});
55+
Rect { x: max.x + offset.x, y: max.y + offset.y, width, height }
5456
};
5557

5658
Adapter::Chafa.image_hide()?;

yazi-adapter/src/iip.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ use ratatui::layout::Rect;
88
use yazi_config::PREVIEW;
99

1010
use super::image::Image;
11-
use crate::{CLOSE, Emulator, START, adapter::Adapter};
11+
use crate::{CLOSE, Emulator, Offset, START, adapter::Adapter};
1212

1313
pub(super) struct Iip;
1414

1515
impl Iip {
16-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
16+
pub(super) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
1717
let img = Image::downscale(path, max).await?;
18-
let area = Image::pixel_area((img.width(), img.height()), max);
18+
let area = Image::pixel_area((img.width(), img.height()), max, offset);
1919
let b = Self::encode(img).await?;
2020

2121
Adapter::Iip.image_hide()?;

yazi-adapter/src/image.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use std::path::{Path, PathBuf};
22

33
use anyhow::Result;
44
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
5-
use ratatui::layout::Rect;
5+
use ratatui::layout::{Rect, Size};
66
use yazi_config::{PREVIEW, TASKS};
77

8-
use crate::Dimension;
8+
use crate::{Dimension, Offset};
99

1010
pub struct Image;
1111

@@ -73,13 +73,15 @@ impl Image {
7373
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height))
7474
}
7575

76-
pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect {
76+
pub(super) fn pixel_area(size: (u32, u32), rect: Rect, offset: Option<Offset>) -> Rect {
7777
Dimension::ratio()
78-
.map(|(r1, r2)| Rect {
79-
x: rect.x,
80-
y: rect.y,
81-
width: (size.0 as f64 / r1).ceil() as u16,
82-
height: (size.1 as f64 / r2).ceil() as u16,
78+
.map(|(r1, r2)| {
79+
let width = (size.0 as f64 / r1).ceil() as u16;
80+
let height = (size.1 as f64 / r2).ceil() as u16;
81+
let offset = offset.unwrap_or_else(|| {
82+
Offset::from((Size { width, height }, Size { width: rect.width, height: rect.height }))
83+
});
84+
Rect { x: rect.x + offset.x, y: rect.y + offset.y, width, height }
8385
})
8486
.unwrap_or(rect)
8587
}

yazi-adapter/src/kgp.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use image::DynamicImage;
88
use ratatui::layout::Rect;
99

1010
use super::image::Image;
11-
use crate::{CLOSE, ESCAPE, Emulator, START, adapter::Adapter};
11+
use crate::{CLOSE, ESCAPE, Emulator, Offset, START, adapter::Adapter};
1212

1313
static DIACRITICS: [char; 297] = [
1414
'\u{0305}',
@@ -313,9 +313,9 @@ static DIACRITICS: [char; 297] = [
313313
pub(super) struct Kgp;
314314

315315
impl Kgp {
316-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
316+
pub(super) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
317317
let img = Image::downscale(path, max).await?;
318-
let area = Image::pixel_area((img.width(), img.height()), max);
318+
let area = Image::pixel_area((img.width(), img.height()), max, offset);
319319

320320
let b1 = Self::encode(img).await?;
321321
let b2 = Self::place(&area)?;

yazi-adapter/src/kgp_old.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ use image::DynamicImage;
77
use ratatui::layout::Rect;
88

99
use super::image::Image;
10-
use crate::{CLOSE, ESCAPE, Emulator, START, adapter::Adapter};
10+
use crate::{CLOSE, ESCAPE, Emulator, Offset, START, adapter::Adapter};
1111

1212
pub(super) struct KgpOld;
1313

1414
impl KgpOld {
15-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
15+
pub(super) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
1616
let img = Image::downscale(path, max).await?;
17-
let area = Image::pixel_area((img.width(), img.height()), max);
17+
let area = Image::pixel_area((img.width(), img.height()), max, offset);
1818
let b = Self::encode(img).await?;
1919

2020
Adapter::KgpOld.image_hide()?;

yazi-adapter/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(clippy::unit_arg)]
22

33
yazi_macro::mod_flat!(
4-
adapter chafa dimension emulator iip image kgp kgp_old mux sixel ueberzug
4+
adapter chafa dimension emulator iip image kgp kgp_old mux offset sixel ueberzug
55
);
66

77
use yazi_shared::{RoCell, SyncCell, env_exists, in_wsl};

yazi-adapter/src/offset.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use ratatui::layout::Size;
2+
use yazi_config::PREVIEW;
3+
use yazi_shared::alignment::{HorizontalAlignment, VerticalAlignment};
4+
5+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6+
pub struct Offset {
7+
pub x: u16,
8+
pub y: u16,
9+
}
10+
11+
impl From<(Size, Size)> for Offset {
12+
fn from(value: (Size, Size)) -> Self {
13+
let inner = value.0;
14+
let outer = value.1;
15+
let offset_x = match PREVIEW.alignment.horizontal {
16+
HorizontalAlignment::Left => 0,
17+
HorizontalAlignment::Center => (outer.width - inner.width) / 2,
18+
HorizontalAlignment::Right => outer.width - inner.width,
19+
};
20+
let offset_y = match PREVIEW.alignment.vertical {
21+
VerticalAlignment::Top => 0,
22+
VerticalAlignment::Center => (outer.height - inner.height) / 2,
23+
VerticalAlignment::Bottom => outer.height - inner.height,
24+
};
25+
Self { x: offset_x, y: offset_y }
26+
}
27+
}

yazi-adapter/src/sixel.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ use image::DynamicImage;
77
use ratatui::layout::Rect;
88
use yazi_config::PREVIEW;
99

10-
use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
10+
use crate::{CLOSE, ESCAPE, Emulator, Image, Offset, START, adapter::Adapter};
1111

1212
pub(super) struct Sixel;
1313

1414
impl Sixel {
15-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
15+
pub(super) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
1616
let img = Image::downscale(path, max).await?;
17-
let area = Image::pixel_area((img.width(), img.height()), max);
17+
let area = Image::pixel_area((img.width(), img.height()), max, offset);
1818
let b = Self::encode(img).await?;
1919

2020
Adapter::Sixel.image_hide()?;

yazi-adapter/src/ueberzug.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ use std::{path::{Path, PathBuf}, process::Stdio};
22

33
use anyhow::{Result, bail};
44
use imagesize::ImageSize;
5-
use ratatui::layout::Rect;
5+
use ratatui::layout::{Rect, Size};
66
use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
77
use tracing::{debug, warn};
88
use yazi_config::PREVIEW;
99
use yazi_shared::{RoCell, env_exists};
1010

11-
use crate::{Adapter, Dimension};
11+
use crate::{Adapter, Dimension, Offset};
1212

1313
#[allow(clippy::type_complexity)]
1414
static DEMON: RoCell<Option<UnboundedSender<Option<(PathBuf, Rect)>>>> = RoCell::new();
@@ -41,7 +41,7 @@ impl Ueberzug {
4141
DEMON.init(Some(tx))
4242
}
4343

44-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
44+
pub(super) async fn image_show(path: &Path, max: Rect, offset: Option<Offset>) -> Result<Rect> {
4545
let Some(tx) = &*DEMON else {
4646
bail!("uninitialized ueberzugpp");
4747
};
@@ -51,11 +51,13 @@ impl Ueberzug {
5151
tokio::task::spawn_blocking(move || imagesize::size(p)).await??;
5252

5353
let area = Dimension::ratio()
54-
.map(|(r1, r2)| Rect {
55-
x: max.x,
56-
y: max.y,
57-
width: max.width.min((w.min(PREVIEW.max_width as _) as f64 / r1).ceil() as _),
58-
height: max.height.min((h.min(PREVIEW.max_height as _) as f64 / r2).ceil() as _),
54+
.map(|(r1, r2)| {
55+
let width = max.width.min((w.min(PREVIEW.max_width as _) as f64 / r1).ceil() as _);
56+
let height = max.height.min((h.min(PREVIEW.max_height as _) as f64 / r2).ceil() as _);
57+
let offset = offset.unwrap_or_else(|| {
58+
Offset::from((Size { width, height }, Size { width: max.width, height: max.height }))
59+
});
60+
Rect { x: max.x + offset.x, y: max.y + offset.y, width, height }
5961
})
6062
.unwrap_or(max);
6163

yazi-config/preset/yazi.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ wrap = "no"
2121
tab_size = 2
2222
max_width = 600
2323
max_height = 900
24+
alignment = { horizontal = "center", vertical = "top" }
2425
cache_dir = ""
2526
image_delay = 30
2627
image_filter = "triangle"

yazi-config/src/preview/preview.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{borrow::Cow, path::PathBuf, str::FromStr, time::{SystemTime, UNIX_EPOC
33
use anyhow::Context;
44
use serde::{Deserialize, Deserializer, Serialize};
55
use validator::Validate;
6-
use yazi_shared::fs::expand_path;
6+
use yazi_shared::{alignment::Alignment, fs::expand_path};
77

88
use super::PreviewWrap;
99
use crate::Xdg;
@@ -17,6 +17,7 @@ pub struct Preview {
1717
pub tab_size: u8,
1818
pub max_width: u32,
1919
pub max_height: u32,
20+
pub alignment: Alignment,
2021

2122
pub cache_dir: PathBuf,
2223

@@ -73,6 +74,8 @@ impl<'de> Deserialize<'de> for Preview {
7374
tab_size: u8,
7475
max_width: u32,
7576
max_height: u32,
77+
#[serde(default)]
78+
alignment: Alignment,
7679

7780
cache_dir: Option<String>,
7881

@@ -96,6 +99,7 @@ impl<'de> Deserialize<'de> for Preview {
9699
tab_size: preview.tab_size,
97100
max_width: preview.max_width,
98101
max_height: preview.max_height,
102+
alignment: preview.alignment,
99103

100104
cache_dir: preview
101105
.cache_dir

yazi-plugin/src/utils/image.rs

+14-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use mlua::{IntoLua, Lua, Table, Value};
2-
use yazi_adapter::{ADAPTOR, Image};
2+
use yazi_adapter::{ADAPTOR, Image, Offset};
33

44
use super::Utils;
55
use crate::{elements::Rect, url::UrlRef};
@@ -8,13 +8,19 @@ impl Utils {
88
pub(super) fn image(lua: &Lua, ya: &Table) -> mlua::Result<()> {
99
ya.raw_set(
1010
"image_show",
11-
lua.create_async_function(|lua, (url, rect): (UrlRef, Rect)| async move {
12-
if let Ok(area) = ADAPTOR.image_show(&url, *rect).await {
13-
Rect::from(area).into_lua(&lua)
14-
} else {
15-
Value::Nil.into_lua(&lua)
16-
}
17-
})?,
11+
lua.create_async_function(
12+
|lua, (url, rect, offset_table): (UrlRef, Rect, Option<Table>)| async move {
13+
let offset = offset_table.map(|lua_offset| Offset {
14+
x: lua_offset.get("x").unwrap_or(0),
15+
y: lua_offset.get("y").unwrap_or(0),
16+
});
17+
if let Ok(area) = ADAPTOR.image_show(&url, *rect, offset).await {
18+
Rect::from(area).into_lua(&lua)
19+
} else {
20+
Value::Nil.into_lua(&lua)
21+
}
22+
},
23+
)?,
1824
)?;
1925

2026
ya.raw_set(

yazi-shared/src/alignment.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
4+
#[serde(rename_all = "snake_case")]
5+
pub enum HorizontalAlignment {
6+
Left,
7+
#[default]
8+
Center,
9+
Right,
10+
}
11+
12+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
13+
#[serde(rename_all = "snake_case")]
14+
pub enum VerticalAlignment {
15+
#[default]
16+
Top,
17+
Center,
18+
Bottom,
19+
}
20+
21+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
22+
pub struct Alignment {
23+
#[serde(default)]
24+
pub horizontal: HorizontalAlignment,
25+
#[serde(default)]
26+
pub vertical: VerticalAlignment,
27+
}

yazi-shared/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![allow(clippy::option_map_unit_fn)]
22

3-
yazi_macro::mod_pub!(errors event fs shell theme translit);
3+
yazi_macro::mod_pub!(alignment errors event fs shell theme translit);
44

55
yazi_macro::mod_flat!(chars condition debounce env id layer natsort number os rand ro_cell sync_cell terminal throttle time xdg);
66

0 commit comments

Comments
 (0)