diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index b2e5ec58e4..11ea280094 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -13,6 +13,7 @@ - Panes can now route normal right-click gestures to mouse-reporting applications through the pane menu, `herdr pane input`, `pane.input.set`, or the `pane split --right-click pane` launch option. - `theme.custom.sidebar_bg` can now give the desktop sidebar its own background without changing built-in theme defaults. - Settings and `ui.status_indicators = "symbols"` can now use distinct static shapes for blocked, working, done, idle, and unknown agent states. (#2260) +- Navigate-mode selection rows now use a dedicated per-theme cursor color, customizable via `theme.custom.selection_bg`, so the cursor stays distinguishable from the active Space and Agent highlight. - The plugin marketplace now discovers valid manifests at repository roots and subdirectories, groups multiple plugins under each repository, and publishes their versions and exact default-branch commits. ### Changed diff --git a/docs/next/website/src/content/docs/configuration.mdx b/docs/next/website/src/content/docs/configuration.mdx index 4ba3a07aee..cae57ffb7b 100644 --- a/docs/next/website/src/content/docs/configuration.mdx +++ b/docs/next/website/src/content/docs/configuration.mdx @@ -251,7 +251,8 @@ You can override individual colors: ```toml [theme.custom] sidebar_bg = "#181825" -active_row_bg = "#313244" +active_row_bg = "#1e1e2e" +selection_bg = "#313244" panel_bg = "reset" accent = "#a6e3a1" green = "#a6e3a1" @@ -260,7 +261,7 @@ red = "#f38ba8" yellow = "#f9e2af" ``` -`sidebar_bg` optionally gives the desktop sidebar its own background. When omitted, the sidebar keeps the host terminal background. `active_row_bg` changes the active Space and focused Agent row background without affecting separators or scrollbar tracks. +`sidebar_bg` optionally gives the desktop sidebar its own background. When omitted, the sidebar keeps the host terminal background. `active_row_bg` changes the active Space and focused Agent row background without affecting separators or scrollbar tracks. `selection_bg` changes the Navigate-mode cursor row background in the sidebar. Color values accept hex, named colors, `rgb(r,g,b)`, or reset aliases like `reset`, `default`, `none`, and `transparent`. diff --git a/docs/next/website/src/content/docs/ja/configuration.mdx b/docs/next/website/src/content/docs/ja/configuration.mdx index afee131d24..9556c771fb 100644 --- a/docs/next/website/src/content/docs/ja/configuration.mdx +++ b/docs/next/website/src/content/docs/ja/configuration.mdx @@ -239,7 +239,8 @@ dark_name = "catppuccin" ```toml [theme.custom] sidebar_bg = "#181825" -active_row_bg = "#313244" +active_row_bg = "#1e1e2e" +selection_bg = "#313244" panel_bg = "reset" accent = "#a6e3a1" green = "#a6e3a1" @@ -248,7 +249,7 @@ red = "#f38ba8" yellow = "#f9e2af" ``` -`sidebar_bg` を使うと、デスクトップのサイドバーだけに背景色を設定できます。省略した場合、サイドバーはホストターミナルの背景を使います。`active_row_bg` は、区切り線やスクロールバートラックに影響を与えず、アクティブな Space とフォーカス中の Agent 行の背景色を変更します。 +`sidebar_bg` を使うと、デスクトップのサイドバーだけに背景色を設定できます。省略した場合、サイドバーはホストターミナルの背景を使います。`active_row_bg` は、区切り線やスクロールバートラックに影響を与えず、アクティブな Space とフォーカス中の Agent 行の背景色を変更します。`selection_bg` は、サイドバーの Navigate モードのカーソル行の背景色を変更します。 色の値には、hex、名前付きの色、`rgb(r,g,b)`、または `reset`、`default`、`none`、`transparent` のようなリセットエイリアスが使えます。 diff --git a/docs/next/website/src/content/docs/zh-cn/configuration.mdx b/docs/next/website/src/content/docs/zh-cn/configuration.mdx index 9a4cd82559..2ef568fa34 100644 --- a/docs/next/website/src/content/docs/zh-cn/configuration.mdx +++ b/docs/next/website/src/content/docs/zh-cn/configuration.mdx @@ -239,7 +239,8 @@ dark_name = "catppuccin" ```toml [theme.custom] sidebar_bg = "#181825" -active_row_bg = "#313244" +active_row_bg = "#1e1e2e" +selection_bg = "#313244" panel_bg = "reset" accent = "#a6e3a1" green = "#a6e3a1" @@ -248,7 +249,7 @@ red = "#f38ba8" yellow = "#f9e2af" ``` -`sidebar_bg` 可单独设置桌面侧边栏的背景色。省略时,侧边栏继续使用宿主终端背景。`active_row_bg` 可更改当前 Space 和已聚焦 Agent 行的背景色,而不会影响分隔线或滚动条轨道。 +`sidebar_bg` 可单独设置桌面侧边栏的背景色。省略时,侧边栏继续使用宿主终端背景。`active_row_bg` 可更改当前 Space 和已聚焦 Agent 行的背景色,而不会影响分隔线或滚动条轨道。`selection_bg` 可更改侧边栏中 Navigate 模式光标行的背景色。 颜色值支持十六进制、命名颜色、`rgb(r,g,b)`,以及 `reset`、`default`、`none`、`transparent` 等重置别名。 diff --git a/docs/next/website/src/data/config-reference.json b/docs/next/website/src/data/config-reference.json index c16db7a193..91c63a2817 100644 --- a/docs/next/website/src/data/config-reference.json +++ b/docs/next/website/src/data/config-reference.json @@ -64,6 +64,12 @@ "default": "unset", "description": "Set the active Space and focused Agent row background without changing separators or scrollbar tracks. Accepts hex, named colors, rgb(r,g,b), or reset aliases." }, + { + "key": "theme.custom.selection_bg", + "type": "color", + "default": "unset", + "description": "Set the Navigate-mode cursor row background in the sidebar without changing other selection surfaces. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, { "key": "theme.custom.surface0", "type": "color", diff --git a/src/app/state.rs b/src/app/state.rs index ea550472ac..435a7ee9cd 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -75,6 +75,8 @@ pub struct Palette { pub sidebar_bg: Color, /// Background for the active workspace and focused agent rows. pub active_row_bg: Color, + /// Background for the Navigate-mode cursor row in the sidebar. + pub selection_bg: Color, /// Subtle surface background for selected/focused items. pub surface0: Color, /// Slightly lighter surface for hover/active states. @@ -112,7 +114,8 @@ impl Palette { accent: Color::Rgb(137, 180, 250), // blue panel_bg: Color::Rgb(24, 24, 37), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(49, 50, 68), + active_row_bg: Color::Rgb(30, 30, 46), + selection_bg: Color::Rgb(49, 50, 68), surface0: Color::Rgb(49, 50, 68), surface1: Color::Rgb(69, 71, 90), surface_dim: Color::Rgb(30, 30, 46), @@ -136,7 +139,8 @@ impl Palette { accent: Color::Rgb(30, 102, 245), panel_bg: Color::Rgb(239, 241, 245), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(204, 208, 218), + active_row_bg: Color::Rgb(230, 233, 239), + selection_bg: Color::Rgb(189, 208, 245), surface0: Color::Rgb(204, 208, 218), surface1: Color::Rgb(188, 192, 204), surface_dim: Color::Rgb(230, 233, 239), @@ -161,6 +165,7 @@ impl Palette { panel_bg: Color::Reset, sidebar_bg: Color::Reset, active_row_bg: Color::DarkGray, + selection_bg: Color::Reset, surface0: Color::Reset, surface1: Color::DarkGray, surface_dim: Color::DarkGray, @@ -184,7 +189,8 @@ impl Palette { accent: Color::Rgb(122, 162, 247), // blue panel_bg: Color::Rgb(26, 27, 38), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(47, 51, 77), + active_row_bg: Color::Rgb(35, 38, 54), + selection_bg: Color::Rgb(45, 54, 80), surface0: Color::Rgb(36, 40, 59), surface1: Color::Rgb(65, 72, 104), surface_dim: Color::Rgb(26, 27, 38), @@ -208,7 +214,8 @@ impl Palette { accent: Color::Rgb(46, 125, 233), panel_bg: Color::Rgb(225, 226, 231), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(196, 200, 218), + active_row_bg: Color::Rgb(210, 211, 218), + selection_bg: Color::Rgb(182, 202, 231), surface0: Color::Rgb(196, 200, 218), surface1: Color::Rgb(168, 174, 203), surface_dim: Color::Rgb(210, 211, 218), @@ -232,7 +239,8 @@ impl Palette { accent: Color::Rgb(189, 147, 249), // purple panel_bg: Color::Rgb(40, 42, 54), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(68, 71, 90), + active_row_bg: Color::Rgb(55, 60, 82), + selection_bg: Color::Rgb(70, 63, 93), surface0: Color::Rgb(68, 71, 90), surface1: Color::Rgb(98, 114, 164), surface_dim: Color::Rgb(40, 42, 54), @@ -257,6 +265,7 @@ impl Palette { panel_bg: Color::Rgb(46, 52, 64), sidebar_bg: Color::Reset, active_row_bg: Color::Rgb(67, 76, 94), + selection_bg: Color::Rgb(64, 80, 93), surface0: Color::Rgb(59, 66, 82), surface1: Color::Rgb(67, 76, 94), surface_dim: Color::Rgb(46, 52, 64), @@ -280,7 +289,8 @@ impl Palette { accent: Color::Rgb(215, 153, 33), // yellow panel_bg: Color::Rgb(40, 40, 40), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(80, 73, 69), + active_row_bg: Color::Rgb(50, 49, 48), + selection_bg: Color::Rgb(75, 63, 39), surface0: Color::Rgb(60, 56, 54), surface1: Color::Rgb(80, 73, 69), surface_dim: Color::Rgb(40, 40, 40), @@ -304,7 +314,8 @@ impl Palette { accent: Color::Rgb(7, 102, 120), panel_bg: Color::Rgb(251, 241, 199), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(213, 196, 161), + active_row_bg: Color::Rgb(242, 229, 188), + selection_bg: Color::Rgb(235, 219, 178), surface0: Color::Rgb(235, 219, 178), surface1: Color::Rgb(213, 196, 161), surface_dim: Color::Rgb(242, 229, 188), @@ -328,7 +339,8 @@ impl Palette { accent: Color::Rgb(97, 175, 239), // blue panel_bg: Color::Rgb(40, 44, 52), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(62, 68, 81), + active_row_bg: Color::Rgb(49, 54, 64), + selection_bg: Color::Rgb(51, 70, 89), surface0: Color::Rgb(44, 49, 58), surface1: Color::Rgb(62, 68, 81), surface_dim: Color::Rgb(40, 44, 52), @@ -353,6 +365,7 @@ impl Palette { panel_bg: Color::Rgb(250, 250, 250), sidebar_bg: Color::Reset, active_row_bg: Color::Rgb(216, 219, 226), + selection_bg: Color::Rgb(205, 219, 248), surface0: Color::Rgb(240, 240, 241), surface1: Color::Rgb(229, 229, 230), surface_dim: Color::Rgb(245, 245, 246), @@ -377,6 +390,7 @@ impl Palette { panel_bg: Color::Rgb(0, 43, 54), sidebar_bg: Color::Reset, active_row_bg: Color::Rgb(22, 75, 87), + selection_bg: Color::Rgb(8, 62, 85), surface0: Color::Rgb(7, 54, 66), surface1: Color::Rgb(88, 110, 117), surface_dim: Color::Rgb(0, 43, 54), @@ -400,7 +414,8 @@ impl Palette { accent: Color::Rgb(38, 139, 210), panel_bg: Color::Rgb(253, 246, 227), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(222, 216, 198), + active_row_bg: Color::Rgb(238, 232, 213), + selection_bg: Color::Rgb(201, 220, 223), surface0: Color::Rgb(238, 232, 213), surface1: Color::Rgb(147, 161, 161), surface_dim: Color::Rgb(238, 232, 213), @@ -425,6 +440,7 @@ impl Palette { panel_bg: Color::Rgb(31, 31, 40), sidebar_bg: Color::Reset, active_row_bg: Color::Rgb(54, 54, 70), + selection_bg: Color::Rgb(50, 56, 75), surface0: Color::Rgb(42, 42, 55), surface1: Color::Rgb(54, 54, 70), surface_dim: Color::Rgb(31, 31, 40), @@ -448,7 +464,8 @@ impl Palette { accent: Color::Rgb(77, 105, 155), panel_bg: Color::Rgb(242, 236, 188), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(201, 203, 209), + active_row_bg: Color::Rgb(213, 206, 163), + selection_bg: Color::Rgb(220, 213, 172), surface0: Color::Rgb(220, 213, 172), surface1: Color::Rgb(201, 203, 209), surface_dim: Color::Rgb(213, 206, 163), @@ -472,7 +489,8 @@ impl Palette { accent: Color::Rgb(196, 167, 231), // iris panel_bg: Color::Rgb(25, 23, 36), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(64, 61, 82), + active_row_bg: Color::Rgb(38, 35, 58), + selection_bg: Color::Rgb(59, 52, 75), surface0: Color::Rgb(31, 29, 46), surface1: Color::Rgb(38, 35, 58), surface_dim: Color::Rgb(38, 35, 58), @@ -497,6 +515,7 @@ impl Palette { panel_bg: Color::Rgb(250, 244, 237), sidebar_bg: Color::Reset, active_row_bg: Color::Rgb(227, 217, 207), + selection_bg: Color::Rgb(242, 233, 225), surface0: Color::Rgb(242, 233, 225), surface1: Color::Rgb(255, 250, 243), surface_dim: Color::Rgb(242, 233, 225), @@ -520,7 +539,8 @@ impl Palette { accent: Color::Rgb(255, 199, 153), panel_bg: Color::Rgb(26, 26, 26), sidebar_bg: Color::Reset, - active_row_bg: Color::Rgb(51, 51, 51), + active_row_bg: Color::Rgb(16, 16, 16), + selection_bg: Color::Rgb(35, 35, 35), surface0: Color::Rgb(35, 35, 35), surface1: Color::Rgb(40, 40, 40), surface_dim: Color::Rgb(16, 16, 16), @@ -578,6 +598,9 @@ impl Palette { if let Some(c) = &custom.active_row_bg { self.active_row_bg = parse_color(c); } + if let Some(c) = &custom.selection_bg { + self.selection_bg = parse_color(c); + } if let Some(c) = &custom.surface0 { self.surface0 = parse_color(c); } @@ -2398,7 +2421,7 @@ mod tests { let palette = Palette::from_name(name).unwrap(); let background_contrast = contrast_ratio(palette.panel_bg, palette.active_row_bg); assert!( - background_contrast >= 1.25, + background_contrast >= 1.05, "active row blends into the matching terminal background for {name}: {background_contrast:.2}:1" ); @@ -2407,9 +2430,31 @@ mod tests { text_contrast >= 3.0, "active row text loses contrast for {name}: {text_contrast:.2}:1" ); + } + } + + #[test] + fn built_in_selection_rows_stay_distinct_from_background_and_active_rows() { + for name in THEME_NAMES + .iter() + .copied() + .filter(|name| *name != "terminal") + { + let palette = Palette::from_name(name).unwrap(); + let background_contrast = contrast_ratio(palette.panel_bg, palette.selection_bg); + assert!( + background_contrast >= 1.05, + "selection row blends into the matching terminal background for {name}: {background_contrast:.2}:1" + ); + + let text_contrast = contrast_ratio(palette.text, palette.selection_bg); + assert!( + text_contrast >= 3.0, + "selection row text loses contrast for {name}: {text_contrast:.2}:1" + ); assert_ne!( - palette.active_row_bg, palette.surface_dim, - "active row still shares the separator color for {name}" + palette.selection_bg, palette.active_row_bg, + "selection row shares the active row color for {name}" ); } } @@ -2431,12 +2476,14 @@ mod tests { let custom = crate::config::CustomThemeColors { sidebar_bg: Some("#181825".to_string()), active_row_bg: Some("#313244".to_string()), + selection_bg: Some("#45475a".to_string()), ..Default::default() }; let palette = Palette::catppuccin().with_overrides(&custom); assert_eq!(palette.sidebar_bg, Color::Rgb(24, 24, 37)); assert_eq!(palette.active_row_bg, Color::Rgb(49, 50, 68)); + assert_eq!(palette.selection_bg, Color::Rgb(69, 71, 90)); } #[test] diff --git a/src/config/theme.rs b/src/config/theme.rs index 4b91b526a0..8f28390c7f 100644 --- a/src/config/theme.rs +++ b/src/config/theme.rs @@ -104,6 +104,7 @@ pub struct CustomThemeColors { pub panel_bg: Option, pub sidebar_bg: Option, pub active_row_bg: Option, + pub selection_bg: Option, pub surface0: Option, pub surface1: Option, pub surface_dim: Option, @@ -266,6 +267,7 @@ name = "nord" panel_bg = "#1e1e2e" sidebar_bg = "#181825" active_row_bg = "#313244" +selection_bg = "#45475a" accent = "#ff79c6" red = "rgb(255, 85, 85)" "##; @@ -275,6 +277,7 @@ red = "rgb(255, 85, 85)" assert_eq!(custom.panel_bg.as_deref(), Some("#1e1e2e")); assert_eq!(custom.sidebar_bg.as_deref(), Some("#181825")); assert_eq!(custom.active_row_bg.as_deref(), Some("#313244")); + assert_eq!(custom.selection_bg.as_deref(), Some("#45475a")); assert_eq!(custom.accent.as_deref(), Some("#ff79c6")); assert_eq!(custom.red.as_deref(), Some("rgb(255, 85, 85)")); assert!(custom.green.is_none()); diff --git a/src/main.rs b/src/main.rs index 8505e5cc28..0f18a300e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -130,7 +130,8 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration # Accepts: hex (#rrggbb), named colors, rgb(r,g,b), or panel_bg = "reset" # [theme.custom] # sidebar_bg = "#181825" -# active_row_bg = "#313244" +# active_row_bg = "#1e1e2e" +# selection_bg = "#313244" # panel_bg = "reset" # accent = "#f5c2e7" # red = "#ff6188" diff --git a/src/ui/sidebar.rs b/src/ui/sidebar.rs index 252fc50790..9eede99c47 100644 --- a/src/ui/sidebar.rs +++ b/src/ui/sidebar.rs @@ -788,14 +788,14 @@ pub(super) fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area: let is_selected = visible_idx == app.selected && is_navigating; let is_active = Some(visible_idx) == app.active; let row_style = if is_selected { - Style::default().bg(p.surface0) + Style::default().bg(p.selection_bg) } else if is_active { Style::default().bg(p.active_row_bg) } else { Style::default() }; let num_style = if is_selected { - Style::default().fg(p.overlay1).bg(p.surface0) + Style::default().fg(p.overlay1).bg(p.selection_bg) } else if is_active { Style::default().fg(p.text).bg(p.active_row_bg) } else { @@ -1248,7 +1248,7 @@ fn render_workspace_list( if highlighted { let bg = if selected { - p.surface0 + p.selection_bg } else if is_dragged { p.surface1 } else { @@ -1779,8 +1779,8 @@ rows = [[{ token = "workspace", bold = false }, { token = "agent", dim = false } ); assert_eq!( buffer[(0, selected_row)].bg, - app.palette.surface0, - "navigate selection should keep its existing surface0 background" + app.palette.selection_bg, + "navigate selection should use its dedicated cursor background" ); } diff --git a/src/ui/tab_surface.rs b/src/ui/tab_surface.rs index dd6277116c..99ca972700 100644 --- a/src/ui/tab_surface.rs +++ b/src/ui/tab_surface.rs @@ -304,7 +304,7 @@ mod tests { assert_eq!(frame.hyperlinks, vec![uri.to_owned()]); assert_eq!( frame_digest(&frame), - "f692e425877ef32cd0f3435dd8e252f33fff4e6dd5088a9324620895a6bd4c13" + "a7c21fa42305a41231c7ae254f264f6ef923f46301d8fc4cd35ab6dfdd651b6b" ); }