diff --git a/yazi-config/preset/theme-dark.toml b/yazi-config/preset/theme-dark.toml index 56f46344..6533e6e8 100644 --- a/yazi-config/preset/theme-dark.toml +++ b/yazi-config/preset/theme-dark.toml @@ -20,6 +20,9 @@ light = "" [app] overall = {} +parent = {} +current = {} +preview = {} # : }}} diff --git a/yazi-config/preset/theme-light.toml b/yazi-config/preset/theme-light.toml index d6aef72b..a823d0c2 100644 --- a/yazi-config/preset/theme-light.toml +++ b/yazi-config/preset/theme-light.toml @@ -20,6 +20,9 @@ light = "" [app] overall = {} +parent = {} +current = {} +preview = {} # : }}} diff --git a/yazi-config/src/theme/theme.rs b/yazi-config/src/theme/theme.rs index 57eb2a46..dac2cb76 100644 --- a/yazi-config/src/theme/theme.rs +++ b/yazi-config/src/theme/theme.rs @@ -10,33 +10,39 @@ use crate::{Style, normalize_path}; #[derive(Deserialize, DeserializeOver1)] pub struct Theme { - pub flavor: Flavor, - pub app: App, - pub mgr: Mgr, - pub tabs: Tabs, - pub mode: Mode, + pub flavor: Flavor, + pub app: App, + pub mgr: Mgr, + pub tabs: Tabs, + pub mode: Mode, pub indicator: Indicator, - pub status: Status, - pub which: Which, - pub confirm: Confirm, - pub spot: Spot, - pub notify: Notify, - pub pick: Pick, - pub input: Input, - pub cmp: Cmp, - pub tasks: Tasks, - pub help: Help, + pub status: Status, + pub which: Which, + pub confirm: Confirm, + pub spot: Spot, + pub notify: Notify, + pub pick: Pick, + pub input: Input, + pub cmp: Cmp, + pub tasks: Tasks, + pub help: Help, // File-specific styles #[serde(skip_serializing)] pub filetype: Filetype, #[serde(skip_serializing)] - pub icon: Icon, + pub icon: Icon, } #[derive(Deserialize, DeserializeOver2)] pub struct App { pub overall: Style, + #[serde(default)] + pub parent: Style, + #[serde(default)] + pub current: Style, + #[serde(default)] + pub preview: Style, } #[derive(Deserialize, DeserializeOver2)] @@ -44,26 +50,26 @@ pub struct Mgr { pub cwd: Style, // Find - pub find_keyword: Style, + pub find_keyword: Style, pub find_position: Style, // Symlink pub symlink_target: Style, // Marker - pub marker_copied: Style, - pub marker_cut: Style, - pub marker_marked: Style, + pub marker_copied: Style, + pub marker_cut: Style, + pub marker_marked: Style, pub marker_selected: Style, // Count - pub count_copied: Style, - pub count_cut: Style, + pub count_copied: Style, + pub count_cut: Style, pub count_selected: Style, // Border pub border_symbol: String, - pub border_style: Style, + pub border_style: Style, // Highlighting pub syntect_theme: PathBuf, @@ -71,7 +77,7 @@ pub struct Mgr { #[derive(Deserialize, DeserializeOver2)] pub struct Tabs { - pub active: Style, + pub active: Style, pub inactive: Style, pub sep_inner: TabsSep, @@ -80,25 +86,25 @@ pub struct Tabs { #[derive(Deserialize, DeserializeOver2)] pub struct TabsSep { - pub open: String, + pub open: String, pub close: String, } #[derive(Deserialize, DeserializeOver2)] pub struct Mode { pub normal_main: Style, - pub normal_alt: Style, + pub normal_alt: Style, pub select_main: Style, - pub select_alt: Style, + pub select_alt: Style, pub unset_main: Style, - pub unset_alt: Style, + pub unset_alt: Style, } #[derive(Deserialize, DeserializeOver2)] pub struct Indicator { - pub parent: Style, + pub parent: Style, pub current: Style, pub preview: Style, pub padding: IndicatorPadding, @@ -106,32 +112,32 @@ pub struct Indicator { #[derive(Deserialize, DeserializeOver2)] pub struct IndicatorPadding { - pub open: String, + pub open: String, pub close: String, } #[derive(Deserialize, DeserializeOver2)] pub struct Status { - pub overall: Style, - pub sep_left: StatusSep, + pub overall: Style, + pub sep_left: StatusSep, pub sep_right: StatusSep, // Permissions - pub perm_sep: Style, - pub perm_type: Style, - pub perm_read: Style, + pub perm_sep: Style, + pub perm_type: Style, + pub perm_read: Style, pub perm_write: Style, - pub perm_exec: Style, + pub perm_exec: Style, // Progress - pub progress_label: Style, + pub progress_label: Style, pub progress_normal: Style, - pub progress_error: Style, + pub progress_error: Style, } #[derive(Deserialize, DeserializeOver2)] pub struct StatusSep { - pub open: String, + pub open: String, pub close: String, } @@ -143,83 +149,83 @@ pub struct Which { pub rest: Style, pub desc: Style, - pub separator: String, + pub separator: String, pub separator_style: Style, } #[derive(Deserialize, DeserializeOver2)] pub struct Confirm { pub border: Style, - pub title: Style, - pub body: Style, - pub list: Style, + pub title: Style, + pub body: Style, + pub list: Style, - pub btn_yes: Style, - pub btn_no: Style, + pub btn_yes: Style, + pub btn_no: Style, pub btn_labels: [String; 2], } #[derive(Deserialize, DeserializeOver2)] pub struct Spot { pub border: Style, - pub title: Style, + pub title: Style, - pub tbl_col: Style, + pub tbl_col: Style, pub tbl_cell: Style, } #[derive(Deserialize, DeserializeOver2)] pub struct Notify { - pub title_info: Style, - pub title_warn: Style, + pub title_info: Style, + pub title_warn: Style, pub title_error: Style, - pub icon_info: String, - pub icon_warn: String, + pub icon_info: String, + pub icon_warn: String, pub icon_error: String, } #[derive(Deserialize, DeserializeOver2)] pub struct Pick { - pub border: Style, - pub active: Style, + pub border: Style, + pub active: Style, pub inactive: Style, } #[derive(Deserialize, DeserializeOver2)] pub struct Input { - pub border: Style, - pub title: Style, - pub value: Style, + pub border: Style, + pub title: Style, + pub value: Style, pub selected: Style, } #[derive(Deserialize, DeserializeOver2)] pub struct Cmp { - pub border: Style, - pub active: Style, + pub border: Style, + pub active: Style, pub inactive: Style, - pub icon_file: String, - pub icon_folder: String, + pub icon_file: String, + pub icon_folder: String, pub icon_command: String, } #[derive(Deserialize, DeserializeOver2)] pub struct Tasks { - pub border: Style, - pub title: Style, + pub border: Style, + pub title: Style, pub hovered: Style, } #[derive(Deserialize, DeserializeOver2)] pub struct Help { - pub on: Style, - pub run: Style, + pub on: Style, + pub run: Style, pub desc: Style, pub hovered: Style, - pub footer: Style, + pub footer: Style, } impl Theme { @@ -247,5 +253,19 @@ impl Theme { } impl App { - pub fn bg_color(&self) -> String { self.overall.bg.map(|c| c.to_string()).unwrap_or_default() } + pub fn bg_color(&self) -> String { + self.overall.bg.map(|c| c.to_string()).unwrap_or_default() + } + + pub fn parent_bg(&self) -> String { + self.parent.bg.map(|c| c.to_string()).unwrap_or_default() + } + + pub fn current_bg(&self) -> String { + self.current.bg.map(|c| c.to_string()).unwrap_or_default() + } + + pub fn preview_bg(&self) -> String { + self.preview.bg.map(|c| c.to_string()).unwrap_or_default() + } } diff --git a/yazi-fm/src/bg_render.rs b/yazi-fm/src/bg_render.rs index 054ed959..e130de66 100644 --- a/yazi-fm/src/bg_render.rs +++ b/yazi-fm/src/bg_render.rs @@ -1,5 +1,37 @@ use ratatui::{buffer::Buffer, layout::Rect, style::Color}; +/// Apply background color to a pane, skipping borders on all sides +#[inline] +pub fn apply_pane_bg_with_borders(buf: &mut Buffer, pane: Rect, bg_color: Color) { + let start_y = pane.top() + 1; + let end_y = if pane.bottom() > 0 { pane.bottom() - 1 } else { pane.bottom() }; + let start_x = pane.left() + 1; + let end_x = if pane.right() > 0 { pane.right() - 1 } else { pane.right() }; + + if start_y < end_y && start_x < end_x { + for y in start_y..end_y { + for x in start_x..end_x { + buf[(x, y)].set_bg(bg_color); + } + } + } +} + +/// Apply background color to a pane, skipping only top/bottom borders +#[inline] +pub fn apply_pane_bg_no_vertical_borders(buf: &mut Buffer, pane: Rect, bg_color: Color) { + let start_y = pane.top() + 1; + let end_y = if pane.bottom() > 0 { pane.bottom() - 1 } else { pane.bottom() }; + + if start_y < end_y { + for y in start_y..end_y { + for x in pane.left()..pane.right() { + buf[(x, y)].set_bg(bg_color); + } + } + } +} + /// Apply background color to entire area, excluding header and status bar #[inline] pub fn apply_overall_bg(buf: &mut Buffer, area: Rect, bg_color: Color) { diff --git a/yazi-fm/src/root.rs b/yazi-fm/src/root.rs index 31df492e..d2113cef 100644 --- a/yazi-fm/src/root.rs +++ b/yazi-fm/src/root.rs @@ -1,8 +1,12 @@ use mlua::{ObjectLike, Table}; -use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Direction, Layout, Rect}, + widgets::Widget, +}; use tracing::error; use yazi_binding::elements::render_once; -use yazi_config::THEME; +use yazi_config::{THEME, YAZI}; use yazi_core::Core; use yazi_plugin::LUA; @@ -13,7 +17,9 @@ pub(super) struct Root<'a> { } impl<'a> Root<'a> { - pub(super) fn new(core: &'a Core) -> Self { Self { core } } + pub(super) fn new(core: &'a Core) -> Self { + Self { core } + } pub(super) fn reflow(area: Rect) -> mlua::Result { let area = yazi_binding::elements::Rect::from(area); @@ -70,9 +76,57 @@ impl Widget for Root<'_> { which::Which::new(self.core).render(area, buf); } + // Calculate the Tab area (excluding Header, Tabs, and Status) + // This matches the Root layout in root.lua + let tabs_height = if self.core.mgr.tabs.len() > 1 { 1 } else { 0 }; + let root_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), // Header + Constraint::Length(tabs_height), // Tabs + Constraint::Fill(1), // Tab content (the 3 panes) + Constraint::Length(1), // Status + ]) + .split(area); + let tab_area = root_chunks[2]; + + // Apply per-pane backgrounds first, then app-wide background + // Calculate pane areas using the manager ratio within the tab area + let ratio = YAZI.mgr.ratio.get(); + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Ratio(ratio.parent as u32, ratio.all as u32), + Constraint::Ratio(ratio.current as u32, ratio.all as u32), + Constraint::Ratio(ratio.preview as u32, ratio.all as u32), + ]) + .split(tab_area); + + // Apply pane backgrounds (if configured) + // Skip borders: top/bottom rows and left/right edges where borders are drawn + let parent_bg = THEME.app.parent_bg(); + if !parent_bg.is_empty() { + if let Ok(bg_color) = parent_bg.parse::() { + apply_pane_bg_with_borders(buf, chunks[0], bg_color); + } + } + + let current_bg = THEME.app.current_bg(); + if !current_bg.is_empty() { + if let Ok(bg_color) = current_bg.parse::() { + apply_pane_bg_no_vertical_borders(buf, chunks[1], bg_color); + } + } + + let preview_bg = THEME.app.preview_bg(); + if !preview_bg.is_empty() { + if let Ok(bg_color) = preview_bg.parse::() { + apply_pane_bg_with_borders(buf, chunks[2], bg_color); + } + } + // Fill background on all cells to create an opaque background (like fzf does). // This is done AFTER all rendering so text/foreground colors are already set. - // The background is skipped on the header (top row) and status bar (bottom row). let bg_color_str = THEME.app.bg_color(); if !bg_color_str.is_empty() { if let Ok(bg_color) = bg_color_str.parse::() { diff --git a/yazi-fm/src/term.rs b/yazi-fm/src/term.rs index c0b23d3b..44516d5f 100644 --- a/yazi-fm/src/term.rs +++ b/yazi-fm/src/term.rs @@ -54,6 +54,8 @@ impl Term { Print("\x1b[?12$p"), // Request cursor blink status (DECRQM query for DECSET 12) Print("\x1b[?u"), // Request keyboard enhancement flags (CSI u) Print("\x1b[0c"), // Request device attributes + // Set terminal background color + yazi_term::If(!background.is_empty(), Print(format!("\x1b]11;{}\x1b\\", background))), yazi_term::If(TMUX.get(), EnterAlternateScreen), yazi_term::SetBackground(true, THEME.app.bg_color()), // Set app background EnableBracketedPaste, diff --git a/yazi-plugin/src/theme/theme.rs b/yazi-plugin/src/theme/theme.rs index 2a1eebbe..70e464a5 100644 --- a/yazi-plugin/src/theme/theme.rs +++ b/yazi-plugin/src/theme/theme.rs @@ -25,7 +25,9 @@ pub fn compose() -> Composer { .into_lua(lua) } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -35,11 +37,16 @@ fn app() -> Composer { let a = &THEME.app; match key { b"overall" => Style::from(a.overall).into_lua(lua), + b"parent" => Style::from(a.parent).into_lua(lua), + b"current" => Style::from(a.current).into_lua(lua), + b"preview" => Style::from(a.preview).into_lua(lua), _ => Ok(Value::Nil), } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -87,7 +94,9 @@ fn mgr() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -116,7 +125,9 @@ fn tabs() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -138,7 +149,9 @@ fn mode() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -161,7 +174,9 @@ fn indicator() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -198,7 +213,9 @@ fn status() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -220,7 +237,9 @@ fn which() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -247,7 +266,9 @@ fn confirm() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -266,7 +287,9 @@ fn spot() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -287,7 +310,9 @@ fn notify() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -304,7 +329,9 @@ fn pick() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -322,7 +349,9 @@ fn input() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -343,7 +372,9 @@ fn cmp() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -360,7 +391,9 @@ fn tasks() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) } @@ -380,7 +413,9 @@ fn help() -> Composer { } } - fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { + Ok(value) + } Composer::new(get, set) }