eternalibre-notes/src/themes.rs

415 lines
13 KiB
Rust

use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Theme {
pub name: &'static str,
pub fg: &'static str,
pub bg: &'static str,
pub ansi_normal: [&'static str; 8],
pub ansi_bright: [&'static str; 8],
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct ANSIPalette {
pub name: &'static str,
pub ansi_normal: [&'static str; 8],
pub ansi_bright: [&'static str; 8],
}
impl Theme {
pub fn to_css_block(&self) -> String {
let (btn_primary_bg, btn_primary_fg) = self.button_colors(4);
let (btn_danger_bg, btn_danger_fg) = self.button_colors(1);
format!(
":root {{\n\
--app-fg: {};\n\
--app-bg: {};\n\
--ui-border: {}33;\n\
--ui-hover: {}1a;\n\
--btn-primary-bg: {};\n\
--btn-primary-fg: {};\n\
--btn-danger-bg: {};\n\
--btn-danger-fg: {};\n\
--ansi-0: {}; --ansi-1: {}; --ansi-2: {}; --ansi-3: {};\n\
--ansi-4: {}; --ansi-5: {}; --ansi-6: {}; --ansi-7: {};\n\
--ansi-8: {}; --ansi-9: {}; --ansi-10: {}; --ansi-11: {};\n\
--ansi-12: {}; --ansi-13: {}; --ansi-14: {}; --ansi-15: {};\n\
}}",
self.fg,
self.bg,
self.fg,
self.fg,
btn_primary_bg,
btn_primary_fg,
btn_danger_bg,
btn_danger_fg,
self.ansi_normal[0],
self.ansi_normal[1],
self.ansi_normal[2],
self.ansi_normal[3],
self.ansi_normal[4],
self.ansi_normal[5],
self.ansi_normal[6],
self.ansi_normal[7],
self.ansi_bright[0],
self.ansi_bright[1],
self.ansi_bright[2],
self.ansi_bright[3],
self.ansi_bright[4],
self.ansi_bright[5],
self.ansi_bright[6],
self.ansi_bright[7],
)
}
fn button_colors(&self, accent: usize) -> (&'static str, &'static str) {
// Use the bright variant of the accent color if it has decent luminance;
// otherwise fall back to the theme foreground color so text remains readable.
let bright = self.ansi_bright[accent];
if relative_luminance(bright) < 0.15 {
(self.fg, self.bg)
} else {
(bright, self.bg)
}
}
}
fn relative_luminance(hex: &str) -> f64 {
let hex = hex.trim_start_matches('#');
if hex.len() != 6 {
return 0.0;
}
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0) as f64 / 255.0;
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f64 / 255.0;
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0) as f64 / 255.0;
0.2126 * r + 0.7152 * g + 0.0722 * b
}
pub static PREDEFINED_THEMES: &[Theme] = &[
Theme {
name: "Dark",
fg: "#d0d0d0",
bg: "#1c1c1c",
ansi_normal: [
"#1c1c1c", "#c50f1f", "#23a523", "#b58900", "#268bd2", "#d33682", "#2aa198", "#d0d0d0",
],
ansi_bright: [
"#666666", "#ff6666", "#66ff66", "#ffff66", "#6666ff", "#ff66ff", "#66ffff", "#ffffff",
],
},
Theme {
name: "Light",
fg: "#1c1c1c",
bg: "#d0d0d0",
ansi_normal: [
"#1c1c1c", "#c50f1f", "#23a523", "#b58900", "#268bd2", "#d33682", "#2aa198", "#d0d0d0",
],
ansi_bright: [
"#666666", "#ff6666", "#66ff66", "#ffff66", "#6666ff", "#ff66ff", "#66ffff", "#ffffff",
],
},
Theme {
name: "Solarized Dark",
fg: "#839496",
bg: "#002b36",
ansi_normal: [
"#073642", "#dc322f", "#859900", "#b58900", "#268bd2", "#d33682", "#2aa198", "#eee8d5",
],
ansi_bright: [
"#002b36", "#cb4b16", "#586e75", "#657b83", "#839496", "#6c71c4", "#93a1a1", "#fdf6e3",
],
},
Theme {
name: "Solarized Light",
fg: "#657b83",
bg: "#fdf6e3",
ansi_normal: [
"#073642", "#dc322f", "#859900", "#b58900", "#268bd2", "#d33682", "#2aa198", "#eee8d5",
],
ansi_bright: [
"#002b36", "#cb4b16", "#586e75", "#657b83", "#839496", "#6c71c4", "#93a1a1", "#fdf6e3",
],
},
Theme {
name: "Dracula",
fg: "#f8f8f2",
bg: "#282a36",
ansi_normal: [
"#21222c", "#ff5555", "#50fa7b", "#f1fa8c", "#bd93f9", "#ff79c6", "#8be9fd", "#f8f8f2",
],
ansi_bright: [
"#6272a4", "#ff6e6e", "#69ff94", "#ffffa5", "#d6acff", "#ff92df", "#a4ffff", "#ffffff",
],
},
Theme {
name: "Gruvbox Dark",
fg: "#ebdbb2",
bg: "#282828",
ansi_normal: [
"#282828", "#cc241d", "#98971a", "#d79921", "#458588", "#b16286", "#689d6a", "#a89984",
],
ansi_bright: [
"#928374", "#fb4934", "#b8bb26", "#fabd2f", "#83a598", "#d3869b", "#8ec07c", "#ebdbb2",
],
},
Theme {
name: "Gruvbox Light",
fg: "#3c3836",
bg: "#fbf1c7",
ansi_normal: [
"#fbf1c7", "#cc241d", "#98971a", "#d79921", "#458588", "#b16286", "#689d6a", "#7c6f64",
],
ansi_bright: [
"#928374", "#9d0006", "#79740e", "#b57614", "#076678", "#8f3f71", "#427b58", "#3c3836",
],
},
Theme {
name: "Nord",
fg: "#d8dee9",
bg: "#2e3440",
ansi_normal: [
"#3b4252", "#bf616a", "#a3be8c", "#ebcb8b", "#81a1c1", "#b48ead", "#88c0d0", "#e5e9f0",
],
ansi_bright: [
"#4c566a", "#bf616a", "#a3be8c", "#ebcb8b", "#81a1c1", "#b48ead", "#8fbcbb", "#eceff4",
],
},
Theme {
name: "Monokai",
fg: "#f8f8f2",
bg: "#272822",
ansi_normal: [
"#272822", "#f92672", "#a6e22e", "#f4bf75", "#66d9ef", "#ae81ff", "#a1efe4", "#f8f8f2",
],
ansi_bright: [
"#75715e", "#f92672", "#a6e22e", "#e6db74", "#66d9ef", "#ae81ff", "#a1efe4", "#f9f8f5",
],
},
Theme {
name: "Tokyo Night",
fg: "#c0caf5",
bg: "#1a1b26",
ansi_normal: [
"#15161e", "#f7768e", "#9ece6a", "#e0af68", "#7aa2f7", "#bb9af7", "#7dcfff", "#a9b1d6",
],
ansi_bright: [
"#414868", "#f7768e", "#9ece6a", "#e0af68", "#7aa2f7", "#bb9af7", "#7dcfff", "#c0caf5",
],
},
Theme {
name: "One Dark",
fg: "#abb2bf",
bg: "#282c34",
ansi_normal: [
"#282c34", "#e06c75", "#98c379", "#e5c07b", "#61afef", "#c678dd", "#56b6c2", "#abb2bf",
],
ansi_bright: [
"#5c6370", "#e06c75", "#98c379", "#e5c07b", "#61afef", "#c678dd", "#56b6c2", "#ffffff",
],
},
Theme {
name: "Cyberpunk 2077",
fg: "#f9f227",
bg: "#0d0d0d",
ansi_normal: [
"#0d0d0d", "#ff0055", "#00ff41", "#f9f227", "#00b8ff", "#ff00cc", "#00ffff", "#f9f227",
],
ansi_bright: [
"#333333", "#ff3377", "#33ff66", "#ffff33", "#33ccff", "#ff33ff", "#33ffff", "#ffff66",
],
},
Theme {
name: "Cyan on Black",
fg: "#00ffff",
bg: "#000000",
ansi_normal: [
"#000000", "#cc0000", "#00cc00", "#cccc00", "#0000cc", "#cc00cc", "#00cccc", "#cccccc",
],
ansi_bright: [
"#666666", "#ff0000", "#00ff00", "#ffff00", "#a9a9f9", "#ff00ff", "#00ffff", "#ffffff",
],
},
Theme {
name: "Red on Black",
fg: "#ff0000",
bg: "#000000",
ansi_normal: [
"#000000", "#cc0000", "#00cc00", "#cccc00", "#0000cc", "#cc00cc", "#00cccc", "#cccccc",
],
ansi_bright: [
"#666666", "#ff0000", "#00ff00", "#ffff00", "#a9a9f9", "#ff00ff", "#00ffff", "#ffffff",
],
},
Theme {
name: "Lime Green on Black",
fg: "#00ff00",
bg: "#000000",
ansi_normal: [
"#000000", "#cc0000", "#00cc00", "#cccc00", "#0000cc", "#cc00cc", "#00cccc", "#cccccc",
],
ansi_bright: [
"#666666", "#ff0000", "#00ff00", "#ffff00", "#a9a9f9", "#ff00ff", "#00ffff", "#ffffff",
],
},
Theme {
name: "Chartreuse on Black",
fg: "#7fff00",
bg: "#000000",
ansi_normal: [
"#000000", "#cc0000", "#00cc00", "#cccc00", "#0000cc", "#cc00cc", "#00cccc", "#cccccc",
],
ansi_bright: [
"#666666", "#ff0000", "#00ff00", "#ffff00", "#a9a9f9", "#ff00ff", "#00ffff", "#ffffff",
],
},
Theme {
name: "Amber on Black",
fg: "#ffbf00",
bg: "#000000",
ansi_normal: [
"#000000", "#cc0000", "#00cc00", "#cccc00", "#0000cc", "#cc00cc", "#00cccc", "#cccccc",
],
ansi_bright: [
"#666666", "#ff0000", "#00ff00", "#ffff00", "#a9a9f9", "#ff00ff", "#00ffff", "#ffffff",
],
},
Theme {
name: "Blueberry on Black",
fg: "#8ec5fc",
bg: "#000000",
ansi_normal: [
"#000000", "#0d47a1", "#1565c0", "#1976d2", "#1e88e5", "#42a5f5", "#64b5f6", "#90caf9",
],
ansi_bright: [
"#37474f", "#1e88e5", "#42a5f5", "#64b5f6", "#90caf9", "#bbdefb", "#e3f2fd", "#ffffff",
],
},
Theme {
name: "Spring Blossom",
fg: "#ffb7c5",
bg: "#2d5016",
ansi_normal: [
"#2d5016", "#c2185b", "#7cb342", "#fbc02d", "#00897b", "#d81b60", "#aed581", "#f8bbd0",
],
ansi_bright: [
"#558b2f", "#e91e63", "#8bc34a", "#ffeb3b", "#009688", "#ec407a", "#c5e1a5", "#fce4ec",
],
},
Theme {
name: "Summer Sunset",
fg: "#ff9a5a",
bg: "#1a1a2e",
ansi_normal: [
"#1a1a2e", "#e64a19", "#ff7043", "#ffab40", "#d81b60", "#8e24aa", "#5c6bc0", "#ffb74d",
],
ansi_bright: [
"#424242", "#ff5722", "#ff8a65", "#ffc107", "#e91e63", "#9c27b0", "#3f51b5", "#ffe0b2",
],
},
Theme {
name: "Autumn Forest",
fg: "#d4a373",
bg: "#3e2723",
ansi_normal: [
"#3e2723", "#bf360c", "#e65100", "#8d6e63", "#5d4037", "#33691e", "#9e9d24", "#a1887f",
],
ansi_bright: [
"#6d4c41", "#e64a19", "#f57c00", "#a1887f", "#795548", "#558b2f", "#c0ca33", "#d7ccc8",
],
},
Theme {
name: "Winter Frost",
fg: "#e0f7fa",
bg: "#1a237e",
ansi_normal: [
"#1a237e", "#006064", "#00838f", "#0097a7", "#00acc1", "#26c6da", "#4dd0e1", "#80deea",
],
ansi_bright: [
"#283593", "#00897b", "#00acc1", "#26c6da", "#4dd0e1", "#80deea", "#b2ebf2", "#ffffff",
],
},
Theme {
name: "Stormy Night",
fg: "#b0bec5",
bg: "#263238",
ansi_normal: [
"#263238", "#37474f", "#455a64", "#546e7a", "#607d8b", "#78909c", "#90a4ae", "#b0bec5",
],
ansi_bright: [
"#455a64", "#607d8b", "#78909c", "#90a4ae", "#b0bec5", "#cfd8dc", "#eceff1", "#ffffff",
],
},
Theme {
name: "Sunny Day",
fg: "#fff59d",
bg: "#33691e",
ansi_normal: [
"#33691e", "#f57f17", "#c0ca33", "#00acc1", "#00897b", "#5c6bc0", "#7cb342", "#fff59d",
],
ansi_bright: [
"#558b2f", "#fbc02d", "#afb42b", "#26c6da", "#0097a7", "#3f51b5", "#9ccc65", "#fff9c4",
],
},
Theme {
name: "Foggy Morning",
fg: "#cfd8dc",
bg: "#455a64",
ansi_normal: [
"#455a64", "#546e7a", "#607d8b", "#78909c", "#90a4ae", "#b0bec5", "#cfd8dc", "#eceff1",
],
ansi_bright: [
"#607d8b", "#78909c", "#90a4ae", "#b0bec5", "#cfd8dc", "#eceff1", "#f5f5f5", "#ffffff",
],
},
];
pub fn get_theme(name: &str) -> &'static Theme {
PREDEFINED_THEMES
.iter()
.find(|t| t.name == name)
.unwrap_or(&PREDEFINED_THEMES[0])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_theme_is_dark() {
let theme = get_theme("Nonexistent");
assert_eq!(theme.name, "Dark");
}
#[test]
fn css_block_contains_variables() {
let theme = get_theme("Dark");
let css = theme.to_css_block();
assert!(css.contains("--app-fg:"));
assert!(css.contains("--app-bg:"));
assert!(css.contains("--btn-primary-bg:"));
assert!(css.contains("--btn-danger-bg:"));
assert!(css.contains("--ansi-0:"));
assert!(css.contains("--ansi-15:"));
}
#[test]
fn dark_themes_use_readable_button_colors() {
// Bright accent colors should be luminous enough to use as button
// backgrounds; very dark accents fall back to the theme foreground.
let cyan = get_theme("Cyan on Black");
let (bg, fg) = cyan.button_colors(4);
// After replacing #0000ff with #a9a9f9, the bright blue is readable
// and no longer falls back to the foreground color.
assert_eq!(bg, cyan.ansi_bright[4]);
assert_eq!(fg, cyan.bg);
}
#[test]
fn all_themes_have_sixteen_ansi_colors() {
for theme in PREDEFINED_THEMES {
assert_eq!(theme.ansi_normal.len(), 8);
assert_eq!(theme.ansi_bright.len(), 8);
}
}
}