1use ratatui::style::{Color, Style};
48use std::collections::HashMap;
49use std::sync::OnceLock;
50use std::sync::atomic::{AtomicBool, Ordering};
51
52pub mod palette;
53pub mod theme;
54
55pub mod palettes {
57 pub mod core;
58 pub mod dark;
59 pub mod light;
60}
61
62pub mod themes {
63 mod core;
64 mod dark;
65 mod fallback;
66 mod shell;
67
68 pub use core::create_core;
71 pub use dark::create_dark;
73 pub use fallback::create_fallback;
79 pub use shell::create_shell;
83}
84
85pub struct WidgetStyle;
120
121impl WidgetStyle {
122 pub const BUTTON: &'static str = "button";
123 pub const CALENDAR: &'static str = "calendar";
124 pub const CHECKBOX: &'static str = "checkbox";
125 pub const CHOICE: &'static str = "choice";
126 pub const CLIPPER: &'static str = "clipper";
127 pub const COLOR_INPUT: &'static str = "color-input";
128 pub const COMBOBOX: &'static str = "combobox";
129 pub const DIALOG_FRAME: &'static str = "dialog-frame";
130 pub const FILE_DIALOG: &'static str = "file-dialog";
131 pub const FORM: &'static str = "form";
132 pub const LINE_NR: &'static str = "line-nr";
133 pub const LIST: &'static str = "list";
134 pub const MENU: &'static str = "menu";
135 pub const MONTH: &'static str = "month";
136 pub const MSG_DIALOG: &'static str = "msg-dialog";
137 pub const PARAGRAPH: &'static str = "paragraph";
138 pub const RADIO: &'static str = "radio";
139 pub const SCROLL: &'static str = "scroll";
140 pub const SCROLL_DIALOG: &'static str = "scroll.dialog";
141 pub const SCROLL_POPUP: &'static str = "scroll.popup";
142 pub const SHADOW: &'static str = "shadow";
143 pub const SLIDER: &'static str = "slider";
144 pub const SPLIT: &'static str = "split";
145 pub const STATUSLINE: &'static str = "statusline";
146 pub const TABBED: &'static str = "tabbed";
147 pub const TABLE: &'static str = "table";
148 pub const TEXT: &'static str = "text";
149 pub const TEXTAREA: &'static str = "textarea";
150 pub const TEXTVIEW: &'static str = "textview";
151 pub const VIEW: &'static str = "view";
152}
153
154pub trait StyleName {
168 const LABEL_FG: &'static str = "label-fg";
169 const INPUT: &'static str = "input";
170 const FOCUS: &'static str = "focus";
171 const SELECT: &'static str = "select";
172 const DISABLED: &'static str = "disabled";
173 const INVALID: &'static str = "invalid";
174 const HOVER: &'static str = "hover";
175 const TITLE: &'static str = "title";
176 const HEADER: &'static str = "header";
177 const FOOTER: &'static str = "footer";
178 const SHADOWS: &'static str = "shadows";
179 const WEEK_HEADER_FG: &'static str = "week-header-fg";
180 const MONTH_HEADER_FG: &'static str = "month-header-fg";
181 const TEXT_FOCUS: &'static str = "text-focus";
182 const TEXT_SELECT: &'static str = "text-select";
183 const KEY_BINDING: &'static str = "key-binding";
184 const BUTTON_BASE: &'static str = "button-base";
185 const MENU_BASE: &'static str = "menu-base";
186 const STATUS_BASE: &'static str = "status-base";
187
188 const CONTAINER_BASE: &'static str = "container-base";
189 const CONTAINER_BORDER_FG: &'static str = "container-border-fg";
190 const CONTAINER_ARROW_FG: &'static str = "container-arrows-fg";
191
192 const POPUP_BASE: &'static str = "popup-base";
193 const POPUP_BORDER_FG: &'static str = "popup-border-fg";
194 const POPUP_ARROW_FG: &'static str = "popup-arrow-fg";
195
196 const DIALOG_BASE: &'static str = "dialog-base";
197 const DIALOG_BORDER_FG: &'static str = "dialog-border-fg";
198 const DIALOG_ARROW_FG: &'static str = "dialog-arrow-fg";
199}
200impl StyleName for Style {}
201
202pub trait RatWidgetColor {
216 const LABEL_FG: &'static str = "label.fg";
217 const INPUT_BG: &'static str = "input.bg";
218 const FOCUS_BG: &'static str = "focus.bg";
219 const SELECT_BG: &'static str = "select.bg";
220 const DISABLED_BG: &'static str = "disabled.bg";
221 const INVALID_BG: &'static str = "invalid.bg";
222 const HOVER_BG: &'static str = "hover.bg";
223 const TITLE_FG: &'static str = "title.fg";
224 const TITLE_BG: &'static str = "title.bg";
225 const HEADER_FG: &'static str = "header.fg";
226 const HEADER_BG: &'static str = "header.bg";
227 const FOOTER_FG: &'static str = "footer.fg";
228 const FOOTER_BG: &'static str = "footer.bg";
229 const SHADOW_BG: &'static str = "shadow.bg";
230 const WEEK_HEADER_FG: &'static str = "week-header.fg";
231 const MONTH_HEADER_FG: &'static str = "month-header.fg";
232 const TEXT_FOCUS_BG: &'static str = "text-focus.bg";
233 const TEXT_SELECT_BG: &'static str = "text-select.bg";
234 const BUTTON_BASE_BG: &'static str = "button-base.bg";
235 const MENU_BASE_BG: &'static str = "menu-base.bg";
236 const KEY_BINDING_BG: &'static str = "key-binding.bg";
237 const STATUS_BASE_BG: &'static str = "status-base.bg";
238 const CONTAINER_BASE_BG: &'static str = "container-base.bg";
239 const CONTAINER_BORDER_FG: &'static str = "container-border.fg";
240 const CONTAINER_ARROW_FG: &'static str = "container-arrow.fg";
241 const POPUP_BASE_BG: &'static str = "popup-base.bg";
242 const POPUP_BORDER_FG: &'static str = "popup-border.fg";
243 const POPUP_ARROW_FG: &'static str = "popup-arrow.fg";
244 const DIALOG_BASE_BG: &'static str = "dialog-base.bg";
245 const DIALOG_BORDER_FG: &'static str = "dialog-border.fg";
246 const DIALOG_ARROW_FG: &'static str = "dialog-arrow.fg";
247}
248impl RatWidgetColor for Color {}
249
250static LOG_DEFINES: AtomicBool = AtomicBool::new(false);
251
252pub fn log_style_define(log: bool) {
255 LOG_DEFINES.store(log, Ordering::Release);
256}
257
258fn is_log_style_define() -> bool {
259 LOG_DEFINES.load(Ordering::Acquire)
260}
261
262const PALETTE_DEF: &str = include_str!("themes.ini");
263
264#[derive(Debug)]
265struct Def {
266 palette: Vec<&'static str>,
267 theme: Vec<&'static str>,
268 theme_init: HashMap<&'static str, (&'static str, &'static str)>,
269}
270
271static THEMES: OnceLock<Def> = OnceLock::new();
272
273fn init_themes() -> Def {
274 let mut palette = Vec::new();
275 let mut theme = Vec::new();
276 let mut theme_init = HashMap::new();
277
278 for l in PALETTE_DEF.lines() {
279 if !l.contains('=') {
280 continue;
281 }
282
283 let mut it = l.split(['=', ',']);
284 let Some(name) = it.next() else {
285 continue;
286 };
287 let Some(cat) = it.next() else {
288 continue;
289 };
290 let Some(pal) = it.next() else {
291 continue;
292 };
293 let name = name.trim();
294 let cat = cat.trim();
295 let pal = pal.trim();
296
297 if pal != "None" {
298 if !palette.contains(&pal) {
299 palette.push(pal);
300 }
301 }
302 if name != "Blackout" && name != "Fallback" {
303 if !theme.contains(&name) {
304 theme.push(name);
305 }
306 }
307 theme_init.insert(name, (cat, pal));
308 }
309
310 let d = Def {
311 palette,
312 theme,
313 theme_init,
314 };
315 d
316}
317
318pub fn salsa_palettes() -> Vec<&'static str> {
320 let themes = THEMES.get_or_init(init_themes);
321 themes.palette.clone()
322}
323
324pub fn create_palette(name: &str) -> Option<palette::Palette> {
334 use crate::palettes::core;
335 use crate::palettes::dark;
336 use crate::palettes::light;
337 match name {
338 "Imperial" => Some(dark::IMPERIAL),
339 "Radium" => Some(dark::RADIUM),
340 "Tundra" => Some(dark::TUNDRA),
341 "Ocean" => Some(dark::OCEAN),
342 "Monochrome" => Some(dark::MONOCHROME),
343 "Black&White" => Some(dark::BLACK_WHITE),
344 "Monekai" => Some(dark::MONEKAI),
345 "Solarized" => Some(dark::SOLARIZED),
346 "OxoCarbon" => Some(dark::OXOCARBON),
347 "EverForest" => Some(dark::EVERFOREST),
348 "Nord" => Some(dark::NORD),
349 "Rust" => Some(dark::RUST),
350 "Material" => Some(dark::MATERIAL),
351 "Tailwind" => Some(dark::TAILWIND),
352 "VSCode" => Some(dark::VSCODE),
353 "Reds" => Some(dark::REDS),
354 "Blackout" => Some(dark::BLACKOUT),
355 "Shell" => Some(core::SHELL),
356 "Imperial Light" => Some(light::IMPERIAL),
357 "EverForest Light" => Some(light::EVERFOREST),
358 "Tailwind Light" => Some(light::TAILWIND),
359 "Rust Light" => Some(light::RUST),
360 _ => None,
361 }
362}
363
364pub fn salsa_themes() -> Vec<&'static str> {
366 let themes = THEMES.get_or_init(init_themes);
367 themes.theme.clone()
368}
369
370pub fn create_theme(theme: &str) -> theme::SalsaTheme {
385 let themes = THEMES.get_or_init(init_themes);
386 let Some(def) = themes.theme_init.get(&theme) else {
387 if cfg!(debug_assertions) {
388 panic!("no theme {:?}", theme);
389 } else {
390 return themes::create_core(theme);
391 }
392 };
393 match def {
394 ("dark", p) => {
395 let Some(pal) = create_palette(*p) else {
396 if cfg!(debug_assertions) {
397 panic!("no palette {:?}", *p);
398 } else {
399 return themes::create_core(theme);
400 }
401 };
402 themes::create_dark(theme, pal)
403 }
404 ("light", p) => {
405 let Some(pal) = create_palette(*p) else {
406 if cfg!(debug_assertions) {
407 panic!("no palette {:?}", *p);
408 } else {
409 return themes::create_core(theme);
410 }
411 };
412 themes::create_dark(theme, pal)
415 }
416 ("shell", p) => {
417 let Some(pal) = create_palette(*p) else {
418 if cfg!(debug_assertions) {
419 panic!("no palette {:?}", *p);
420 } else {
421 return themes::create_core(theme);
422 }
423 };
424 themes::create_shell(theme, pal)
425 }
426 ("core", _) => themes::create_core(theme),
427 ("blackout", _) => themes::create_dark(theme, palettes::dark::BLACKOUT),
428 ("fallback", _) => themes::create_fallback(theme, palettes::dark::REDS),
429 _ => {
430 if cfg!(debug_assertions) {
431 panic!("no theme {:?}", theme);
432 } else {
433 themes::create_core(theme)
434 }
435 }
436 }
437}