aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-08-05 03:41:38 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-08-05 04:07:11 +0300
commitadd6c14f1ee244502bd1a9599f2cd08320f935b7 (patch)
treed02b35da75a6a1ab9e37cc699c3db877cf84d884 /src
parent1f1e434268ad6e8102fdac6d5996e3f190138a28 (diff)
feat(theme): add optional sidebar background
Diffstat (limited to 'src')
-rw-r--r--src/app/state.rs50
-rw-r--r--src/config/theme.rs3
-rw-r--r--src/main.rs1
-rw-r--r--src/ui/sidebar.rs37
4 files changed, 90 insertions, 1 deletions
diff --git a/src/app/state.rs b/src/app/state.rs
index 7a8837ee..0e1a8439 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -107,8 +107,10 @@ use crate::workspace::Workspace;
pub struct Palette {
/// Primary accent (highlight, active borders).
pub accent: Color,
- /// Background for floating panels, overlays, and modals.
+ /// Background for the tab bar, floating panels, overlays, and modals.
pub panel_bg: Color,
+ /// Optional desktop sidebar background. Reset preserves the terminal background.
+ pub sidebar_bg: Color,
/// Subtle surface background for selected/focused items.
pub surface0: Color,
/// Slightly lighter surface for hover/active states.
@@ -145,6 +147,7 @@ impl Palette {
Self {
accent: Color::Rgb(137, 180, 250), // blue
panel_bg: Color::Rgb(24, 24, 37),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(49, 50, 68),
surface1: Color::Rgb(69, 71, 90),
surface_dim: Color::Rgb(30, 30, 46),
@@ -167,6 +170,7 @@ impl Palette {
Self {
accent: Color::Rgb(30, 102, 245),
panel_bg: Color::Rgb(239, 241, 245),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(204, 208, 218),
surface1: Color::Rgb(188, 192, 204),
surface_dim: Color::Rgb(230, 233, 239),
@@ -189,6 +193,7 @@ impl Palette {
Self {
accent: Color::Blue,
panel_bg: Color::Reset,
+ sidebar_bg: Color::Reset,
surface0: Color::Reset,
surface1: Color::DarkGray,
surface_dim: Color::DarkGray,
@@ -211,6 +216,7 @@ impl Palette {
Self {
accent: Color::Rgb(122, 162, 247), // blue
panel_bg: Color::Rgb(26, 27, 38),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(36, 40, 59),
surface1: Color::Rgb(65, 72, 104),
surface_dim: Color::Rgb(26, 27, 38),
@@ -233,6 +239,7 @@ impl Palette {
Self {
accent: Color::Rgb(46, 125, 233),
panel_bg: Color::Rgb(225, 226, 231),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(196, 200, 218),
surface1: Color::Rgb(168, 174, 203),
surface_dim: Color::Rgb(210, 211, 218),
@@ -255,6 +262,7 @@ impl Palette {
Self {
accent: Color::Rgb(189, 147, 249), // purple
panel_bg: Color::Rgb(40, 42, 54),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(68, 71, 90),
surface1: Color::Rgb(98, 114, 164),
surface_dim: Color::Rgb(40, 42, 54),
@@ -277,6 +285,7 @@ impl Palette {
Self {
accent: Color::Rgb(136, 192, 208), // frost
panel_bg: Color::Rgb(46, 52, 64),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(59, 66, 82),
surface1: Color::Rgb(67, 76, 94),
surface_dim: Color::Rgb(46, 52, 64),
@@ -299,6 +308,7 @@ impl Palette {
Self {
accent: Color::Rgb(215, 153, 33), // yellow
panel_bg: Color::Rgb(40, 40, 40),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(60, 56, 54),
surface1: Color::Rgb(80, 73, 69),
surface_dim: Color::Rgb(40, 40, 40),
@@ -321,6 +331,7 @@ impl Palette {
Self {
accent: Color::Rgb(7, 102, 120),
panel_bg: Color::Rgb(251, 241, 199),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(235, 219, 178),
surface1: Color::Rgb(213, 196, 161),
surface_dim: Color::Rgb(242, 229, 188),
@@ -343,6 +354,7 @@ impl Palette {
Self {
accent: Color::Rgb(97, 175, 239), // blue
panel_bg: Color::Rgb(40, 44, 52),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(44, 49, 58),
surface1: Color::Rgb(62, 68, 81),
surface_dim: Color::Rgb(40, 44, 52),
@@ -365,6 +377,7 @@ impl Palette {
Self {
accent: Color::Rgb(64, 120, 242),
panel_bg: Color::Rgb(250, 250, 250),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(240, 240, 241),
surface1: Color::Rgb(229, 229, 230),
surface_dim: Color::Rgb(245, 245, 246),
@@ -387,6 +400,7 @@ impl Palette {
Self {
accent: Color::Rgb(38, 139, 210), // blue
panel_bg: Color::Rgb(0, 43, 54),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(7, 54, 66),
surface1: Color::Rgb(88, 110, 117),
surface_dim: Color::Rgb(0, 43, 54),
@@ -409,6 +423,7 @@ impl Palette {
Self {
accent: Color::Rgb(38, 139, 210),
panel_bg: Color::Rgb(253, 246, 227),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(238, 232, 213),
surface1: Color::Rgb(147, 161, 161),
surface_dim: Color::Rgb(238, 232, 213),
@@ -431,6 +446,7 @@ impl Palette {
Self {
accent: Color::Rgb(126, 156, 216), // blue
panel_bg: Color::Rgb(31, 31, 40),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(42, 42, 55),
surface1: Color::Rgb(54, 54, 70),
surface_dim: Color::Rgb(31, 31, 40),
@@ -453,6 +469,7 @@ impl Palette {
Self {
accent: Color::Rgb(77, 105, 155),
panel_bg: Color::Rgb(242, 236, 188),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(220, 213, 172),
surface1: Color::Rgb(201, 203, 209),
surface_dim: Color::Rgb(213, 206, 163),
@@ -475,6 +492,7 @@ impl Palette {
Self {
accent: Color::Rgb(196, 167, 231), // iris
panel_bg: Color::Rgb(25, 23, 36),
+ sidebar_bg: Color::Reset,
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 {
Self {
accent: Color::Rgb(144, 122, 169),
panel_bg: Color::Rgb(250, 244, 237),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(242, 233, 225),
surface1: Color::Rgb(255, 250, 243),
surface_dim: Color::Rgb(242, 233, 225),
@@ -519,6 +538,7 @@ impl Palette {
Self {
accent: Color::Rgb(255, 199, 153),
panel_bg: Color::Rgb(26, 26, 26),
+ sidebar_bg: Color::Reset,
surface0: Color::Rgb(35, 35, 35),
surface1: Color::Rgb(40, 40, 40),
surface_dim: Color::Rgb(16, 16, 16),
@@ -570,6 +590,9 @@ impl Palette {
if let Some(c) = &custom.panel_bg {
self.panel_bg = parse_color(c);
}
+ if let Some(c) = &custom.sidebar_bg {
+ self.sidebar_bg = parse_color(c);
+ }
if let Some(c) = &custom.surface0 {
self.surface0 = parse_color(c);
}
@@ -2398,6 +2421,31 @@ mod tests {
}
#[test]
+ fn built_in_themes_leave_sidebar_background_unset() {
+ for name in THEME_NAMES {
+ let palette = Palette::from_name(name).unwrap();
+ assert_eq!(
+ palette.sidebar_bg,
+ Color::Reset,
+ "built-in theme changed the sidebar background: {name}"
+ );
+ }
+ }
+
+ #[test]
+ fn custom_sidebar_background_overrides_the_default() {
+ let custom = crate::config::CustomThemeColors {
+ sidebar_bg: Some("#181825".to_string()),
+ ..Default::default()
+ };
+
+ assert_eq!(
+ Palette::catppuccin().with_overrides(&custom).sidebar_bg,
+ Color::Rgb(24, 24, 37)
+ );
+ }
+
+ #[test]
fn light_theme_aliases_resolve() {
for name in ["light", "latte", "tokyo-day", "onelight", "lotus", "dawn"] {
assert!(
diff --git a/src/config/theme.rs b/src/config/theme.rs
index fc601d3f..e98c9dfb 100644
--- a/src/config/theme.rs
+++ b/src/config/theme.rs
@@ -32,6 +32,7 @@ pub struct ThemeConfig {
pub struct CustomThemeColors {
pub accent: Option<String>,
pub panel_bg: Option<String>,
+ pub sidebar_bg: Option<String>,
pub surface0: Option<String>,
pub surface1: Option<String>,
pub surface_dim: Option<String>,
@@ -164,6 +165,7 @@ name = "nord"
[theme.custom]
panel_bg = "#1e1e2e"
+sidebar_bg = "#181825"
accent = "#ff79c6"
red = "rgb(255, 85, 85)"
"##;
@@ -171,6 +173,7 @@ red = "rgb(255, 85, 85)"
assert_eq!(config.theme.name.as_deref(), Some("nord"));
let custom = config.theme.custom.as_ref().unwrap();
assert_eq!(custom.panel_bg.as_deref(), Some("#1e1e2e"));
+ assert_eq!(custom.sidebar_bg.as_deref(), Some("#181825"));
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 da905311..b6cc62b8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -128,6 +128,7 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration
# Override individual color tokens on top of the base theme.
# Accepts: hex (#rrggbb), named colors, rgb(r,g,b), or panel_bg = "reset"
# [theme.custom]
+# sidebar_bg = "#181825"
# panel_bg = "reset"
# accent = "#f5c2e7"
# red = "#ff6188"
diff --git a/src/ui/sidebar.rs b/src/ui/sidebar.rs
index 50796e81..7ce1b0ad 100644
--- a/src/ui/sidebar.rs
+++ b/src/ui/sidebar.rs
@@ -757,6 +757,9 @@ pub(super) fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area:
let is_navigating = matches!(app.mode, Mode::Navigate);
let p = &app.palette;
+ frame
+ .buffer_mut()
+ .set_style(area, Style::default().bg(p.sidebar_bg));
let sep_style = if is_navigating {
Style::default().fg(p.accent)
} else {
@@ -960,6 +963,9 @@ pub(super) fn render_sidebar(
area: Rect,
) {
let p = &app.palette;
+ frame
+ .buffer_mut()
+ .set_style(area, Style::default().bg(p.sidebar_bg));
let is_navigating = matches!(app.mode, Mode::Navigate);
let sep_style = if is_navigating {
Style::default().fg(p.accent)
@@ -1592,6 +1598,37 @@ mod tests {
}
#[test]
+ fn expanded_and_collapsed_sidebars_use_custom_background() {
+ let mut app = crate::app::state::AppState::test_new();
+ app.workspaces.clear();
+ app.active = None;
+ app.palette.sidebar_bg = ratatui::style::Color::Rgb(12, 34, 56);
+ let area = Rect::new(0, 0, 26, 20);
+
+ let mut expanded = Terminal::new(TestBackend::new(26, 20)).unwrap();
+ expanded
+ .draw(|frame| render_sidebar(&app, &TerminalRuntimeRegistry::new(), frame, area))
+ .unwrap();
+ assert!(expanded
+ .backend()
+ .buffer()
+ .content
+ .iter()
+ .all(|cell| cell.bg == app.palette.sidebar_bg));
+
+ let mut collapsed = Terminal::new(TestBackend::new(26, 20)).unwrap();
+ collapsed
+ .draw(|frame| render_sidebar_collapsed(&app, frame, area))
+ .unwrap();
+ assert!(collapsed
+ .backend()
+ .buffer()
+ .content
+ .iter()
+ .all(|cell| cell.bg == app.palette.sidebar_bg));
+ }
+
+ #[test]
fn default_agent_rows_remove_redundant_state_text() {
let mut app = crate::app::state::AppState::test_new();
let workspace = Workspace::test_new("one");