rat_theme4/
lib.rs

1//!
2//! SalsaTheme provides a styling system for ratatui apps.
3//!
4//! It has a simple flat naming scheme.
5//!
6//! But it can store
7//! * [ratatui Style](ratatui::style::Style)
8//! * composite styles as used by [rat-widget](rat_widget).
9//!   eg [CheckboxStyle](rat_widget::checkbox::CheckboxStyle)
10//! * practically anything else.
11//!
12//! ## Naming styles
13//!
14//! * It has an extension trait for [Style](ratatui::style::Style) that
15//!   adds constants for known styles. In the same manner you can add your
16//!   application specific styles and have them with code completion.
17//!
18//! * For [rat-widget](rat_widget) composite style it defines an anchor struct
19//!   [WidgetStyle] that performs the same purpose.
20//!
21//! ## Usage
22//!
23//! ```rust
24//! # use ratatui::buffer::Buffer;
25//! # use ratatui::layout::Rect;
26//! # use ratatui::style::Style;
27//! # use ratatui::widgets::StatefulWidget;
28//! # use rat_theme4::theme::{SalsaTheme};
29//! # use rat_theme4::{StyleName, WidgetStyle};
30//! # use rat_theme4::palettes::core::BLACKOUT;
31//! # use rat_widget::checkbox::{Checkbox, CheckboxState, CheckboxStyle};
32//! # let theme = SalsaTheme::default();
33//! # let area = Rect::default();
34//! # let mut buf = Buffer::default();
35//! # let buf = &mut buf;
36//! # let mut state = CheckboxState::default();
37//!
38//! // ratatui Style
39//! let s: Style = theme.style(Style::SELECT);
40//!
41//! // composite style
42//! Checkbox::new()
43//!     .styles(theme.style(WidgetStyle::CHECKBOX))
44//!     .render(area, buf, &mut state);
45//! ```
46//!
47//! ## Palette
48//!
49//! Palette holds the color definitions and aliases for the
50//! colors. This is the part of the theme that can be persisted.
51//! It can be stored/loaded from file or put into a `static`.
52//!
53//! With [create_palette_theme] the theme can be reconstructed.
54//!
55
56use crate::palette::{Colors, Palette};
57use crate::palettes::shell;
58use crate::theme::SalsaTheme;
59use ratatui::style::{Color, Style};
60use std::borrow::Cow;
61use std::error::Error;
62use std::fmt::{Display, Formatter};
63use std::io;
64use std::io::ErrorKind;
65use std::sync::atomic::{AtomicBool, Ordering};
66
67pub mod palette;
68pub mod theme;
69
70/// Currently shipped palettes.
71pub mod palettes {
72    pub mod core;
73    pub mod dark;
74    pub mod light;
75    pub mod shell;
76}
77
78pub mod themes {
79    mod dark;
80    mod fallback;
81    mod light;
82    mod shell;
83
84    /// Creates a `dark` theme.
85    pub use dark::create_dark;
86    /// Creates a 'light' theme.
87    pub use light::create_light;
88    /// Creates a `shell` theme. This uses the dark palettes,
89    /// but sets almost no backgrounds. Instead, it lets the
90    /// terminal background shine.
91    pub use shell::create_shell;
92
93    /// Create the `fallback` theme.
94    /// This is more for testing widgets than anything else.
95    /// It just uses `Default::default()` for any style.
96    /// This helps to check if a widget is still functional
97    /// if no styling is applied.
98    pub use fallback::create_fallback;
99}
100
101/// Anchor struct for the names of composite styles used
102/// by rat-widget's.
103///
104/// Use as
105/// ```rust
106/// # use ratatui::style::Style;
107/// # use rat_theme4::theme::{SalsaTheme};
108/// # use rat_theme4::{ StyleName, WidgetStyle};
109/// # use rat_theme4::palettes::core::BLACKOUT;
110/// # use rat_widget::checkbox::CheckboxStyle;
111/// # let theme = SalsaTheme::default();
112///
113/// let s: CheckboxStyle = theme.style(WidgetStyle::CHECKBOX);
114/// ```
115/// or more likely
116/// ```rust
117/// # use ratatui::buffer::Buffer;
118/// # use ratatui::layout::Rect;
119/// # use ratatui::style::Style;
120/// # use ratatui::widgets::StatefulWidget;
121/// # use rat_theme4::theme::{SalsaTheme};
122/// # use rat_theme4::{ StyleName, WidgetStyle};
123/// # use rat_theme4::palettes::core::BLACKOUT;
124/// # use rat_widget::checkbox::{Checkbox, CheckboxState, CheckboxStyle};
125/// # let theme = SalsaTheme::default();
126/// # let area = Rect::default();
127/// # let mut buf = Buffer::default();
128/// # let buf = &mut buf;
129/// # let mut state = CheckboxState::default();
130///
131/// Checkbox::new()
132///     .styles(theme.style(WidgetStyle::CHECKBOX))
133///     .render(area, buf, &mut state);
134/// ```
135pub struct WidgetStyle;
136
137impl WidgetStyle {
138    pub const BUTTON: &'static str = "button";
139    pub const CALENDAR: &'static str = "calendar";
140    pub const CHECKBOX: &'static str = "checkbox";
141    pub const CHOICE: &'static str = "choice";
142    pub const CLIPPER: &'static str = "clipper";
143    #[cfg(feature = "color_input")]
144    pub const COLOR_INPUT: &'static str = "color-input";
145    pub const COMBOBOX: &'static str = "combobox";
146    pub const DIALOG_FRAME: &'static str = "dialog-frame";
147    pub const FILE_DIALOG: &'static str = "file-dialog";
148    pub const FORM: &'static str = "form";
149    pub const LINE_NR: &'static str = "line-nr";
150    pub const LIST: &'static str = "list";
151    pub const MENU: &'static str = "menu";
152    pub const MONTH: &'static str = "month";
153    pub const MSG_DIALOG: &'static str = "msg-dialog";
154    pub const PARAGRAPH: &'static str = "paragraph";
155    pub const RADIO: &'static str = "radio";
156    pub const SCROLL: &'static str = "scroll";
157    pub const SCROLL_DIALOG: &'static str = "scroll.dialog";
158    pub const SCROLL_POPUP: &'static str = "scroll.popup";
159    pub const SHADOW: &'static str = "shadow";
160    pub const SLIDER: &'static str = "slider";
161    pub const SPLIT: &'static str = "split";
162    pub const STATUSLINE: &'static str = "statusline";
163    pub const TABBED: &'static str = "tabbed";
164    pub const TABLE: &'static str = "table";
165    pub const TEXT: &'static str = "text";
166    pub const TEXTAREA: &'static str = "textarea";
167    pub const TEXTVIEW: &'static str = "textview";
168    pub const VIEW: &'static str = "view";
169}
170
171/// Extension trait for [Style](ratatui::style::Style) that defines
172/// some standard names used by rat-theme/rat-widget
173///
174/// Use as
175/// ```rust
176/// # use ratatui::style::Style;
177/// # use rat_theme4::theme::{SalsaTheme};
178/// # use rat_theme4::{ StyleName, WidgetStyle};
179/// # use rat_theme4::palettes::core::BLACKOUT;
180/// # let theme = SalsaTheme::default();
181///
182/// let s: Style = theme.style(Style::INPUT);
183/// ```
184pub trait StyleName {
185    const LABEL_FG: &'static str = "label-fg";
186    const INPUT: &'static str = "input";
187    const INPUT_FOCUS: &'static str = "text-focus";
188    const INPUT_SELECT: &'static str = "text-select";
189    const FOCUS: &'static str = "focus";
190    const SELECT: &'static str = "select";
191    const DISABLED: &'static str = "disabled";
192    const INVALID: &'static str = "invalid";
193
194    const TITLE: &'static str = "title";
195    const HEADER: &'static str = "header";
196    const FOOTER: &'static str = "footer";
197
198    const HOVER: &'static str = "hover";
199    const SHADOWS: &'static str = "shadows";
200
201    const WEEK_HEADER_FG: &'static str = "week-header-fg";
202    const MONTH_HEADER_FG: &'static str = "month-header-fg";
203
204    const KEY_BINDING: &'static str = "key-binding";
205    const BUTTON_BASE: &'static str = "button-base";
206    const MENU_BASE: &'static str = "menu-base";
207    const STATUS_BASE: &'static str = "status-base";
208
209    const CONTAINER_BASE: &'static str = "container-base";
210    const CONTAINER_BORDER_FG: &'static str = "container-border-fg";
211    const CONTAINER_ARROW_FG: &'static str = "container-arrows-fg";
212
213    const DOCUMENT_BASE: &'static str = "document-base";
214    const DOCUMENT_BORDER_FG: &'static str = "document-border-fg";
215    const DOCUMENT_ARROW_FG: &'static str = "document-arrows-fg";
216
217    const POPUP_BASE: &'static str = "popup-base";
218    const POPUP_BORDER_FG: &'static str = "popup-border-fg";
219    const POPUP_ARROW_FG: &'static str = "popup-arrow-fg";
220
221    const DIALOG_BASE: &'static str = "dialog-base";
222    const DIALOG_BORDER_FG: &'static str = "dialog-border-fg";
223    const DIALOG_ARROW_FG: &'static str = "dialog-arrow-fg";
224}
225impl StyleName for Style {}
226
227///
228/// Extension trait for [Color](ratatui::style::Color) that defines
229/// standard names used by rat-theme to define color-aliases.
230///
231/// Use as
232/// ```rust
233/// # use ratatui::style::{Style, Color};
234/// # use rat_theme4::theme::{SalsaTheme};
235/// # use rat_theme4::RatWidgetColor;
236/// # let theme = SalsaTheme::default();
237///
238/// let c: Color = theme.p.color_alias(Color::LABEL_FG);
239/// ```
240pub trait RatWidgetColor {
241    const LABEL_FG: &'static str = "label.fg";
242    const INPUT_BG: &'static str = "input.bg";
243    const INPUT_FOCUS_BG: &'static str = "input-focus.bg";
244    const INPUT_SELECT_BG: &'static str = "input-select.bg";
245    const FOCUS_BG: &'static str = "focus.bg";
246    const SELECT_BG: &'static str = "select.bg";
247    const DISABLED_BG: &'static str = "disabled.bg";
248    const INVALID_BG: &'static str = "invalid.bg";
249
250    const TITLE_FG: &'static str = "title.fg";
251    const TITLE_BG: &'static str = "title.bg";
252    const HEADER_FG: &'static str = "header.fg";
253    const HEADER_BG: &'static str = "header.bg";
254    const FOOTER_FG: &'static str = "footer.fg";
255    const FOOTER_BG: &'static str = "footer.bg";
256
257    const HOVER_BG: &'static str = "hover.bg";
258    const BUTTON_BASE_BG: &'static str = "button-base.bg";
259    const KEY_BINDING_BG: &'static str = "key-binding.bg";
260    const MENU_BASE_BG: &'static str = "menu-base.bg";
261    const STATUS_BASE_BG: &'static str = "status-base.bg";
262    const SHADOW_BG: &'static str = "shadow.bg";
263
264    const WEEK_HEADER_FG: &'static str = "week-header.fg";
265    const MONTH_HEADER_FG: &'static str = "month-header.fg";
266
267    const CONTAINER_BASE_BG: &'static str = "container-base.bg";
268    const CONTAINER_BORDER_FG: &'static str = "container-border.fg";
269    const CONTAINER_ARROW_FG: &'static str = "container-arrow.fg";
270    const DOCUMENT_BASE_BG: &'static str = "document-base.bg";
271    const DOCUMENT_BORDER_FG: &'static str = "document-border.fg";
272    const DOCUMENT_ARROW_FG: &'static str = "document-arrow.fg";
273    const POPUP_BASE_BG: &'static str = "popup-base.bg";
274    const POPUP_BORDER_FG: &'static str = "popup-border.fg";
275    const POPUP_ARROW_FG: &'static str = "popup-arrow.fg";
276    const DIALOG_BASE_BG: &'static str = "dialog-base.bg";
277    const DIALOG_BORDER_FG: &'static str = "dialog-border.fg";
278    const DIALOG_ARROW_FG: &'static str = "dialog-arrow.fg";
279}
280impl RatWidgetColor for Color {}
281
282static LOG_DEFINES: AtomicBool = AtomicBool::new(false);
283
284/// Log style definition.
285/// May help debugging styling problems ...
286pub fn log_style_define(log: bool) {
287    LOG_DEFINES.store(log, Ordering::Release);
288}
289
290fn is_log_style_define() -> bool {
291    LOG_DEFINES.load(Ordering::Acquire)
292}
293
294#[derive(Debug)]
295pub struct LoadPaletteErr(u8);
296
297impl Display for LoadPaletteErr {
298    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
299        write!(f, "load palette failed: {}", self.0)
300    }
301}
302
303impl Error for LoadPaletteErr {}
304
305/// Stora a Palette as a .pal file.
306pub fn store_palette(pal: &Palette, mut buf: impl io::Write) -> Result<(), io::Error> {
307    writeln!(buf, "[theme]")?;
308    writeln!(buf, "name={}", pal.theme_name)?;
309    writeln!(buf, "theme={}", pal.theme)?;
310    writeln!(buf, "")?;
311    writeln!(buf, "[palette]")?;
312    writeln!(buf, "name={}", pal.name)?;
313    writeln!(buf, "docs={}", pal.doc.replace('\n', "\\n"))?;
314    writeln!(buf, "generator={}", pal.generator)?;
315    writeln!(buf, "")?;
316    writeln!(buf, "[color]")?;
317    for c in Colors::array() {
318        writeln!(
319            buf,
320            "{}={}, {}",
321            *c, pal.color[*c as usize][0], pal.color[*c as usize][3]
322        )?;
323    }
324    writeln!(buf, "")?;
325    writeln!(buf, "[reference]")?;
326    for (r, i) in pal.aliased.as_ref() {
327        writeln!(buf, "{}={}", r, i)?;
328    }
329    Ok(())
330}
331
332/// Load a .pal file as a Palette.
333pub fn load_palette(mut r: impl io::Read) -> Result<Palette, io::Error> {
334    let mut buf = String::new();
335    r.read_to_string(&mut buf)?;
336
337    enum S {
338        Start,
339        Theme,
340        Palette,
341        Color,
342        Reference,
343        Fail(u8),
344    }
345
346    let mut pal = Palette::default();
347    let mut dark = 63u8;
348
349    let mut state = S::Start;
350    'm: for l in buf.lines() {
351        let l = l.trim();
352        match state {
353            S::Start => {
354                if l == "[theme]" {
355                    state = S::Theme;
356                } else if l == "[palette]" {
357                    state = S::Palette;
358                } else {
359                    state = S::Fail(1);
360                    break 'm;
361                }
362            }
363            S::Theme => {
364                if l == "[palette]" {
365                    state = S::Palette;
366                } else if l.is_empty() || l.starts_with("#") {
367                    // ok
368                } else if l.starts_with("name") {
369                    if let Some(s) = l.split('=').nth(1) {
370                        pal.theme_name = Cow::Owned(s.trim().to_string());
371                    }
372                } else if l.starts_with("theme") {
373                    if let Some(s) = l.split('=').nth(1) {
374                        pal.theme = Cow::Owned(s.trim().to_string());
375                    }
376                } else {
377                    state = S::Fail(2);
378                    break 'm;
379                }
380            }
381            S::Palette => {
382                if l == "[color]" {
383                    state = S::Color;
384                } else if l.is_empty() || l.starts_with("#") {
385                    // ok
386                } else if l.starts_with("name") {
387                    if let Some(s) = l.split('=').nth(1) {
388                        pal.name = Cow::Owned(s.trim().to_string());
389                    }
390                } else if l.starts_with("docs") {
391                    if let Some(s) = l.split('=').nth(1) {
392                        let doc = s.trim().replace("\\n", "\n");
393                        pal.doc = Cow::Owned(doc);
394                    }
395                } else if l.starts_with("generator") {
396                    if let Some(s) = l.split('=').nth(1) {
397                        pal.generator = Cow::Owned(s.trim().to_string());
398                        if s.starts_with("light-dark") {
399                            if let Some(s) = l.split(':').nth(1) {
400                                dark = s.trim().parse::<u8>().unwrap_or(63);
401                            }
402                        }
403                    }
404                } else if l.starts_with("dark") {
405                    if let Some(s) = l.split('=').nth(1) {
406                        if let Ok(v) = s.trim().parse::<u8>() {
407                            dark = v;
408                        } else {
409                            // skip
410                        }
411                    }
412                } else {
413                    state = S::Fail(3);
414                    break 'm;
415                }
416            }
417            S::Color => {
418                if l == "[reference]" {
419                    state = S::Reference;
420                } else if l.is_empty() || l.starts_with("#") {
421                    // ok
422                } else {
423                    let mut kv = l.split('=');
424                    let cn = if let Some(v) = kv.next() {
425                        let Ok(c) = v.trim().parse::<palette::Colors>() else {
426                            state = S::Fail(4);
427                            break 'm;
428                        };
429                        c
430                    } else {
431                        state = S::Fail(5);
432                        break 'm;
433                    };
434                    let (c0, c3) = if let Some(v) = kv.next() {
435                        let mut vv = v.split(',');
436                        let c0 = if let Some(v) = vv.next() {
437                            let Ok(v) = v.trim().parse::<Color>() else {
438                                state = S::Fail(6);
439                                break 'm;
440                            };
441                            v
442                        } else {
443                            state = S::Fail(7);
444                            break 'm;
445                        };
446                        let c3 = if let Some(v) = vv.next() {
447                            let Ok(v) = v.trim().parse::<Color>() else {
448                                state = S::Fail(8);
449                                break 'm;
450                            };
451                            v
452                        } else {
453                            state = S::Fail(9);
454                            break 'm;
455                        };
456                        (c0, c3)
457                    } else {
458                        state = S::Fail(10);
459                        break 'm;
460                    };
461
462                    if cn == Colors::TextLight || cn == Colors::TextDark {
463                        pal.color[cn as usize] =
464                            Palette::interpolatec2(c0, c3, Color::default(), Color::default())
465                    } else {
466                        pal.color[cn as usize] = Palette::interpolatec(c0, c3, dark);
467                    }
468                }
469            }
470            S::Reference => {
471                let mut kv = l.split('=');
472                let rn = if let Some(v) = kv.next() {
473                    v
474                } else {
475                    state = S::Fail(11);
476                    break 'm;
477                };
478                let ci = if let Some(v) = kv.next() {
479                    if let Ok(ci) = v.parse::<palette::ColorIdx>() {
480                        ci
481                    } else {
482                        state = S::Fail(12);
483                        break 'm;
484                    }
485                } else {
486                    state = S::Fail(13);
487                    break 'm;
488                };
489                pal.add_aliased(rn, ci);
490            }
491            S::Fail(_) => {
492                unreachable!()
493            }
494        }
495    }
496
497    match state {
498        S::Fail(n) => Err(io::Error::new(ErrorKind::Other, LoadPaletteErr(n))),
499        S::Start => Err(io::Error::new(ErrorKind::Other, LoadPaletteErr(100))),
500        S::Theme => Err(io::Error::new(ErrorKind::Other, LoadPaletteErr(101))),
501        S::Palette => Err(io::Error::new(ErrorKind::Other, LoadPaletteErr(102))),
502        S::Color | S::Reference => Ok(pal),
503    }
504}
505
506/// Create the Theme based on the given Palette.
507pub fn create_palette_theme(pal: Palette) -> Result<SalsaTheme, Palette> {
508    match pal.theme.as_ref() {
509        "Dark" => Ok(themes::create_dark(pal)),
510        "Light" => Ok(themes::create_light(pal)),
511        "Shell" => Ok(themes::create_shell(pal)),
512        _ => Err(pal),
513    }
514}
515
516static THEMES: &'static [&'static str] = &[
517    "Imperial",
518    "Radium",
519    "Tundra",
520    "Ocean",
521    "Monochrome",
522    "Black&White",
523    "Monekai",
524    "Solarized",
525    "OxoCarbon",
526    "EverForest",
527    "Nord",
528    "Rust",
529    "Material",
530    "Tailwind",
531    "VSCode",
532    "Reds",
533    //
534    "Imperial Light",
535    "EverForest Light",
536    "Tailwind Light",
537    "Rust Light",
538    "SunriseBreeze Light",
539    //
540    "Imperial Shell",
541    "Radium Shell",
542    "Tundra Shell",
543    "Ocean Shell",
544    "Monochrome Shell",
545    "Black&White Shell",
546    "Monekai Shell",
547    "Solarized Shell",
548    "OxoCarbon Shell",
549    "EverForest Shell",
550    "Nord Shell",
551    "Rust Shell",
552    "Material Shell",
553    "Tailwind Shell",
554    "VSCode Shell",
555    "Reds Shell",
556    //
557    "Shell",
558    "Blackout",
559    "Fallback",
560];
561
562/// All predefined rat-salsa themes.
563#[deprecated(
564    since = "4.1.0",
565    note = "there is no separation between themes and palettes any more. use salsa_themes()"
566)]
567pub fn salsa_palettes() -> Vec<&'static str> {
568    let mut r = Vec::new();
569    for v in THEMES {
570        r.push(*v);
571    }
572    r
573}
574
575/// Create one of the predefined themes as a Palette.
576///
577/// The available themes can be queried by [salsa_themes].
578///
579/// Known palettes: Imperial, Radium, Tundra, Ocean, Monochrome,
580/// Black&White, Monekai, Solarized, OxoCarbon, EverForest,
581/// Nord, Rust, Material, Tailwind, VSCode, Reds, Blackout,
582/// Shell, Imperial Light, EverForest Light, Tailwind Light,
583/// Rust Light.
584#[deprecated(since = "4.1.0", note = "use create_salsa_palette() instead")]
585pub fn create_palette(name: &str) -> Option<Palette> {
586    create_salsa_palette(name)
587}
588
589/// Create one of the predefined themes as a Palette.
590///
591/// The available themes can be queried by [salsa_themes].
592///
593/// Known palettes: Imperial, Radium, Tundra, Ocean, Monochrome,
594/// Black&White, Monekai, Solarized, OxoCarbon, EverForest,
595/// Nord, Rust, Material, Tailwind, VSCode, Reds, Blackout,
596/// Shell, Imperial Light, EverForest Light, Tailwind Light,
597/// Rust Light.
598pub fn create_salsa_palette(name: &str) -> Option<Palette> {
599    use crate::palettes::core;
600    use crate::palettes::dark;
601    use crate::palettes::light;
602    match name {
603        "Imperial" => Some(dark::IMPERIAL),
604        "Radium" => Some(dark::RADIUM),
605        "Tundra" => Some(dark::TUNDRA),
606        "Ocean" => Some(dark::OCEAN),
607        "Monochrome" => Some(dark::MONOCHROME),
608        "Black&White" => Some(dark::BLACK_WHITE),
609        "Monekai" => Some(dark::MONEKAI),
610        "Solarized" => Some(dark::SOLARIZED),
611        "OxoCarbon" => Some(dark::OXOCARBON),
612        "EverForest" => Some(dark::EVERFOREST),
613        "Nord" => Some(dark::NORD),
614        "Rust" => Some(dark::RUST),
615        "Material" => Some(dark::MATERIAL),
616        "Tailwind" => Some(dark::TAILWIND),
617        "VSCode" => Some(dark::VSCODE),
618        "Reds" => Some(dark::REDS),
619
620        "Imperial Light" => Some(light::IMPERIAL_LIGHT),
621        "EverForest Light" => Some(light::EVERFOREST_LIGHT),
622        "Tailwind Light" => Some(light::TAILWIND_LIGHT),
623        "Rust Light" => Some(light::RUST_LIGHT),
624        "SunriseBreeze Light" => Some(light::SUNRISEBREEZE_LIGHT),
625
626        "Imperial Shell" => Some(shell::IMPERIAL_SHELL),
627        "Radium Shell" => Some(shell::RADIUM_SHELL),
628        "Tundra Shell" => Some(shell::TUNDRA_SHELL),
629        "Ocean Shell" => Some(shell::OCEAN_SHELL),
630        "Monochrome Shell" => Some(shell::MONOCHROME_SHELL),
631        "Black&White Shell" => Some(shell::BLACK_WHITE_SHELL),
632        "Monekai Shell" => Some(shell::MONEKAI_SHELL),
633        "Solarized Shell" => Some(shell::SOLARIZED_SHELL),
634        "OxoCarbon Shell" => Some(shell::OXOCARBON_SHELL),
635        "EverForest Shell" => Some(shell::EVERFOREST_SHELL),
636        "Nord Shell" => Some(shell::NORD_SHELL),
637        "Rust Shell" => Some(shell::RUST_SHELL),
638        "Material Shell" => Some(shell::MATERIAL_SHELL),
639        "Tailwind Shell" => Some(shell::TAILWIND_SHELL),
640        "VSCode Shell" => Some(shell::VSCODE_SHELL),
641        "Reds Shell" => Some(shell::REDS_SHELL),
642
643        "Shell" => Some(core::SHELL),
644        "Blackout" => Some(core::BLACKOUT),
645        "Fallback" => Some(core::FALLBACK),
646        _ => None,
647    }
648}
649
650/// All predefined rat-salsa themes.
651pub fn salsa_themes() -> Vec<&'static str> {
652    let mut r = Vec::new();
653    for v in THEMES {
654        r.push(*v);
655    }
656    r
657}
658
659#[deprecated(since = "4.1.0", note = "use create_salsa_theme() instead")]
660pub fn create_theme(theme_name: &str) -> SalsaTheme {
661    create_salsa_theme(theme_name)
662}
663
664/// Create one of the predefined themes.
665///
666/// The available themes can be queried by [salsa_themes].
667///
668/// Known themes: Imperial Dark, Radium Dark, Tundra Dark,
669/// Ocean Dark, Monochrome Dark, Black&White Dark, Monekai Dark,
670/// Solarized Dark, OxoCarbon Dark, EverForest Dark, Nord Dark,
671/// Rust Dark, Material Dark, Tailwind Dark, VSCode Dark,
672/// Imperial Light, EverForest Light, Tailwind Light, Rust Light,
673/// Imperial Shell, Radium Shell, Tundra Shell, Ocean Shell,
674/// Monochrome Shell, Black&White Shell, Monekai Shell,
675/// Solarized Shell, OxoCarbon Shell, EverForest Shell, Nord Shell,
676/// Rust Shell, Material Shell, Tailwind Shell, VSCode Shell,
677/// Shell, Blackout and Fallback.
678pub fn create_salsa_theme(theme_name: &str) -> SalsaTheme {
679    if let Some(pal) = create_salsa_palette(theme_name) {
680        match pal.theme.as_ref() {
681            "Dark" => themes::create_dark(pal),
682            "Light" => themes::create_light(pal),
683            "Shell" => themes::create_shell(pal),
684            "Fallback" => themes::create_fallback(pal),
685            _ => themes::create_shell(palettes::core::SHELL),
686        }
687    } else {
688        themes::create_shell(palettes::core::SHELL)
689    }
690}