Skip to main content

rustyclaw_tui/
theme.rs

1// ── RustyClaw TUI Theme ─────────────────────────────────────────────────────
2//
3// Colour palette for the iocraft TUI.
4// Follows the "lobster palette" from rustyclaw-core/src/theme.rs
5//
6// | Token          | Hex       | Usage                          |
7// |----------------|-----------|--------------------------------|
8// | accent         | `#FF5A2D` | headings, labels, primary      |
9// | accent_bright  | `#FF7A3D` | command names, emphasis        |
10// | accent_dim     | `#D14A22` | secondary highlight            |
11// | info           | `#FF8A5B` | informational values           |
12// | success        | `#2FBF71` | success states                 |
13// | warn           | `#FFB020` | warnings, fallbacks            |
14// | error          | `#E23D2D` | errors, failures               |
15// | muted          | `#8B7F77` | de-emphasis, metadata          |
16
17use iocraft::prelude::*;
18use rustyclaw_core::types::MessageRole;
19
20// ── Accent (lobster orange) ─────────────────────────────────────────────────
21
22pub const ACCENT: Color = Color::Rgb {
23    r: 0xFF,
24    g: 0x5A,
25    b: 0x2D,
26};
27pub const ACCENT_BRIGHT: Color = Color::Rgb {
28    r: 0xFF,
29    g: 0x7A,
30    b: 0x3D,
31};
32pub const ACCENT_DIM: Color = Color::Rgb {
33    r: 0xD1,
34    g: 0x4A,
35    b: 0x22,
36};
37
38// ── Text ────────────────────────────────────────────────────────────────────
39
40pub const TEXT: Color = Color::Rgb {
41    r: 0xE8,
42    g: 0xE0,
43    b: 0xD8,
44};
45pub const TEXT_DIM: Color = Color::Rgb {
46    r: 0xA0,
47    g: 0x98,
48    b: 0x90,
49};
50pub const MUTED: Color = Color::Rgb {
51    r: 0x8B,
52    g: 0x7F,
53    b: 0x77,
54};
55
56// ── Semantic ────────────────────────────────────────────────────────────────
57
58pub const INFO: Color = Color::Rgb {
59    r: 0xFF,
60    g: 0x8A,
61    b: 0x5B,
62};
63pub const SUCCESS: Color = Color::Rgb {
64    r: 0x2F,
65    g: 0xBF,
66    b: 0x71,
67};
68pub const WARN: Color = Color::Rgb {
69    r: 0xFF,
70    g: 0xB0,
71    b: 0x20,
72};
73pub const ERROR: Color = Color::Rgb {
74    r: 0xE2,
75    g: 0x3D,
76    b: 0x2D,
77};
78
79// ── Backgrounds ─────────────────────────────────────────────────────────────
80
81pub const BG_MAIN: Color = Color::Rgb {
82    r: 0x1A,
83    g: 0x18,
84    b: 0x16,
85};
86pub const BG_SURFACE: Color = Color::Rgb {
87    r: 0x24,
88    g: 0x20,
89    b: 0x1C,
90};
91pub const BG_USER: Color = Color::Rgb {
92    r: 0x2A,
93    g: 0x22,
94    b: 0x1A,
95};
96pub const BG_ASSISTANT: Color = Color::Rgb {
97    r: 0x22,
98    g: 0x1E,
99    b: 0x1A,
100};
101pub const BG_CODE: Color = Color::Rgb {
102    r: 0x1E,
103    g: 0x1A,
104    b: 0x16,
105};
106
107// ── Spinner frames ──────────────────────────────────────────────────────────
108
109pub const SPINNER: &[char] = &['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
110
111// ── Role helpers ────────────────────────────────────────────────────────────
112
113pub fn role_color(role: &MessageRole) -> Color {
114    match role {
115        MessageRole::User => ACCENT_BRIGHT,
116        MessageRole::Assistant => TEXT,
117        MessageRole::Info => INFO,
118        MessageRole::Success => SUCCESS,
119        MessageRole::Warning => WARN,
120        MessageRole::Error => ERROR,
121        MessageRole::System => MUTED,
122        MessageRole::ToolCall => MUTED,
123        MessageRole::ToolResult => TEXT_DIM,
124        MessageRole::Thinking => MUTED,
125    }
126}
127
128pub fn role_bg(role: &MessageRole) -> Color {
129    match role {
130        MessageRole::User => BG_USER,
131        MessageRole::Assistant => BG_ASSISTANT,
132        MessageRole::ToolCall | MessageRole::ToolResult => BG_CODE,
133        _ => BG_SURFACE,
134    }
135}
136
137pub fn role_border(role: &MessageRole) -> Color {
138    match role {
139        MessageRole::User => ACCENT_BRIGHT,
140        MessageRole::Assistant => ACCENT_DIM,
141        MessageRole::Error => ERROR,
142        MessageRole::Warning => WARN,
143        MessageRole::Success => SUCCESS,
144        MessageRole::Info => INFO,
145        _ => MUTED,
146    }
147}
148
149pub fn gateway_color(status: &rustyclaw_core::types::GatewayStatus) -> Color {
150    use rustyclaw_core::types::GatewayStatus::*;
151    match status {
152        Connected | ModelReady => SUCCESS,
153        Connecting => WARN,
154        Disconnected | Error | ModelError => ERROR,
155        Unconfigured => MUTED,
156        VaultLocked | AuthRequired => WARN,
157    }
158}
159
160pub fn gateway_icon(status: &rustyclaw_core::types::GatewayStatus) -> &'static str {
161    use rustyclaw_core::types::GatewayStatus::*;
162    match status {
163        Connected | ModelReady => "●",
164        Connecting => "◌",
165        Disconnected => "○",
166        Error | ModelError => "✖",
167        Unconfigured => "○",
168        VaultLocked => "🔒",
169        AuthRequired => "🔑",
170    }
171}