1use ratatui::style::{Color, Modifier, Style};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum ThemeVariant {
13 EverforestDark,
15 EverforestLight,
17}
18
19impl Default for ThemeVariant {
20 fn default() -> Self {
21 Self::EverforestDark
22 }
23}
24
25#[derive(Debug, Clone)]
27pub struct ColorPalette {
28 pub background: Color,
29 pub foreground: Color,
30 pub accent: Color,
31 pub secondary: Color,
32 pub info: Color,
33 pub border: Color,
34 pub selection: Color,
35 pub cursor: Color,
36 pub warning: Color, }
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum Element {
42 Text,
44 Title,
46 Border,
48 Highlight,
50 Accent,
52 Secondary,
54 Info,
56 Background,
58 Active,
60 Inactive,
62 Warning,
64}
65
66#[derive(Debug, Clone)]
68pub struct Theme {
69 variant: ThemeVariant,
70 colors: ColorPalette,
71}
72
73impl Default for Theme {
74 fn default() -> Self {
75 Self::new(ThemeVariant::default())
76 }
77}
78
79impl Theme {
80 pub fn new(variant: ThemeVariant) -> Self {
82 let use_basic_colors = std::env::var("TERM_PROGRAM")
84 .map(|term| term == "Apple_Terminal")
85 .unwrap_or(false);
86
87 let colors = match (variant, use_basic_colors) {
88 (ThemeVariant::EverforestDark, false) => ColorPalette {
89 background: Color::Rgb(45, 53, 59), foreground: Color::Rgb(211, 198, 170), accent: Color::Rgb(167, 192, 128), secondary: Color::Rgb(230, 126, 128), info: Color::Rgb(127, 187, 179), border: Color::Rgb(130, 140, 150), selection: Color::Rgb(64, 72, 78), cursor: Color::Rgb(211, 198, 170), warning: Color::Rgb(219, 188, 127), },
99 (ThemeVariant::EverforestLight, false) => ColorPalette {
100 background: Color::Rgb(253, 246, 227), foreground: Color::Rgb(76, 86, 94), accent: Color::Rgb(141, 161, 1), secondary: Color::Rgb(248, 85, 82), info: Color::Rgb(53, 167, 124), border: Color::Rgb(150, 160, 170), selection: Color::Rgb(243, 236, 217), cursor: Color::Rgb(76, 86, 94), warning: Color::Rgb(207, 131, 44), },
110 (ThemeVariant::EverforestDark, true) => ColorPalette {
112 background: Color::Reset, foreground: Color::White,
114 accent: Color::Green,
115 secondary: Color::Red,
116 info: Color::Cyan,
117 border: Color::Gray, selection: Color::Blue, cursor: Color::White,
120 warning: Color::Yellow,
121 },
122 (ThemeVariant::EverforestLight, true) => ColorPalette {
123 background: Color::Reset, foreground: Color::Black,
125 accent: Color::Green,
126 secondary: Color::Red,
127 info: Color::Blue, border: Color::DarkGray, selection: Color::Yellow, cursor: Color::Black,
131 warning: Color::Magenta, },
133 };
134
135 Self { variant, colors }
136 }
137
138 pub fn variant(&self) -> ThemeVariant {
140 self.variant
141 }
142
143 pub fn colors(&self) -> &ColorPalette {
145 &self.colors
146 }
147
148 pub fn toggle(&mut self) {
150 self.variant = match self.variant {
151 ThemeVariant::EverforestDark => ThemeVariant::EverforestLight,
152 ThemeVariant::EverforestLight => ThemeVariant::EverforestDark,
153 };
154 *self = Self::new(self.variant);
155 }
156
157 pub fn set_variant(&mut self, variant: ThemeVariant) {
159 if self.variant != variant {
160 self.variant = variant;
161 *self = Self::new(self.variant);
162 }
163 }
164
165 pub fn ratatui_style(&self, element: Element) -> Style {
167 match element {
168 Element::Text => Style::default()
169 .fg(self.colors.foreground)
170 .bg(self.colors.background),
171
172 Element::Title => Style::default()
173 .fg(self.colors.accent)
174 .bg(self.colors.background)
175 .add_modifier(Modifier::BOLD),
176
177 Element::Border => Style::default()
178 .fg(self.colors.border)
179 .bg(self.colors.background),
180
181 Element::Highlight => Style::default()
182 .fg(self.colors.foreground)
183 .bg(self.colors.selection)
184 .add_modifier(Modifier::BOLD),
185
186 Element::Accent => Style::default()
187 .fg(self.colors.accent)
188 .bg(self.colors.background)
189 .add_modifier(Modifier::BOLD),
190
191 Element::Secondary => Style::default()
192 .fg(self.colors.secondary)
193 .bg(self.colors.background),
194
195 Element::Info => Style::default()
196 .fg(self.colors.info)
197 .bg(self.colors.background),
198
199 Element::Background => Style::default()
200 .fg(self.colors.foreground)
201 .bg(self.colors.background),
202
203 Element::Active => Style::default()
204 .fg(self.colors.accent)
205 .bg(self.colors.selection)
206 .add_modifier(Modifier::BOLD),
207
208 Element::Inactive => Style::default()
209 .fg(self.colors.border)
210 .bg(self.colors.background),
211
212 Element::Warning => Style::default()
213 .fg(self.colors.warning)
214 .bg(self.colors.background),
215 }
216 }
217
218 pub fn fg_color(&self, element: Element) -> Color {
220 match element {
221 Element::Text | Element::Background => self.colors.foreground,
222 Element::Title | Element::Accent | Element::Active => self.colors.accent,
223 Element::Border | Element::Inactive => self.colors.border,
224 Element::Highlight => self.colors.foreground,
225 Element::Secondary => self.colors.secondary,
226 Element::Info => self.colors.info,
227 Element::Warning => self.colors.warning,
228 }
229 }
230
231 pub fn bg_color(&self, element: Element) -> Color {
233 match element {
234 Element::Highlight | Element::Active => self.colors.selection,
235 _ => self.colors.background,
236 }
237 }
238
239 pub fn title_style(&self) -> Style {
241 self.ratatui_style(Element::Title)
242 }
243
244 pub fn border_style(&self) -> Style {
246 self.ratatui_style(Element::Border)
247 }
248
249 pub fn text_style(&self) -> Style {
251 self.ratatui_style(Element::Text)
252 }
253
254 pub fn highlight_style(&self) -> Style {
256 self.ratatui_style(Element::Highlight)
257 }
258
259 pub fn accent_style(&self) -> Style {
261 self.ratatui_style(Element::Accent)
262 }
263
264 pub fn secondary_style(&self) -> Style {
266 self.ratatui_style(Element::Secondary)
267 }
268
269 pub fn info_style(&self) -> Style {
271 self.ratatui_style(Element::Info)
272 }
273
274 pub fn warning_style(&self) -> Style {
276 self.ratatui_style(Element::Warning)
277 }
278}