feat: advanced app overall style (#4023)

This commit is contained in:
三咲雅 misaki masa 2026-06-07 21:46:07 +08:00 committed by GitHub
parent ea7fa2127c
commit f6113d3865
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 158 additions and 70 deletions

View file

@ -4,8 +4,9 @@ use ansi_to_tui::IntoText;
use anyhow::{Result, anyhow, bail};
use ratatui::layout::Rect;
use tokio::process::Command;
use yazi_config::THEME;
use yazi_emulator::Emulator;
use yazi_term::sequence::MoveTo;
use yazi_term::sequence::{MoveTo, ResetAttrs, SetBg};
use crate::Adapter;
@ -71,11 +72,14 @@ impl Chafa {
pub(crate) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |w| {
if let Some(c) = THEME.app.overall.get().bg {
write!(w, "{}", SetBg(c))?;
}
for y in area.top()..area.bottom() {
write!(w, "{}", MoveTo(area.x, y))?;
write!(w, "{s}")?;
}
Ok(())
Ok(write!(w, "{ResetAttrs}")?)
})
}
}

View file

@ -4,9 +4,9 @@ use anyhow::Result;
use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}};
use ratatui::layout::Rect;
use yazi_config::YAZI;
use yazi_config::{THEME, YAZI};
use yazi_emulator::{CLOSE, Emulator, START};
use yazi_term::sequence::MoveTo;
use yazi_term::sequence::{MoveTo, ResetAttrs, SetBg};
use crate::{Image, adapter::Adapter};
@ -29,11 +29,14 @@ impl Iip {
pub(crate) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |w| {
if let Some(c) = THEME.app.overall.get().bg {
write!(w, "{}", SetBg(c))?;
}
for y in area.top()..area.bottom() {
write!(w, "{}", MoveTo(area.x, y))?;
write!(w, "{s}")?;
}
Ok(())
Ok(write!(w, "{ResetAttrs}")?)
})
}

View file

