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::buffer::Buffer;
22//! # use ratatui::layout::Rect;
23//! # use ratatui::style::Style;
24//! # use ratatui::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::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::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::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::buffer::Buffer;
132/// # use ratatui::layout::Rect;
133/// # use ratatui::style::Style;
134/// # use ratatui::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 VIEW: &'static str = "view";
212}
213
214/// Extension trait for [Style](ratatui::style::Style) that defines
215/// some standard names used by rat-theme/rat-widget
216///
217/// Use as
218/// ```rust
219/// # use ratatui::style::Style;
220/// # use rat_theme4::theme::{SalsaTheme};
221/// # use rat_theme4::{ StyleName, WidgetStyle};
222/// # use rat_theme4::palettes::core::BLACKOUT;
223/// # let theme = SalsaTheme::default();
224///
225/// let s: Style = theme.style(Style::INPUT);
226/// ```
227pub trait StyleName {
228    const LABEL_FG: &'static str = "label-fg";
229    const INPUT: &'static str = "input";
230    const INPUT_FOCUS: &'static str = "text-focus";
231    const INPUT_SELECT: &'static str = "text-select";
232    const FOCUS: &'static str = "focus";
233    const SELECT: &'static str = "select";
234    const DISABLED: &'static str = "disabled";
235    const INVALID: &'static str = "invalid";
236
237    const TITLE: &'static str = "title";
238    const HEADER: &'static str = "header";
239    const FOOTER: &'static str = "footer";
240
241    const HOVER: &'static str = "hover";
242    const SHADOWS: &'static str = "shadows";
243
244    const WEEK_HEADER_FG: &'static str = "week-header-fg";
245    const MONTH_HEADER_FG: &'static str = "month-header-fg";
246
247    const KEY_BINDING: &'static str = "key-binding";
248    const BUTTON_BASE: &'static str = "button-base";
249    const MENU_BASE: &'static str = "menu-base";
250    const STATUS_BASE: &'static str = "status-base";
251
252    const CONTAINER_BASE: &'static str = "container-base";
253    const CONTAINER_BORDER_FG: &'static str = "container-border-fg";
254    const CONTAINER_ARROW_FG: &'static str = "container-arrows-fg";
255
256    const DOCUMENT_BASE: &'static str = "document-base";
257    const DOCUMENT_BORDER_FG: &'static str = "document-border-fg";
258    const DOCUMENT_ARROW_FG: &'static str = "document-arrows-fg";
259
260    const POPUP_BASE: &'static str = "popup-base";
261    const POPUP_BORDER_FG: &'static str = "popup-border-fg";
262    const POPUP_ARROW_FG: &'static str = "popup-arrow-fg";
263
264    const DIALOG_BASE: &'static str = "dialog-base";
265    const DIALOG_BORDER_FG: &'static str = "dialog-border-fg";
266    const DIALOG_ARROW_FG: &'static str = "dialog-arrow-fg";
267}
268impl StyleName for Style {}
269
270///
271/// Extension trait for [Color](ratatui::style::Color) that defines
272/// standard names used by rat-theme to define color-aliases.
273///
274/// Use as
275/// ```rust
276/// # use ratatui::style::{Style, Color};
277/// # use rat_theme4::theme::{SalsaTheme};
278/// # use rat_theme4::RatWidgetColor;
279/// # let theme = SalsaTheme::default();
280///
281/// let c: Color = theme.p.color_alias(Color::LABEL_FG);
282/// ```
283pub trait RatWidgetColor {
284    const LABEL_FG: &'static str = "label.fg";
285    const INPUT_BG: &'static str = "input.bg";
286    const INPUT_FOCUS_BG: &'static str = "input-focus.bg";
287    const INPUT_SELECT_BG: &'static str = "input-select.bg";
288    const FOCUS_BG: &'static str = "focus.bg";
289    const SELECT_BG: &'static str = "select.bg";
290    const DISABLED_BG: &'static str = "disabled.bg";
291    const INVALID_BG: &'static str = "invalid.bg";
292
293    const TITLE_FG: &'static str = "title.fg";
294    const TITLE_BG: &'static str = "title.bg";
295    const HEADER_FG: &'static str = "header.fg";
296    const HEADER_BG: &'static str = "header.bg";
297    const FOOTER_FG: &'static str = "footer.fg";
298    const FOOTER_BG: &'static str = "footer.bg";
299
300    const HOVER_BG: &'static str = "hover.bg";
301    const BUTTON_BASE_BG: &'static str = "button-base.bg";
302    const KEY_BINDING_BG: &'static str = "key-binding.bg";
303    const MENU_BASE_BG: &'static str = "menu-base.bg";
304    const STATUS_BASE_BG: &'static str = "status-base.bg";
305    const SHADOW_BG: &'static str = "shadow.bg";
306
307    const WEEK_HEADER_FG: &'static str = "week-header.fg";
308    const MONTH_HEADER_FG: &'static str = "month-header.fg";
309
310    const CONTAINER_BASE_BG: &'static str = "container-base.bg";
311    const CONTAINER_BORDER_FG: &'static str = "container-border.fg";
312    const CONTAINER_ARROW_FG: &'static str = "container-arrow.fg";
313    const DOCUMENT_BASE_BG: &'static str = "document-base.bg";
314    const DOCUMENT_BORDER_FG: &'static str = "document-border.fg";
315    const DOCUMENT_ARROW_FG: &'static str = "document-arrow.fg";
316    const POPUP_BASE_BG: &'static str = "popup-base.bg";
317    const POPUP_BORDER_FG: &'static str = "popup-border.fg";
318    const POPUP_ARROW_FG: &'static str = "popup-arrow.fg";
319    const DIALOG_BASE_BG: &'static str = "dialog-base.bg";
320    const DIALOG_BORDER_FG: &'static str = "dialog-border.fg";
321    const DIALOG_ARROW_FG: &'static str = "dialog-arrow.fg";
322}
323impl RatWidgetColor for Color {}
324
325static LOG_DEFINES: AtomicBool = AtomicBool::new(false);
326
327/// Log style definition.
328/// May help debugging styling problems ...
329pub fn log_style_define(log: bool) {
330    LOG_DEFINES.store(log, Ordering::Release);
331}
332
333fn is_log_style_define() -> bool {
334    LOG_DEFINES.load(Ordering::Acquire)
335}
336
337/// Create the Theme based on the given Palette.
338#[allow(clippy::result_large_err)]
339pub fn create_palette_theme(pal: Palette) -> Result<SalsaTheme, Palette> {
340    match pal.theme.as_ref() {
341        "Dark" => Ok(themes::create_dark(pal)),
342        "Light" => Ok(themes::create_light(pal)),
343        "Shell" => Ok(themes::create_shell(pal)),
344        _ => Err(pal),
345    }
346}
347
348static THEMES: &[&str] = &[
349    "Imperial",
350    "Black&White",
351    "EverForest",
352    "Embark",
353    "FalconDark",
354    "Gatekeeper",
355    "Material",
356    "Monekai",
357    "Monochrome",
358    "Nord",
359    "Ocean",
360    "OxoCarbon",
361    "Radium",
362    "Reds",
363    "Rust",
364    "Solarized",
365    "Tailwind",
366    "Tundra",
367    "VSCode",
368    //
369    "Imperial Light",
370    "Blossom Light",
371    "EverForest Light",
372    "Gatekeeper Light",
373    "Embark Light",
374    "Rust Light",
375    "SunriseBreeze Light",
376    "Tailwind Light",
377    //
378    "Imperial Shell",
379    "Black&White Shell",
380    "EverForest Shell",
381    "Embark Shell",
382    "Gatekeeper Shell",
383    "Material Shell",
384    "Monekai Shell",
385    "Monochrome Shell",
386    "Nord Shell",
387    "Ocean Shell",
388    "OxoCarbon Shell",
389    "Radium Shell",
390    "Reds Shell",
391    "Rust Shell",
392    "Solarized Shell",
393    "Tailwind Shell",
394    "Tundra Shell",
395    "VSCode Shell",
396    //
397    "Shell",
398    "Blackout",
399    "Fallback",
400];
401
402/// All predefined rat-salsa themes.
403#[deprecated(
404    since = "4.1.0",
405    note = "there is no separation between themes and palettes any more. use salsa_themes()"
406)]
407pub fn salsa_palettes() -> Vec<&'static str> {
408    let mut r = Vec::new();
409    for v in THEMES {
410        r.push(*v);
411    }
412    r
413}
414
415/// Create one of the predefined themes as a Palette.
416///
417/// The available themes can be queried by [salsa_themes].
418///
419/// Known palettes: Imperial, Radium, Tundra, Ocean, Monochrome,
420/// Black&White, Monekai, Solarized, OxoCarbon, EverForest,
421/// Nord, Rust, Material, Tailwind, VSCode, Reds, Blackout,
422/// Shell, Imperial Light, EverForest Light, Tailwind Light,
423/// Rust Light.
424#[deprecated(since = "4.1.0", note = "use create_salsa_palette() instead")]
425pub fn create_palette(name: &str) -> Option<Palette> {
426    create_salsa_palette(name)
427}
428
429/// Create one of the predefined themes as a Palette.
430///
431/// The available themes can be queried by [salsa_themes].
432///
433/// Known palettes: Imperial, Radium, Tundra, Ocean, Monochrome,
434/// Black&White, Monekai, Solarized, OxoCarbon, EverForest,
435/// Nord, Rust, Material, Tailwind, VSCode, Reds, Blackout,
436/// Shell, Imperial Light, EverForest Light, Tailwind Light,
437/// Rust Light.
438pub fn create_salsa_palette(name: &str) -> Option<Palette> {
439    use crate::palettes::core;
440    use crate::palettes::dark;
441    use crate::palettes::light;
442    match name {
443        "Imperial" => Some(dark::IMPERIAL),
444        "Black&White" => Some(dark::BLACK_WHITE),
445        "EverForest" => Some(dark::EVERFOREST),
446        "FalconDark" => Some(dark::FALCON_DARK),
447        "Gatekeeper" => Some(dark::GATEKEEPER),
448        "Embark" => Some(dark::EMBARK),
449        "Material" => Some(dark::MATERIAL),
450        "Monekai" => Some(dark::MONEKAI),
451        "Monochrome" => Some(dark::MONOCHROME),
452        "Nord" => Some(dark::NORD),
453        "Ocean" => Some(dark::OCEAN),
454        "OxoCarbon" => Some(dark::OXOCARBON),
455        "Radium" => Some(dark::RADIUM),
456        "Reds" => Some(dark::REDS),
457        "Rust" => Some(dark::RUST),
458        "Solarized" => Some(dark::SOLARIZED),
459        "Tailwind" => Some(dark::TAILWIND),
460        "Tundra" => Some(dark::TUNDRA),
461        "VSCode" => Some(dark::VSCODE),
462
463        "Imperial Light" => Some(light::IMPERIAL_LIGHT),
464        "Blossom Light" => Some(light::BLOSSOM_LIGHT),
465        "Embark Light" => Some(light::EMBARK_LIGHT),
466        "EverForest Light" => Some(light::EVERFOREST_LIGHT),
467        "Gatekeeper Light" => Some(light::GATEKEEPER_LIGHT),
468        "Rust Light" => Some(light::RUST_LIGHT),
469        "SunriseBreeze Light" => Some(light::SUNRISEBREEZE_LIGHT),
470        "Tailwind Light" => Some(light::TAILWIND_LIGHT),
471
472        "Imperial Shell" => Some(shell::IMPERIAL_SHELL),
473        "Black&White Shell" => Some(shell::BLACK_WHITE_SHELL),
474        "Embark Shell" => Some(shell::EMBARK_SHELL),
475        "EverForest Shell" => Some(shell::EVERFOREST_SHELL),
476        "Gatekeeper Shell" => Some(shell::GATEKEEPER_SHELL),
477        "Material Shell" => Some(shell::MATERIAL_SHELL),
478        "Monekai Shell" => Some(shell::MONEKAI_SHELL),
479        "Monochrome Shell" => Some(shell::MONOCHROME_SHELL),
480        "Nord Shell" => Some(shell::NORD_SHELL),
481        "Ocean Shell" => Some(shell::OCEAN_SHELL),
482        "OxoCarbon Shell" => Some(shell::OXOCARBON_SHELL),
483        "Radium Shell" => Some(shell::RADIUM_SHELL),
484        "Reds Shell" => Some(shell::REDS_SHELL),
485        "Rust Shell" => Some(shell::RUST_SHELL),
486        "Solarized Shell" => Some(shell::SOLARIZED_SHELL),
487        "Tailwind Shell" => Some(shell::TAILWIND_SHELL),
488        "Tundra Shell" => Some(shell::TUNDRA_SHELL),
489        "VSCode Shell" => Some(shell::VSCODE_SHELL),
490
491        "Shell" => Some(core::SHELL),
492        "Blackout" => Some(core::BLACKOUT),
493        "Fallback" => Some(core::FALLBACK),
494        _ => None,
495    }
496}
497
498/// All predefined rat-salsa themes.
499pub fn salsa_themes() -> Vec<&'static str> {
500    let mut r = Vec::new();
501    for v in THEMES {
502        r.push(*v);
503    }
504    r
505}
506
507#[deprecated(since = "4.1.0", note = "use create_salsa_theme() instead")]
508pub fn create_theme(theme_name: &str) -> SalsaTheme {
509    create_salsa_theme(theme_name)
510}
511
512/// Create one of the predefined themes.
513///
514/// The available themes can be queried by [salsa_themes].
515///
516/// Known themes: Imperial Dark, Radium Dark, Tundra Dark,
517/// Ocean Dark, Monochrome Dark, Black&White Dark, Monekai Dark,
518/// Solarized Dark, OxoCarbon Dark, EverForest Dark, Nord Dark,
519/// Rust Dark, Material Dark, Tailwind Dark, VSCode Dark,
520/// Imperial Light, EverForest Light, Tailwind Light, Rust Light,
521/// Imperial Shell, Radium Shell, Tundra Shell, Ocean Shell,
522/// Monochrome Shell, Black&White Shell, Monekai Shell,
523/// Solarized Shell, OxoCarbon Shell, EverForest Shell, Nord Shell,
524/// Rust Shell, Material Shell, Tailwind Shell, VSCode Shell,
525/// Shell, Blackout and Fallback.
526pub fn create_salsa_theme(theme_name: &str) -> SalsaTheme {
527    if let Some(pal) = create_salsa_palette(theme_name) {
528        match pal.theme.as_ref() {
529            "Dark" => themes::create_dark(pal),
530            "Light" => themes::create_light(pal),
531            "Shell" => themes::create_shell(pal),
532            "Fallback" => themes::create_fallback(pal),
533            _ => themes::create_shell(palettes::core::SHELL),
534        }
535    } else {
536        themes::create_shell(palettes::core::SHELL)
537    }
538}