rat_theme4/
lib.rs

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