@ -5,9 +5,10 @@ use anyhow::Result;
use base64::{Engine, engine::general_purpose};
use image::DynamicImage;
use ratatui::{layout::Rect, style::Color};
use yazi_config::THEME;
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
use yazi_shim::cell::SyncCell;
use yazi_term::sequence::{MoveTo, SetFg};
use yazi_term::sequence::{MoveTo, ResetAttrs, SetBg, SetFg};
use crate::{adapter::Adapter, image::Image};
@ -333,12 +334,16 @@ impl Kgp {
pub(crate) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |w| {
if let Some(c) = THEME.app.overall.get().bg {
write!(w, "{}", SetBg(c))?;
}
for y in area.top()..area.bottom() {
write!(w, "{}", MoveTo(area.x, y))?;
write!(w, "{s}")?;
}
write!(w, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?;
write!(w, "{ResetAttrs}{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?;
Ok(())
})
}
@ -381,12 +386,16 @@ impl Kgp {
}
fn place(area: &Rect) -> Result<Vec<u8>> {
let mut buf = Vec::with_capacity(area.width as usize * area.height as usize * 3 + 50);
let mut buf = Vec::with_capacity(area.width as usize * area.height as usize * 3 + 500);
let id = Self::image_id();
let (r, g, b) = ((id >> 16) & 0xff, (id >> 8) & 0xff, id & 0xff);
write!(buf, "{}", SetFg(Color::Rgb(r as u8, g as u8, b as u8)))?;
if let Some(c) = THEME.app.overall.get().bg {
write!(buf, "{}", SetBg(c))?;
}
for y in 0..area.height {
write!(buf, "{}", MoveTo(area.x, area.y + y))?;
for x in 0..area.width {
@ -395,6 +404,8 @@ impl Kgp {
write!(buf, "{}", *DIACRITICS.get(x as usize).unwrap_or(&DIACRITICS[0]))?;
}
}
write!(buf, "{ResetAttrs}")?;
Ok(buf)
}

View file

@ -5,8 +5,9 @@ use image::{DynamicImage, GenericImageView, RgbImage};
use palette::{Srgb, cast::ComponentsAs};
use quantette::{PaletteSize, color_map::IndexedColorMap, wu::{BinnerU8x3, WuU8x3}};
use ratatui::layout::Rect;
use yazi_config::THEME;
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
use yazi_term::sequence::MoveTo;
use yazi_term::sequence::{MoveTo, ResetAttrs, SetBg};
use crate::{Image, adapter::Adapter};
@ -34,11 +35,14 @@ impl Sixel {
pub(crate) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |w| {
if let Some(c) = THEME.app.overall.get().bg {
write!(w, "{}", SetBg(c))?;
}
for y in area.top()..area.bottom() {
write!(w, "{}", MoveTo(area.x, y))?;
write!(w, "{s}")?;
}
Ok(())
Ok(write!(w, "{ResetAttrs}")?)
})
}

View file

@ -14,6 +14,7 @@ pub fn compose(p_get: ComposerGet, p_set: ComposerSet) -> Composer<ComposerGet,
b"Color" => super::Color::compose(lua)?,
b"Constraint" => super::Constraint::compose(lua)?,
b"Edge" => super::Edge::compose(lua)?,
b"Fill" => super::Fill::compose(lua)?,
b"Gauge" => super::Gauge::compose(lua)?,
b"Layout" => super::Layout::compose(lua)?,
b"Line" => super::Line::compose(lua)?,

View file

@ -0,0 +1,53 @@
use mlua::{IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use ratatui::widgets::Widget;
use super::Area;
#[derive(Clone, Copy, Debug, Default)]
pub struct Fill {
pub area: Area,
style: ratatui::style::Style,
}
impl Fill {
pub fn compose(lua: &Lua) -> mlua::Result<Value> {
let new =
lua.create_function(|_, (_, area): (Table, Area)| Ok(Self { area, ..Default::default() }))?;
let fill = lua.create_table()?;
fill.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;
fill.into_lua(lua)
}
}
impl Widget for Fill {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
(&self).render(rect, buf);
}
}
impl Widget for &Fill {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
if self.style == Default::default() {
return;
}
for pos in rect.positions() {
buf[pos].set_style(self.style);
}
}
}
impl UserData for Fill {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_style_method!(methods, style);
}
}

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, mem, ops::{Deref, DerefMut}};
use ansi_to_tui::IntoText;
use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, Function, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use ratatui::widgets::Widget;
use unicode_width::UnicodeWidthChar;
@ -140,6 +140,13 @@ impl UserData for Line {
methods.add_method("visible", |_, me, ()| {
Ok(me.iter().flat_map(|s| s.content.chars()).any(|c| c.width().unwrap_or(0) > 0))
});
methods.add_function("map", |_, (ud, f): (AnyUserData, Function)| {
let mut me = ud.borrow_mut::<Self>()?;
for span in &mut me.spans {
*span = f.call::<Span>(Span(mem::take(span)))?.0;
}
Ok(ud)
});
methods.add_function("truncate", |lua, (ud, t): (AnyUserData, Table)| {
let mut me = ud.borrow_mut::<Self>()?;

View file

@ -1 +1 @@
yazi_macro::mod_flat!(align area bar border cell clear color constraint edge elements gauge layout line list pad pos rect renderable row span table text wrap);
yazi_macro::mod_flat!(align area bar border cell clear color constraint edge elements fill gauge layout line list pad pos rect renderable row span table text wrap);

View file

@ -3,8 +3,8 @@ use std::any::TypeId;
use mlua::{AnyUserData, ExternalError, FromLua, Lua, Value};
use ratatui::widgets::Widget;
use super::{Bar, Border, Clear, Gauge, Line, List, Table, Text};
use crate::{Error, elements::Area};
use super::{Area, Bar, Border, Clear, Fill, Gauge, Line, List, Table, Text};
use crate::Error;
#[derive(Clone, Debug)]
pub enum Renderable {
@ -13,6 +13,7 @@ pub enum Renderable {
List(Box<List>),
Bar(Bar),
Clear(Clear),
Fill(Fill),
Border(Border),
Gauge(Box<Gauge>),
Table(Box<Table>),
@ -26,6 +27,7 @@ impl Renderable {
Self::List(list) => list.area,
Self::Bar(bar) => bar.area,
Self::Clear(clear) => clear.area,
Self::Fill(fill) => fill.area,
Self::Border(border) => border.area,
Self::Gauge(gauge) => gauge.area,
Self::Table(table) => table.area,
@ -40,6 +42,7 @@ impl Renderable {
Self::List(list) => list.area = area,
Self::Bar(bar) => bar.area = area,
Self::Clear(clear) => clear.area = area,
Self::Fill(fill) => fill.area = area,
Self::Border(border) => border.area = area,
Self::Gauge(gauge) => gauge.area = area,
Self::Table(table) => table.area = area,
@ -66,6 +69,7 @@ impl TryFrom<&AnyUserData> for Renderable {
Some(t) if t == TypeId::of::<List>() => Self::List(Box::new(ud.take()?)),
Some(t) if t == TypeId::of::<Bar>() => Self::Bar(ud.take()?),
Some(t) if t == TypeId::of::<Clear>() => Self::Clear(ud.take()?),
Some(t) if t == TypeId::of::<Fill>() => Self::Fill(ud.take()?),
Some(t) if t == TypeId::of::<Border>() => Self::Border(ud.take()?),
Some(t) if t == TypeId::of::<Gauge>() => Self::Gauge(Box::new(ud.take()?)),
Some(t) if t == TypeId::of::<Table>() => Self::Table(Box::new(ud.take()?)),
@ -95,6 +99,7 @@ impl Widget for Renderable {
Self::List(list) => list.render(rect, buf),
Self::Bar(bar) => bar.render(rect, buf),
Self::Clear(clear) => clear.render(rect, buf),
Self::Fill(fill) => fill.render(rect, buf),
Self::Border(border) => border.render(rect, buf),
Self::Gauge(gauge) => gauge.render(rect, buf),
Self::Table(table) => table.render(rect, buf),
@ -113,6 +118,7 @@ impl Widget for &Renderable {
Renderable::List(list) => (&**list).render(rect, buf),
Renderable::Bar(bar) => bar.render(rect, buf),
Renderable::Clear(clear) => clear.render(rect, buf),
Renderable::Fill(fill) => fill.render(rect, buf),
Renderable::Border(border) => border.render(rect, buf),
Renderable::Gauge(gauge) => (&**gauge).render(rect, buf),
Renderable::Table(table) => (&**table).render(rect, buf),

View file

@ -130,14 +130,19 @@ impl Url {
}
}
Value::UserData(ref ud) => {
let url = ud.borrow::<Self>()?;
if url.scheme().covariant(self.scheme()) {
Self::new(self.try_join(url.loc()).into_lua_err()?).into_lua(lua)
if let Ok(url) = ud.borrow::<Self>() {
if url.scheme().covariant(self.scheme()) {
Self::new(self.try_join(url.loc()).into_lua_err()?).into_lua(lua)
} else {
Ok(other)
}
} else if let Ok(path) = ud.borrow::<Path>() {
Self::new(self.try_join(&*path).into_lua_err()?).into_lua(lua)
} else {
Ok(other)
Err(EXPECTED.into_lua_err())?
}
}
_ => Err("must be a string or Url".into_lua_err())?,
_ => Err(EXPECTED.into_lua_err())?,
}
}

View file

@ -246,12 +246,6 @@ impl Theme {
}
}
impl App {
pub fn bg_color(&self) -> String {
self.overall.get().bg.map(|c| c.to_string()).unwrap_or_default()
}
}
fn deserialize_syntect_theme<'de, D>(deserializer: D) -> Result<ArcSwap<PathBuf>, D::Error>
where
D: Deserializer<'de>,

View file

@ -0,0 +1,5 @@
App = {
_id = "app",
}
function App.bg() return th.app.overall:bg() or "reset" end

View file

@ -0,0 +1,13 @@
Backdrop = {
_id = "backdrop",
}
function Backdrop:new(area) return setmetatable({ _area = area }, { __index = self }) end
function Backdrop:reflow() return {} end
function Backdrop:redraw()
return {
ui.Fill(self._area):style(th.app.overall),
}
end

View file

@ -115,9 +115,9 @@ function Entity:style_rev()
local s = self:style()
local bg = s:bg(true)
if bg then
return ui.Style():fg(bg):bg("reset"):reverse(true)
return ui.Style():fg(bg):bg(App.bg()):reverse(true)
elseif s:raw().reversed then
return ui.Style():bg("reset"):reverse(true)
return ui.Style():bg(App.bg()):reverse(true)
end
end

View file

@ -25,6 +25,7 @@ end
function Root:build()
self._children = {
Backdrop:new(self._area),
Header:new(self._chunks[1], cx.active),
Tabs:new(self._chunks[2]),
Tab:new(self._chunks[3], cx.active),

View file

@ -41,7 +41,7 @@ function Status:mode()
local style = self:style()
return ui.Line {
ui.Span(th.status.sep_left.open):fg(style.main:bg()):bg("reset"),
ui.Span(th.status.sep_left.open):fg(style.main:bg()):bg(App.bg()),
ui.Span(" " .. mode .. " "):style(style.main),
ui.Span(th.status.sep_left.close):fg(style.main:bg()):bg(style.alt:bg()),
}
@ -127,7 +127,7 @@ function Status:position()
return ui.Line {
ui.Span(th.status.sep_right.open):fg(style.main:bg()):bg(style.alt:bg()),
ui.Span(string.format(" %2d/%-2d ", math.min(cursor + 1, length), length)):style(style.main),
ui.Span(th.status.sep_right.close):fg(style.main:bg()):bg("reset"),
ui.Span(th.status.sep_right.close):fg(style.main:bg()):bg(App.bg()),
}
end

View file

@ -20,7 +20,7 @@ function M.cut_uri_list(list)
local from = Url(ya.percent_decode(line:sub(8)))
if from.name then
local to = cx.active.current.cwd:join(from.name)
local to = cx.active.current.cwd:join(Path.os(from.name))
ya.async(function() ya.task("cut", { from = from, to = to }):spawn() end)
end

View file

@ -28,7 +28,7 @@ function M:peek(job)
i = i + #wrapped
for j = from, to do
lines[#lines + 1] = wrapped[j]
lines[#lines + 1] = M.normalize_bg(wrapped[j])
end
until i >= job.skip + limit
@ -42,4 +42,13 @@ end
function M:seek(job) require("code"):seek(job) end
function M.normalize_bg(line)
local bg = th.app.overall:bg()
if bg then
return line:map(function(span) return span:bg(bg) end)
else
return line
end
end
return M

View file

@ -47,6 +47,8 @@ fn stage_1(lua: &Lua) -> Result<()> {
lua.load(preset!("components/header")).set_name("header.lua").exec()?;
lua.load(preset!("components/linemode")).set_name("linemode.lua").exec()?;
lua.load(preset!("components/app")).set_name("app.lua").exec()?;
lua.load(preset!("components/backdrop")).set_name("backdrop.lua").exec()?;
lua.load(preset!("components/marker")).set_name("marker.lua").exec()?;
lua.load(preset!("components/markers")).set_name("markers.lua").exec()?;
lua.load(preset!("components/modal")).set_name("modal.lua").exec()?;

View file

@ -55,6 +55,8 @@ impl Default for Loader {
("video".to_owned(), preset!("plugins/video").into()),
("zoxide".to_owned(), preset!("plugins/zoxide").into()),
// Components
("app".to_owned(), [][..].into()),
("backdrop".to_owned(), [][..].into()),
("current".to_owned(), [][..].into()),
("entity".to_owned(), [][..].into()),
("header".to_owned(), [][..].into()),

View file

@ -1 +1 @@
yazi_macro::mod_flat!(clipboard csi_u cursor dnd erase mode query r#if restore_background set_background style sync traits);
yazi_macro::mod_flat!(clipboard csi_u cursor dnd erase mode query r#if style sync traits);

View file

@ -1,8 +0,0 @@
use std::fmt::{self, Display};
/// Restore background color to default
pub struct RestoreBackground;
impl Display for RestoreBackground {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "\x1b]111\x1b\\") }
}

View file

@ -1,10 +0,0 @@
use std::fmt::{self, Display};
/// Set background color to a RGB value or named color
pub struct SetBackground<'a>(pub &'a str);
impl Display for SetBackground<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_empty() { Ok(()) } else { write!(f, "\x1b]11;{}\x1b\\", self.0) }
}
}

View file

@ -1,12 +1,9 @@
use yazi_config::{THEME, YAZI};
use yazi_config::YAZI;
pub(super) struct RatermOption {
pub bg: String,
pub mouse: bool,
}
impl Default for RatermOption {
fn default() -> Self {
Self { bg: THEME.app.bg_color(), mouse: !YAZI.mgr.mouse_events.get().is_empty() }
}
fn default() -> Self { Self { mouse: !YAZI.mgr.mouse_events.get().is_empty() } }
}

View file

@ -6,7 +6,7 @@ use yazi_config::YAZI;
use yazi_emulator::{Emulator, Mux, TMUX};
use yazi_macro::writef;
use yazi_shim::cell::SyncCell;
use yazi_term::{TERM, event::{Event, KeyEventKind}, sequence::{DisableBracketedPaste, DisableDrag, DisableDrop, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableDrag, EnableDrop, EnableFocusChange, EnableMouseCapture, EnterAlternateScreen, If, LeaveAlternateScreen, PopKeyboardFlags, PushKeyboardFlags, RequestCursorBlink, RequestCursorStyle, RequestDA1, RequestKeyboardFlags, RestoreBackground, RestoreCursorStyle, SetBackground, SetTitle, ShowCursor}, stream::EventStream};
use yazi_term::{TERM, event::{Event, KeyEventKind}, sequence::{DisableBracketedPaste, DisableDrag, DisableDrop, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableDrag, EnableDrop, EnableFocusChange, EnableMouseCapture, EnterAlternateScreen, If, LeaveAlternateScreen, PopKeyboardFlags, PushKeyboardFlags, RequestCursorBlink, RequestCursorStyle, RequestDA1, RequestKeyboardFlags, RestoreCursorStyle, SetTitle, ShowCursor}, stream::EventStream};
use yazi_tty::{TTY, TtyWriter};
use crate::{RatermBackend, RatermOption, RatermState};
@ -42,10 +42,9 @@ impl Raterm {
let opt = RatermOption::default();
writef!(
TTY.writer(),
"{}{RequestCursorStyle}{RequestCursorBlink}{RequestKeyboardFlags}{RequestDA1}{}{}{EnableBracketedPaste}{EnableFocusChange}{}{}{}",
"{}{RequestCursorStyle}{RequestCursorBlink}{RequestKeyboardFlags}{RequestDA1}{}{EnableBracketedPaste}{EnableFocusChange}{}{}{}",
If(!TMUX.get(), EnterAlternateScreen),
If(TMUX.get(), EnterAlternateScreen),
SetBackground(&opt.bg),
EnableDrag(""),
EnableDrop(&["text/uri-list"]),
If(opt.mouse, EnableMouseCapture),
@ -82,9 +81,8 @@ impl Raterm {
_ = writef!(
TTY.writer(),
"{}{DisableDrop}{DisableDrag}{}{}{}{}{DisableFocusChange}{DisableBracketedPaste}{LeaveAlternateScreen}{ShowCursor}",
"{}{DisableDrop}{DisableDrag}{}{}{}{DisableFocusChange}{DisableBracketedPaste}{LeaveAlternateScreen}{ShowCursor}",
If(state.mouse, DisableMouseCapture),
If(state.bg, RestoreBackground),
If(state.csi_u, PopKeyboardFlags),
RestoreCursorStyle { shape: state.cursor_shape, blink: state.cursor_blink },
If(state.title, SetTitle("")),

View file

@ -2,7 +2,6 @@ use crate::RatermOption;
#[derive(Clone, Copy)]
pub struct RatermState {
pub bg: bool,
pub csi_u: bool,
pub mouse: bool,
pub title: bool,
@ -13,7 +12,6 @@ pub struct RatermState {
impl RatermState {
pub(super) const fn default() -> Self {
Self {
bg: false,
csi_u: false,
mouse: false,
title: false,
@ -33,13 +31,6 @@ impl RatermState {
let cursor_blink = resp.contains("\x1b[?12;1$y");
Self {
bg: !opt.bg.is_empty(),
csi_u,
mouse: opt.mouse,
title: false,
cursor_shape,
cursor_blink,
}
Self { csi_u, mouse: opt.mouse, title: false, cursor_shape, cursor_blink }
}
}