Skip to content

Commit 76a5a0e

Browse files
committed
feat: support customizable alignment for image display
1 parent c65bdb3 commit 76a5a0e

File tree

15 files changed

+126
-56
lines changed

15 files changed

+126
-56
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

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

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
type Cmd = Option<(PathBuf, Rect)>;
1414

@@ -42,7 +42,7 @@ impl Ueberzug {
4242
DEMON.init(Some(tx))
4343
}
4444

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

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

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/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-config/src/preview/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
yazi_macro::mod_flat!(preview wrap);
1+
yazi_macro::mod_flat!(alignment preview wrap);

yazi-config/src/preview/preview.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize};
55
use validator::Validate;
66
use yazi_shared::fs::expand_path;
77

8-
use super::PreviewWrap;
8+
use super::{Alignment, PreviewWrap};
99
use crate::Xdg;
1010

1111
#[rustfmt::skip]
@@ -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

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
use mlua::{Function, IntoLua, Lua, Value};
2-
use yazi_adapter::{ADAPTOR, Image};
1+
use mlua::{Function, IntoLua, Lua, Table, Value};
2+
use yazi_adapter::{ADAPTOR, Image, Offset};
33

44
use super::Utils;
55
use crate::{elements::Rect, url::UrlRef};
66

77
impl Utils {
88
pub(super) fn image_show(lua: &Lua) -> mlua::Result<Function> {
9-
lua.create_async_function(|lua, (url, rect): (UrlRef, Rect)| async move {
10-
if let Ok(area) = ADAPTOR.image_show(&url, *rect).await {
11-
Rect::from(area).into_lua(&lua)
12-
} else {
13-
Value::Nil.into_lua(&lua)
14-
}
15-
})
9+
lua.create_async_function(
10+
|lua, (url, rect, offset_table): (UrlRef, Rect, Option<Table>)| async move {
11+
let offset = offset_table.map(|lua_offset| Offset {
12+
x: lua_offset.get("x").unwrap_or(0),
13+
y: lua_offset.get("y").unwrap_or(0),
14+
});
15+
if let Ok(area) = ADAPTOR.image_show(&url, *rect, offset).await {
16+
Rect::from(area).into_lua(&lua)
17+
} else {
18+
Value::Nil.into_lua(&lua)
19+
}
20+
},
21+
)
1622
}
1723

1824
pub(super) fn image_precache(lua: &Lua) -> mlua::Result<Function> {

0 commit comments

Comments
 (0)