1use std::sync::OnceLock;
4
5use ratatui::style::{Color, Modifier, Style};
6
7static PALETTE: OnceLock<Palette> = OnceLock::new();
9
10#[derive(Debug, Clone, Copy)]
12pub struct Palette {
13 pub bg: Color,
14 pub fg: Color,
15 pub dim: Color,
16 pub surface: Color,
17 pub overlay: Color,
18 pub blue: Color,
19 pub green: Color,
20 pub red: Color,
21 pub yellow: Color,
22 pub peach: Color,
23 pub mauve: Color,
24 pub teal: Color,
25 pub pink: Color,
26}
27
28impl Palette {
29 pub const DARK: Self = Self {
31 bg: Color::Rgb(22, 22, 30),
32 fg: Color::Rgb(205, 214, 244),
33 dim: Color::Rgb(108, 112, 134),
34 surface: Color::Rgb(30, 30, 46),
35 overlay: Color::Rgb(49, 50, 68),
36 blue: Color::Rgb(137, 180, 250),
37 green: Color::Rgb(166, 227, 161),
38 red: Color::Rgb(243, 139, 168),
39 yellow: Color::Rgb(249, 226, 175),
40 peach: Color::Rgb(250, 179, 135),
41 mauve: Color::Rgb(203, 166, 247),
42 teal: Color::Rgb(148, 226, 213),
43 pink: Color::Rgb(245, 194, 231),
44 };
45
46 pub const LIGHT: Self = Self {
48 bg: Color::Rgb(239, 241, 245),
49 fg: Color::Rgb(76, 79, 105),
50 dim: Color::Rgb(140, 143, 161),
51 surface: Color::Rgb(230, 233, 239),
52 overlay: Color::Rgb(204, 208, 218),
53 blue: Color::Rgb(30, 102, 245),
54 green: Color::Rgb(64, 160, 43),
55 red: Color::Rgb(210, 15, 57),
56 yellow: Color::Rgb(223, 142, 29),
57 peach: Color::Rgb(254, 100, 11),
58 mauve: Color::Rgb(136, 57, 239),
59 teal: Color::Rgb(23, 146, 153),
60 pink: Color::Rgb(234, 118, 203),
61 };
62}
63
64pub struct Theme;
66
67impl Theme {
68 pub fn init(light: bool) {
71 let palette = if light { Palette::LIGHT } else { Palette::DARK };
72 let _ = PALETTE.set(palette);
73 }
74
75 #[inline]
76 fn p() -> &'static Palette {
77 PALETTE.get().unwrap_or(&Palette::DARK)
78 }
79
80 #[inline]
82 pub fn fg() -> Color {
83 Self::p().fg
84 }
85 #[inline]
86 pub fn bg() -> Color {
87 Self::p().bg
88 }
89 #[inline]
90 pub fn dim_color() -> Color {
91 Self::p().dim
92 }
93 #[inline]
94 pub fn surface_color() -> Color {
95 Self::p().surface
96 }
97 #[inline]
98 pub fn overlay_color() -> Color {
99 Self::p().overlay
100 }
101 #[inline]
102 pub fn blue() -> Color {
103 Self::p().blue
104 }
105 #[inline]
106 pub fn green() -> Color {
107 Self::p().green
108 }
109 #[inline]
110 pub fn red() -> Color {
111 Self::p().red
112 }
113 #[inline]
114 pub fn yellow() -> Color {
115 Self::p().yellow
116 }
117 #[inline]
118 pub fn peach() -> Color {
119 Self::p().peach
120 }
121 #[inline]
122 pub fn mauve() -> Color {
123 Self::p().mauve
124 }
125 #[inline]
126 pub fn teal() -> Color {
127 Self::p().teal
128 }
129 #[inline]
130 pub fn pink() -> Color {
131 Self::p().pink
132 }
133
134 pub const FG: Color = Color::Rgb(205, 214, 244);
138 pub const BG: Color = Color::Rgb(22, 22, 30);
139 pub const DIM: Color = Color::Rgb(108, 112, 134);
140 pub const SURFACE: Color = Color::Rgb(30, 30, 46);
141 pub const OVERLAY: Color = Color::Rgb(49, 50, 68);
142 pub const BLUE: Color = Color::Rgb(137, 180, 250);
143 pub const GREEN: Color = Color::Rgb(166, 227, 161);
144 pub const RED: Color = Color::Rgb(243, 139, 168);
145 pub const YELLOW: Color = Color::Rgb(249, 226, 175);
146 pub const PEACH: Color = Color::Rgb(250, 179, 135);
147 pub const MAUVE: Color = Color::Rgb(203, 166, 247);
148 pub const TEAL: Color = Color::Rgb(148, 226, 213);
149 pub const PINK: Color = Color::Rgb(245, 194, 231);
150
151 pub const STATUS_UP: Color = Self::GREEN;
152 pub const STATUS_DOWN: Color = Self::RED;
153 pub const STATUS_WARN: Color = Self::YELLOW;
154
155 pub fn base() -> Style {
158 Style::default().fg(Self::fg()).bg(Self::bg())
159 }
160
161 pub fn surface() -> Style {
162 Style::default().fg(Self::fg()).bg(Self::surface_color())
163 }
164
165 pub fn title() -> Style {
166 Style::default().fg(Self::blue()).add_modifier(Modifier::BOLD)
167 }
168
169 pub fn highlight() -> Style {
170 Style::default().fg(Self::bg()).bg(Self::blue())
171 }
172
173 pub fn tab_active() -> Style {
174 Style::default().fg(Self::bg()).bg(Self::blue()).add_modifier(Modifier::BOLD)
175 }
176
177 pub fn tab_inactive() -> Style {
178 Style::default().fg(Self::dim_color()).bg(Self::surface_color())
179 }
180
181 pub fn status_bar() -> Style {
182 Style::default().fg(Self::fg()).bg(Self::overlay_color())
183 }
184
185 pub fn key_hint() -> Style {
186 Style::default().fg(Self::yellow()).add_modifier(Modifier::BOLD)
187 }
188
189 pub fn error() -> Style {
190 Style::default().fg(Self::red())
191 }
192
193 pub fn success() -> Style {
194 Style::default().fg(Self::green())
195 }
196
197 pub fn dim() -> Style {
198 Style::default().fg(Self::dim_color())
199 }
200
201 pub fn http_method(method: &str) -> Style {
202 let color = match method.to_uppercase().as_str() {
203 "GET" => Self::green(),
204 "POST" => Self::blue(),
205 "PUT" => Self::yellow(),
206 "PATCH" => Self::peach(),
207 "DELETE" => Self::red(),
208 "HEAD" | "OPTIONS" => Self::dim_color(),
209 _ => Self::fg(),
210 };
211 Style::default().fg(color).add_modifier(Modifier::BOLD)
212 }
213
214 pub fn status_code(code: u16) -> Style {
215 let color = match code {
216 200..=299 => Self::green(),
217 300..=399 => Self::blue(),
218 400..=499 => Self::yellow(),
219 500..=599 => Self::red(),
220 _ => Self::dim_color(),
221 };
222 Style::default().fg(color)
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn dark_palette_default() {
232 let p = Theme::p();
234 assert_eq!(p.bg, Palette::DARK.bg);
235 assert_eq!(p.fg, Palette::DARK.fg);
236 }
237
238 #[test]
239 fn composed_styles_use_palette() {
240 let base = Theme::base();
241 assert_eq!(base.fg, Some(Theme::fg()));
242 assert_eq!(base.bg, Some(Theme::bg()));
243 }
244
245 #[test]
246 fn http_method_styles() {
247 let get_style = Theme::http_method("GET");
248 assert_eq!(get_style.fg, Some(Theme::green()));
249
250 let delete_style = Theme::http_method("DELETE");
251 assert_eq!(delete_style.fg, Some(Theme::red()));
252 }
253
254 #[test]
255 fn status_code_styles() {
256 assert_eq!(Theme::status_code(200).fg, Some(Theme::green()));
257 assert_eq!(Theme::status_code(404).fg, Some(Theme::yellow()));
258 assert_eq!(Theme::status_code(500).fg, Some(Theme::red()));
259 }
260
261 #[test]
262 fn const_aliases_match_dark_palette() {
263 assert_eq!(Theme::FG, Palette::DARK.fg);
264 assert_eq!(Theme::BG, Palette::DARK.bg);
265 assert_eq!(Theme::BLUE, Palette::DARK.blue);
266 assert_eq!(Theme::RED, Palette::DARK.red);
267 }
268
269 #[test]
270 fn light_palette_differs_from_dark() {
271 assert_ne!(Palette::DARK.bg, Palette::LIGHT.bg);
272 assert_ne!(Palette::DARK.fg, Palette::LIGHT.fg);
273 assert_ne!(Palette::DARK.blue, Palette::LIGHT.blue);
274 }
275}