mod color_utils;
pub mod css;
mod palettes;
pub mod syntect;
use ratatui::style::Color;
use std::cell::RefCell;
pub use color_utils::{
adjust_surface_rgb, color_rgb_to_hex6, color_to_hex6, color_to_hsl_token, color_to_osc11_hex8,
color_to_osc11_rgba_payload, darken_rgb, lighten_rgb, rgb_euclidean_sq, rgb_to_hex6,
rgb_to_hsl_token, rgb_to_osc11_hex8, rgb_to_osc11_rgba_payload, rgb_u8_to_hsl,
};
pub use css::{ThemeCss, tokens_for_theme_name, tokens_from_palette};
pub use palettes::{DEFAULT_COLORS, OBLIVION_INK, theme_ordered_list, theme_selector_entries};
pub use syntect::{
CodeThemeKeys, SYNTECT_DEFAULT_KEYS, SYNTECT_EXTRA_KEYS, SYNTECT_THEME_KEYS,
resolve_syntect_key, theme_for_key, theme_for_palette,
};
const SWATCH_LIGHTEN: f32 = 0.2;
const SWATCH_LIGHTEN_DARK_ON_LIGHT_POPUP: f32 = 0.38;
const SWATCH_LIGHT_THEME_TEXT: f32 = 0.6;
#[must_use]
pub fn theme_selector_swatch(theme: &Palette, popup_theme: &Palette) -> Color {
match theme.appearance {
Appearance::Light => lighten_rgb(theme.swatch, SWATCH_LIGHT_THEME_TEXT),
Appearance::Dark => {
let pct = if popup_theme.appearance == Appearance::Light {
SWATCH_LIGHTEN_DARK_ON_LIGHT_POPUP
} else {
SWATCH_LIGHTEN
};
adjust_surface_rgb(theme.swatch, pct, theme.appearance)
}
}
}
#[must_use]
pub fn theme_selector_swatch_token(theme: &Palette, popup_theme: &Palette) -> String {
color_to_hsl_token(theme_selector_swatch(theme, popup_theme))
.unwrap_or_else(|| rgb_to_hsl_token(0, 0, 0))
}
#[derive(Clone, Copy)]
pub enum SelectorEntry {
Section(&'static str),
Item(&'static Palette),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Appearance {
Dark,
Light,
}
pub const DEFAULT_THEME: &Palette = &OBLIVION_INK;
pub const TABLE_SECTION_TITLE_TAB_FG_BG_MAX_DIST_SQ: u32 = 2200;
#[must_use]
pub fn table_section_title_fg(palette: &Palette) -> Color {
match color_utils::rgb_euclidean_sq(palette.background, palette.tab_active_fg) {
Some(d) if d <= TABLE_SECTION_TITLE_TAB_FG_BG_MAX_DIST_SQ => palette.tab_active_bg,
_ => palette.text,
}
}
#[inline]
#[must_use]
pub fn default_theme_for_new_config_file() -> &'static str {
DEFAULT_THEME.name
}
#[derive(Clone, Debug)]
pub struct Palette {
pub name: &'static str,
pub appearance: Appearance,
pub syntect: &'static str,
pub background: Color,
pub text: Color,
pub focused_border: Color,
pub tab_active_fg: Color,
pub tab_active_bg: Color,
pub tab_inactive_bg: Color,
pub search_text: Color,
pub hint: Color,
pub popup_bg: Color,
pub node_bg: Color,
pub notification_bg: Color,
pub delta_added: Color,
pub delta_mod: Color,
pub delta_removed: Color,
pub title_brand: Color,
pub swatch: Color,
}
const LIGHT_THEME_NODE_PILL_PCT: f32 = 0.11;
#[must_use]
pub fn node_pill_background(theme: &Palette) -> Color {
match theme.appearance {
Appearance::Light => adjust_surface_rgb(
theme.background,
LIGHT_THEME_NODE_PILL_PCT,
theme.appearance,
),
Appearance::Dark => theme.node_bg,
}
}
thread_local! {
static CURRENT_THEME_NAME: RefCell<Option<String>> = const { RefCell::new(None) };
}
pub fn set_current(name: Option<&str>) {
CURRENT_THEME_NAME.with(|cell| {
*cell.borrow_mut() = name.map(String::from);
});
}
#[must_use]
pub fn current() -> &'static Palette {
let name = CURRENT_THEME_NAME.with(|cell| cell.borrow().clone());
get(name.as_deref())
}
#[must_use]
pub fn theme_name_from_config(config_theme: Option<&str>) -> &str {
match config_theme {
None => DEFAULT_THEME.name,
Some(s) => {
let t = s.trim();
if t.is_empty() || t == "default" {
DEFAULT_THEME.name
} else {
t
}
}
}
}
#[must_use]
pub fn get(name: Option<&str>) -> &'static Palette {
let n = theme_name_from_config(name);
if n == DEFAULT_THEME.name {
return DEFAULT_THEME;
}
theme_ordered_list()
.iter()
.copied()
.find(|t| t.name == n)
.unwrap_or(DEFAULT_THEME)
}