feat(ui): introduce focus beacon and unified panel styling helpers
Add `focus_beacon_span`, `panel_title_spans`, `panel_hint_style`, and `panel_border_style` utilities to centralize panel header, hint, border, and beacon rendering. Integrate these helpers across all UI panels (files, chat, thinking, agent actions, input, status bar) and update help text. Extend `Theme` with new color fields for beacons, pane headers, and hint text, providing defaults for all built‑in themes. Include comprehensive unit tests for the new styling functions.
This commit is contained in:
@@ -36,6 +36,42 @@ pub struct Theme {
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub unfocused_panel_border: Color,
|
||||
|
||||
/// Foreground color for the active pane beacon (`▌`)
|
||||
#[serde(default = "Theme::default_focus_beacon_fg")]
|
||||
#[serde(deserialize_with = "deserialize_color")]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub focus_beacon_fg: Color,
|
||||
|
||||
/// Background color for the active pane beacon (`▌`)
|
||||
#[serde(default = "Theme::default_focus_beacon_bg")]
|
||||
#[serde(deserialize_with = "deserialize_color")]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub focus_beacon_bg: Color,
|
||||
|
||||
/// Foreground color for the inactive pane beacon (`▌`)
|
||||
#[serde(default = "Theme::default_unfocused_beacon_fg")]
|
||||
#[serde(deserialize_with = "deserialize_color")]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub unfocused_beacon_fg: Color,
|
||||
|
||||
/// Title color for active pane headers
|
||||
#[serde(default = "Theme::default_pane_header_active")]
|
||||
#[serde(deserialize_with = "deserialize_color")]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub pane_header_active: Color,
|
||||
|
||||
/// Title color for inactive pane headers
|
||||
#[serde(default = "Theme::default_pane_header_inactive")]
|
||||
#[serde(deserialize_with = "deserialize_color")]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub pane_header_inactive: Color,
|
||||
|
||||
/// Hint text color used within pane headers
|
||||
#[serde(default = "Theme::default_pane_hint_text")]
|
||||
#[serde(deserialize_with = "deserialize_color")]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub pane_hint_text: Color,
|
||||
|
||||
/// Color for user message role indicator
|
||||
#[serde(deserialize_with = "deserialize_color")]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
@@ -313,6 +349,30 @@ impl Theme {
|
||||
Color::Cyan
|
||||
}
|
||||
|
||||
const fn default_focus_beacon_fg() -> Color {
|
||||
Color::LightMagenta
|
||||
}
|
||||
|
||||
const fn default_focus_beacon_bg() -> Color {
|
||||
Color::Black
|
||||
}
|
||||
|
||||
const fn default_unfocused_beacon_fg() -> Color {
|
||||
Color::DarkGray
|
||||
}
|
||||
|
||||
const fn default_pane_header_active() -> Color {
|
||||
Color::White
|
||||
}
|
||||
|
||||
const fn default_pane_header_inactive() -> Color {
|
||||
Color::Gray
|
||||
}
|
||||
|
||||
const fn default_pane_hint_text() -> Color {
|
||||
Color::DarkGray
|
||||
}
|
||||
|
||||
const fn default_operating_chat_fg() -> Color {
|
||||
Color::Black
|
||||
}
|
||||
@@ -474,6 +534,12 @@ fn default_dark() -> Theme {
|
||||
background: Color::Black,
|
||||
focused_panel_border: Color::LightMagenta,
|
||||
unfocused_panel_border: Color::Rgb(95, 20, 135),
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::LightBlue,
|
||||
assistant_message_role: Color::Yellow,
|
||||
tool_output: Color::Gray,
|
||||
@@ -523,6 +589,12 @@ fn default_light() -> Theme {
|
||||
background: Color::White,
|
||||
focused_panel_border: Color::Rgb(74, 144, 226),
|
||||
unfocused_panel_border: Color::Rgb(221, 221, 221),
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(0, 85, 164),
|
||||
assistant_message_role: Color::Rgb(142, 68, 173),
|
||||
tool_output: Color::Gray,
|
||||
@@ -572,7 +644,13 @@ fn gruvbox() -> Theme {
|
||||
background: Color::Rgb(40, 40, 40), // #282828
|
||||
focused_panel_border: Color::Rgb(254, 128, 25), // #fe8019 (orange)
|
||||
unfocused_panel_border: Color::Rgb(124, 111, 100), // #7c6f64
|
||||
user_message_role: Color::Rgb(184, 187, 38), // #b8bb26 (green)
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(184, 187, 38), // #b8bb26 (green)
|
||||
assistant_message_role: Color::Rgb(131, 165, 152), // #83a598 (blue)
|
||||
tool_output: Color::Rgb(146, 131, 116),
|
||||
thinking_panel_title: Color::Rgb(211, 134, 155), // #d3869b (purple)
|
||||
@@ -617,11 +695,17 @@ fn gruvbox() -> Theme {
|
||||
fn dracula() -> Theme {
|
||||
Theme {
|
||||
name: "dracula".to_string(),
|
||||
text: Color::Rgb(248, 248, 242), // #f8f8f2
|
||||
background: Color::Rgb(40, 42, 54), // #282a36
|
||||
focused_panel_border: Color::Rgb(255, 121, 198), // #ff79c6 (pink)
|
||||
unfocused_panel_border: Color::Rgb(68, 71, 90), // #44475a
|
||||
user_message_role: Color::Rgb(139, 233, 253), // #8be9fd (cyan)
|
||||
text: Color::Rgb(248, 248, 242), // #f8f8f2
|
||||
background: Color::Rgb(40, 42, 54), // #282a36
|
||||
focused_panel_border: Color::Rgb(255, 121, 198), // #ff79c6 (pink)
|
||||
unfocused_panel_border: Color::Rgb(68, 71, 90), // #44475a
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(139, 233, 253), // #8be9fd (cyan)
|
||||
assistant_message_role: Color::Rgb(255, 121, 198), // #ff79c6 (pink)
|
||||
tool_output: Color::Rgb(98, 114, 164),
|
||||
thinking_panel_title: Color::Rgb(189, 147, 249), // #bd93f9 (purple)
|
||||
@@ -670,6 +754,12 @@ fn solarized() -> Theme {
|
||||
background: Color::Rgb(0, 43, 54), // #002b36 (base03)
|
||||
focused_panel_border: Color::Rgb(38, 139, 210), // #268bd2 (blue)
|
||||
unfocused_panel_border: Color::Rgb(7, 54, 66), // #073642 (base02)
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(42, 161, 152), // #2aa198 (cyan)
|
||||
assistant_message_role: Color::Rgb(203, 75, 22), // #cb4b16 (orange)
|
||||
tool_output: Color::Rgb(101, 123, 131),
|
||||
@@ -719,6 +809,12 @@ fn midnight_ocean() -> Theme {
|
||||
background: Color::Rgb(13, 17, 23),
|
||||
focused_panel_border: Color::Rgb(88, 166, 255),
|
||||
unfocused_panel_border: Color::Rgb(48, 54, 61),
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(121, 192, 255),
|
||||
assistant_message_role: Color::Rgb(137, 221, 255),
|
||||
tool_output: Color::Rgb(84, 110, 122),
|
||||
@@ -764,11 +860,17 @@ fn midnight_ocean() -> Theme {
|
||||
fn rose_pine() -> Theme {
|
||||
Theme {
|
||||
name: "rose-pine".to_string(),
|
||||
text: Color::Rgb(224, 222, 244), // #e0def4
|
||||
background: Color::Rgb(25, 23, 36), // #191724
|
||||
focused_panel_border: Color::Rgb(235, 111, 146), // #eb6f92 (love)
|
||||
unfocused_panel_border: Color::Rgb(38, 35, 58), // #26233a
|
||||
user_message_role: Color::Rgb(49, 116, 143), // #31748f (foam)
|
||||
text: Color::Rgb(224, 222, 244), // #e0def4
|
||||
background: Color::Rgb(25, 23, 36), // #191724
|
||||
focused_panel_border: Color::Rgb(235, 111, 146), // #eb6f92 (love)
|
||||
unfocused_panel_border: Color::Rgb(38, 35, 58), // #26233a
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(49, 116, 143), // #31748f (foam)
|
||||
assistant_message_role: Color::Rgb(156, 207, 216), // #9ccfd8 (foam light)
|
||||
tool_output: Color::Rgb(110, 106, 134),
|
||||
thinking_panel_title: Color::Rgb(196, 167, 231), // #c4a7e7 (iris)
|
||||
@@ -813,11 +915,17 @@ fn rose_pine() -> Theme {
|
||||
fn monokai() -> Theme {
|
||||
Theme {
|
||||
name: "monokai".to_string(),
|
||||
text: Color::Rgb(248, 248, 242), // #f8f8f2
|
||||
background: Color::Rgb(39, 40, 34), // #272822
|
||||
focused_panel_border: Color::Rgb(249, 38, 114), // #f92672 (pink)
|
||||
unfocused_panel_border: Color::Rgb(117, 113, 94), // #75715e
|
||||
user_message_role: Color::Rgb(102, 217, 239), // #66d9ef (cyan)
|
||||
text: Color::Rgb(248, 248, 242), // #f8f8f2
|
||||
background: Color::Rgb(39, 40, 34), // #272822
|
||||
focused_panel_border: Color::Rgb(249, 38, 114), // #f92672 (pink)
|
||||
unfocused_panel_border: Color::Rgb(117, 113, 94), // #75715e
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(102, 217, 239), // #66d9ef (cyan)
|
||||
assistant_message_role: Color::Rgb(174, 129, 255), // #ae81ff (purple)
|
||||
tool_output: Color::Rgb(117, 113, 94),
|
||||
thinking_panel_title: Color::Rgb(230, 219, 116), // #e6db74 (yellow)
|
||||
@@ -862,11 +970,17 @@ fn monokai() -> Theme {
|
||||
fn material_dark() -> Theme {
|
||||
Theme {
|
||||
name: "material-dark".to_string(),
|
||||
text: Color::Rgb(238, 255, 255), // #eeffff
|
||||
background: Color::Rgb(38, 50, 56), // #263238
|
||||
focused_panel_border: Color::Rgb(128, 203, 196), // #80cbc4 (cyan)
|
||||
unfocused_panel_border: Color::Rgb(84, 110, 122), // #546e7a
|
||||
user_message_role: Color::Rgb(130, 170, 255), // #82aaff (blue)
|
||||
text: Color::Rgb(238, 255, 255), // #eeffff
|
||||
background: Color::Rgb(38, 50, 56), // #263238
|
||||
focused_panel_border: Color::Rgb(128, 203, 196), // #80cbc4 (cyan)
|
||||
unfocused_panel_border: Color::Rgb(84, 110, 122), // #546e7a
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(130, 170, 255), // #82aaff (blue)
|
||||
assistant_message_role: Color::Rgb(199, 146, 234), // #c792ea (purple)
|
||||
tool_output: Color::Rgb(84, 110, 122),
|
||||
thinking_panel_title: Color::Rgb(255, 203, 107), // #ffcb6b (yellow)
|
||||
@@ -915,6 +1029,12 @@ fn material_light() -> Theme {
|
||||
background: Color::Rgb(236, 239, 241),
|
||||
focused_panel_border: Color::Rgb(0, 150, 136),
|
||||
unfocused_panel_border: Color::Rgb(176, 190, 197),
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(68, 138, 255),
|
||||
assistant_message_role: Color::Rgb(124, 77, 255),
|
||||
tool_output: Color::Rgb(144, 164, 174),
|
||||
@@ -964,6 +1084,12 @@ fn grayscale_high_contrast() -> Theme {
|
||||
background: Color::Black,
|
||||
focused_panel_border: Color::White,
|
||||
unfocused_panel_border: Color::Rgb(76, 76, 76),
|
||||
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
||||
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
||||
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
||||
pane_header_active: Theme::default_pane_header_active(),
|
||||
pane_header_inactive: Theme::default_pane_header_inactive(),
|
||||
pane_hint_text: Theme::default_pane_hint_text(),
|
||||
user_message_role: Color::Rgb(240, 240, 240),
|
||||
assistant_message_role: Color::Rgb(214, 214, 214),
|
||||
tool_output: Color::Rgb(189, 189, 189),
|
||||
|
||||
Reference in New Issue
Block a user