//! Dynamic theme system for Azul browser//!//! Provides 7 built-in themes with the ability to cycle through them at runtime. use ratatui::style::Color; /// Available theme presets#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum ThemePreset { AzulDark, // Default - Tokyo Night based Monokai, // Classic Monokai Gruvbox, // Gruvbox Dark Nord, // Nord theme Dracula, // Dracula theme Catppuccin, // Catppuccin Mocha Solarized, // Solarized Dark} impl ThemePreset { pub fn name(&self) -> &'static str { match self { ThemePreset::AzulDark => "Azul Dark", ThemePreset::Monokai => "Monokai", ThemePreset::Gruvbox => "Gruvbox", ThemePreset::Nord => "Nord", ThemePreset::Dracula => "Dracula", ThemePreset::Catppuccin => "Catppuccin", ThemePreset::Solarized => "Solarized", } } pub fn all() -> &'static [ThemePreset] { &[ ThemePreset::AzulDark, ThemePreset::Monokai, ThemePreset::Gruvbox, ThemePreset::Nord, ThemePreset::Dracula, ThemePreset::Catppuccin, ThemePreset::Solarized, ] } pub fn next(&self) -> ThemePreset { let all = Self::all(); let idx = all.iter().position(|t| t == self).unwrap_or(0); all[(idx + 1) % all.len()] } pub fn prev(&self) -> ThemePreset { let all = Self::all(); let idx = all.iter().position(|t| t == self).unwrap_or(0); all[(idx + all.len() - 1) % all.len()] }} /// Theme color configuration#[derive(Debug, Clone)]pub struct ThemeColors { // Primary accent colors pub primary: Color, pub primary_bright: Color, pub primary_dim: Color, // Secondary colors pub secondary: Color, pub tertiary: Color, // Semantic colors pub success: Color, pub warning: Color, pub error: Color, pub info: Color, // Text colors pub text: Color, pub text_dim: Color, pub text_muted: Color, // Background colors pub bg: Color, pub bg_secondary: Color, pub bg_highlight: Color, // Border colors pub border: Color, pub border_active: Color,} impl ThemeColors { /// Get colors for a theme preset pub fn from_preset(preset: ThemePreset) -> Self { match preset { ThemePreset::AzulDark => Self::azul_dark(), ThemePreset::Monokai => Self::monokai(), ThemePreset::Gruvbox => Self::gruvbox(), ThemePreset::Nord => Self::nord(), ThemePreset::Dracula => Self::dracula(), ThemePreset::Catppuccin => Self::catppuccin(), ThemePreset::Solarized => Self::solarized(), } } /// Azul Dark - Tokyo Night inspired (default) fn azul_dark() -> Self { Self { primary: Color::Rgb(0, 191, 255), // Deep sky blue primary_bright: Color::Rgb(125, 207, 255), primary_dim: Color::Rgb(0, 140, 200), secondary: Color::Rgb(187, 154, 247), // Purple tertiary: Color::Rgb(255, 169, 0), // Orange success: Color::Rgb(158, 206, 106), // Green warning: Color::Rgb(255, 169, 0), // Orange error: Color::Rgb(247, 118, 142), // Red info: Color::Rgb(122, 162, 247), // Blue text: Color::Rgb(192, 202, 245), text_dim: Color::Rgb(165, 175, 215), text_muted: Color::Rgb(125, 137, 168), bg: Color::Rgb(26, 27, 38), bg_secondary: Color::Rgb(36, 40, 59), bg_highlight: Color::Rgb(41, 46, 66), border: Color::Rgb(122, 162, 247), border_active: Color::Rgb(0, 191, 255), } } /// Monokai theme fn monokai() -> Self { Self { primary: Color::Rgb(102, 217, 239), // Cyan primary_bright: Color::Rgb(166, 226, 46), primary_dim: Color::Rgb(82, 190, 200), secondary: Color::Rgb(174, 129, 255), // Purple tertiary: Color::Rgb(253, 151, 31), // Orange success: Color::Rgb(166, 226, 46), // Green warning: Color::Rgb(253, 151, 31), // Orange error: Color::Rgb(249, 38, 114), // Pink/Red info: Color::Rgb(102, 217, 239), // Cyan text: Color::Rgb(248, 248, 242), text_dim: Color::Rgb(200, 200, 195), text_muted: Color::Rgb(117, 113, 94), bg: Color::Rgb(39, 40, 34), bg_secondary: Color::Rgb(49, 50, 44), bg_highlight: Color::Rgb(59, 60, 54), border: Color::Rgb(102, 217, 239), border_active: Color::Rgb(166, 226, 46), } } /// Gruvbox Dark theme fn gruvbox() -> Self { Self { primary: Color::Rgb(215, 153, 33), // Yellow primary_bright: Color::Rgb(250, 189, 47), primary_dim: Color::Rgb(181, 118, 20), secondary: Color::Rgb(211, 134, 155), // Purple tertiary: Color::Rgb(254, 128, 25), // Orange success: Color::Rgb(152, 151, 26), // Green warning: Color::Rgb(254, 128, 25), // Orange error: Color::Rgb(204, 36, 29), // Red info: Color::Rgb(69, 133, 136), // Aqua text: Color::Rgb(235, 219, 178), text_dim: Color::Rgb(213, 196, 161), text_muted: Color::Rgb(168, 153, 132), bg: Color::Rgb(40, 40, 40), bg_secondary: Color::Rgb(50, 48, 47), bg_highlight: Color::Rgb(60, 56, 54), border: Color::Rgb(215, 153, 33), border_active: Color::Rgb(250, 189, 47), } } /// Nord theme fn nord() -> Self { Self { primary: Color::Rgb(136, 192, 208), // Frost cyan primary_bright: Color::Rgb(143, 188, 187), primary_dim: Color::Rgb(94, 129, 172), secondary: Color::Rgb(180, 142, 173), // Purple tertiary: Color::Rgb(208, 135, 112), // Orange success: Color::Rgb(163, 190, 140), // Green warning: Color::Rgb(235, 203, 139), // Yellow error: Color::Rgb(191, 97, 106), // Red info: Color::Rgb(129, 161, 193), // Blue text: Color::Rgb(236, 239, 244), text_dim: Color::Rgb(216, 222, 233), text_muted: Color::Rgb(76, 86, 106), bg: Color::Rgb(46, 52, 64), bg_secondary: Color::Rgb(59, 66, 82), bg_highlight: Color::Rgb(67, 76, 94), border: Color::Rgb(136, 192, 208), border_active: Color::Rgb(143, 188, 187), } } /// Dracula theme fn dracula() -> Self { Self { primary: Color::Rgb(189, 147, 249), // Purple primary_bright: Color::Rgb(255, 121, 198), primary_dim: Color::Rgb(139, 233, 253), secondary: Color::Rgb(255, 121, 198), // Pink tertiary: Color::Rgb(255, 184, 108), // Orange success: Color::Rgb(80, 250, 123), // Green warning: Color::Rgb(241, 250, 140), // Yellow error: Color::Rgb(255, 85, 85), // Red info: Color::Rgb(139, 233, 253), // Cyan text: Color::Rgb(248, 248, 242), text_dim: Color::Rgb(226, 226, 220), text_muted: Color::Rgb(98, 114, 164), bg: Color::Rgb(40, 42, 54), bg_secondary: Color::Rgb(68, 71, 90), bg_highlight: Color::Rgb(55, 58, 76), border: Color::Rgb(189, 147, 249), border_active: Color::Rgb(255, 121, 198), } } /// Catppuccin Mocha theme fn catppuccin() -> Self { Self { primary: Color::Rgb(137, 180, 250), // Blue primary_bright: Color::Rgb(148, 226, 213), primary_dim: Color::Rgb(116, 199, 236), secondary: Color::Rgb(203, 166, 247), // Mauve tertiary: Color::Rgb(250, 179, 135), // Peach success: Color::Rgb(166, 227, 161), // Green warning: Color::Rgb(249, 226, 175), // Yellow error: Color::Rgb(243, 139, 168), // Red info: Color::Rgb(137, 220, 235), // Sky text: Color::Rgb(205, 214, 244), text_dim: Color::Rgb(186, 194, 222), text_muted: Color::Rgb(108, 112, 134), bg: Color::Rgb(30, 30, 46), bg_secondary: Color::Rgb(49, 50, 68), bg_highlight: Color::Rgb(69, 71, 90), border: Color::Rgb(137, 180, 250), border_active: Color::Rgb(203, 166, 247), } } /// Solarized Dark theme fn solarized() -> Self { Self { primary: Color::Rgb(38, 139, 210), // Blue primary_bright: Color::Rgb(42, 161, 152), primary_dim: Color::Rgb(108, 113, 196), secondary: Color::Rgb(211, 54, 130), // Magenta tertiary: Color::Rgb(203, 75, 22), // Orange success: Color::Rgb(133, 153, 0), // Green warning: Color::Rgb(181, 137, 0), // Yellow error: Color::Rgb(220, 50, 47), // Red info: Color::Rgb(42, 161, 152), // Cyan text: Color::Rgb(131, 148, 150), text_dim: Color::Rgb(147, 161, 161), text_muted: Color::Rgb(88, 110, 117), bg: Color::Rgb(0, 43, 54), bg_secondary: Color::Rgb(7, 54, 66), bg_highlight: Color::Rgb(0, 57, 71), border: Color::Rgb(38, 139, 210), border_active: Color::Rgb(42, 161, 152), } }} /// Theme manager for runtime theme switchingpub struct ThemeManager { current_preset: ThemePreset, colors: ThemeColors,} impl ThemeManager { pub fn new() -> Self { let preset = ThemePreset::AzulDark; Self { current_preset: preset, colors: ThemeColors::from_preset(preset), } } pub fn with_preset(preset: ThemePreset) -> Self { Self { current_preset: preset, colors: ThemeColors::from_preset(preset), } } /// Get current theme preset pub fn preset(&self) -> ThemePreset { self.current_preset } /// Get current theme colors pub fn colors(&self) -> &ThemeColors { &self.colors } /// Cycle to next theme, returns the new theme name pub fn cycle_next(&mut self) -> &'static str { self.current_preset = self.current_preset.next(); self.colors = ThemeColors::from_preset(self.current_preset); self.current_preset.name() } /// Cycle to previous theme, returns the new theme name pub fn cycle_prev(&mut self) -> &'static str { self.current_preset = self.current_preset.prev(); self.colors = ThemeColors::from_preset(self.current_preset); self.current_preset.name() } /// Set a specific theme pub fn set_theme(&mut self, preset: ThemePreset) { self.current_preset = preset; self.colors = ThemeColors::from_preset(preset); }} impl Default for ThemeManager { fn default() -> Self { Self::new() }} #[cfg(test)]mod tests { use super::*; #[test] fn test_theme_cycling() { let mut tm = ThemeManager::new(); assert_eq!(tm.preset(), ThemePreset::AzulDark); tm.cycle_next(); assert_eq!(tm.preset(), ThemePreset::Monokai); tm.cycle_prev(); assert_eq!(tm.preset(), ThemePreset::AzulDark); } #[test] fn test_all_themes_have_colors() { for preset in ThemePreset::all() { let colors = ThemeColors::from_preset(*preset); // Just ensure we can access all color fields let _ = colors.primary; let _ = colors.text; let _ = colors.bg; } }}
//! Dynamic theme system for Azul browser
//!
//! Provides 7 built-in themes with the ability to cycle through them at runtime.
use ratatui::style::Color;
/// Available theme presets
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThemePreset {
AzulDark, // Default - Tokyo Night based
Monokai, // Classic Monokai
Gruvbox, // Gruvbox Dark
Nord, // Nord theme
Dracula, // Dracula theme
Catppuccin, // Catppuccin Mocha
Solarized, // Solarized Dark
}
impl ThemePreset {
pub fn name(&self) -> &'static str {
match self {
ThemePreset::AzulDark => "Azul Dark",
ThemePreset::Monokai => "Monokai",
ThemePreset::Gruvbox => "Gruvbox",
ThemePreset::Nord => "Nord",
ThemePreset::Dracula => "Dracula",
ThemePreset::Catppuccin => "Catppuccin",
ThemePreset::Solarized => "Solarized",
}
}
pub fn all() -> &'static [ThemePreset] {
&[
ThemePreset::AzulDark,
ThemePreset::Monokai,
ThemePreset::Gruvbox,
ThemePreset::Nord,
ThemePreset::Dracula,
ThemePreset::Catppuccin,
ThemePreset::Solarized,
]
}
pub fn next(&self) -> ThemePreset {
let all = Self::all();
let idx = all.iter().position(|t| t == self).unwrap_or(0);
all[(idx + 1) % all.len()]
}
pub fn prev(&self) -> ThemePreset {
let all = Self::all();
let idx = all.iter().position(|t| t == self).unwrap_or(0);
all[(idx + all.len() - 1) % all.len()]
}
}
/// Theme color configuration
#[derive(Debug, Clone)]
pub struct ThemeColors {
// Primary accent colors
pub primary: Color,
pub primary_bright: Color,
pub primary_dim: Color,
// Secondary colors
pub secondary: Color,
pub tertiary: Color,
// Semantic colors
pub success: Color,
pub warning: Color,
pub error: Color,
pub info: Color,
// Text colors
pub text: Color,
pub text_dim: Color,
pub text_muted: Color,
// Background colors
pub bg: Color,
pub bg_secondary: Color,
pub bg_highlight: Color,
// Border colors
pub border: Color,
pub border_active: Color,
}
impl ThemeColors {
/// Get colors for a theme preset
pub fn from_preset(preset: ThemePreset) -> Self {
match preset {
ThemePreset::AzulDark => Self::azul_dark(),
ThemePreset::Monokai => Self::monokai(),
ThemePreset::Gruvbox => Self::gruvbox(),
ThemePreset::Nord => Self::nord(),
ThemePreset::Dracula => Self::dracula(),
ThemePreset::Catppuccin => Self::catppuccin(),
ThemePreset::Solarized => Self::solarized(),
}
}
/// Azul Dark - Tokyo Night inspired (default)
fn azul_dark() -> Self {
Self {
primary: Color::Rgb(0, 191, 255), // Deep sky blue
primary_bright: Color::Rgb(125, 207, 255),
primary_dim: Color::Rgb(0, 140, 200),
secondary: Color::Rgb(187, 154, 247), // Purple
tertiary: Color::Rgb(255, 169, 0), // Orange
success: Color::Rgb(158, 206, 106), // Green
warning: Color::Rgb(255, 169, 0), // Orange
error: Color::Rgb(247, 118, 142), // Red
info: Color::Rgb(122, 162, 247), // Blue
text: Color::Rgb(192, 202, 245),
text_dim: Color::Rgb(165, 175, 215),
text_muted: Color::Rgb(125, 137, 168),
bg: Color::Rgb(26, 27, 38),
bg_secondary: Color::Rgb(36, 40, 59),
bg_highlight: Color::Rgb(41, 46, 66),
border: Color::Rgb(122, 162, 247),
border_active: Color::Rgb(0, 191, 255),
}
}
/// Monokai theme
fn monokai() -> Self {
Self {
primary: Color::Rgb(102, 217, 239), // Cyan
primary_bright: Color::Rgb(166, 226, 46),
primary_dim: Color::Rgb(82, 190, 200),
secondary: Color::Rgb(174, 129, 255), // Purple
tertiary: Color::Rgb(253, 151, 31), // Orange
success: Color::Rgb(166, 226, 46), // Green
warning: Color::Rgb(253, 151, 31), // Orange
error: Color::Rgb(249, 38, 114), // Pink/Red
info: Color::Rgb(102, 217, 239), // Cyan
text: Color::Rgb(248, 248, 242),
text_dim: Color::Rgb(200, 200, 195),
text_muted: Color::Rgb(117, 113, 94),
bg: Color::Rgb(39, 40, 34),
bg_secondary: Color::Rgb(49, 50, 44),
bg_highlight: Color::Rgb(59, 60, 54),
border: Color::Rgb(102, 217, 239),
border_active: Color::Rgb(166, 226, 46),
}
}
/// Gruvbox Dark theme
fn gruvbox() -> Self {
Self {
primary: Color::Rgb(215, 153, 33), // Yellow
primary_bright: Color::Rgb(250, 189, 47),
primary_dim: Color::Rgb(181, 118, 20),
secondary: Color::Rgb(211, 134, 155), // Purple
tertiary: Color::Rgb(254, 128, 25), // Orange
success: Color::Rgb(152, 151, 26), // Green
warning: Color::Rgb(254, 128, 25), // Orange
error: Color::Rgb(204, 36, 29), // Red
info: Color::Rgb(69, 133, 136), // Aqua
text: Color::Rgb(235, 219, 178),
text_dim: Color::Rgb(213, 196, 161),
text_muted: Color::Rgb(168, 153, 132),
bg: Color::Rgb(40, 40, 40),
bg_secondary: Color::Rgb(50, 48, 47),
bg_highlight: Color::Rgb(60, 56, 54),
border: Color::Rgb(215, 153, 33),
border_active: Color::Rgb(250, 189, 47),
}
}
/// Nord theme
fn nord() -> Self {
Self {
primary: Color::Rgb(136, 192, 208), // Frost cyan
primary_bright: Color::Rgb(143, 188, 187),
primary_dim: Color::Rgb(94, 129, 172),
secondary: Color::Rgb(180, 142, 173), // Purple
tertiary: Color::Rgb(208, 135, 112), // Orange
success: Color::Rgb(163, 190, 140), // Green
warning: Color::Rgb(235, 203, 139), // Yellow
error: Color::Rgb(191, 97, 106), // Red
info: Color::Rgb(129, 161, 193), // Blue
text: Color::Rgb(236, 239, 244),
text_dim: Color::Rgb(216, 222, 233),
text_muted: Color::Rgb(76, 86, 106),
bg: Color::Rgb(46, 52, 64),
bg_secondary: Color::Rgb(59, 66, 82),
bg_highlight: Color::Rgb(67, 76, 94),
border: Color::Rgb(136, 192, 208),
border_active: Color::Rgb(143, 188, 187),
}
}
/// Dracula theme
fn dracula() -> Self {
Self {
primary: Color::Rgb(189, 147, 249), // Purple
primary_bright: Color::Rgb(255, 121, 198),
primary_dim: Color::Rgb(139, 233, 253),
secondary: Color::Rgb(255, 121, 198), // Pink
tertiary: Color::Rgb(255, 184, 108), // Orange
success: Color::Rgb(80, 250, 123), // Green
warning: Color::Rgb(241, 250, 140), // Yellow
error: Color::Rgb(255, 85, 85), // Red
info: Color::Rgb(139, 233, 253), // Cyan
text: Color::Rgb(248, 248, 242),
text_dim: Color::Rgb(226, 226, 220),
text_muted: Color::Rgb(98, 114, 164),
bg: Color::Rgb(40, 42, 54),
bg_secondary: Color::Rgb(68, 71, 90),
bg_highlight: Color::Rgb(55, 58, 76),
border: Color::Rgb(189, 147, 249),
border_active: Color::Rgb(255, 121, 198),
}
}
/// Catppuccin Mocha theme
fn catppuccin() -> Self {
Self {
primary: Color::Rgb(137, 180, 250), // Blue
primary_bright: Color::Rgb(148, 226, 213),
primary_dim: Color::Rgb(116, 199, 236),
secondary: Color::Rgb(203, 166, 247), // Mauve
tertiary: Color::Rgb(250, 179, 135), // Peach
success: Color::Rgb(166, 227, 161), // Green
warning: Color::Rgb(249, 226, 175), // Yellow
error: Color::Rgb(243, 139, 168), // Red
info: Color::Rgb(137, 220, 235), // Sky
text: Color::Rgb(205, 214, 244),
text_dim: Color::Rgb(186, 194, 222),
text_muted: Color::Rgb(108, 112, 134),
bg: Color::Rgb(30, 30, 46),
bg_secondary: Color::Rgb(49, 50, 68),
bg_highlight: Color::Rgb(69, 71, 90),
border: Color::Rgb(137, 180, 250),
border_active: Color::Rgb(203, 166, 247),
}
}
/// Solarized Dark theme
fn solarized() -> Self {
Self {
primary: Color::Rgb(38, 139, 210), // Blue
primary_bright: Color::Rgb(42, 161, 152),
primary_dim: Color::Rgb(108, 113, 196),
secondary: Color::Rgb(211, 54, 130), // Magenta
tertiary: Color::Rgb(203, 75, 22), // Orange
success: Color::Rgb(133, 153, 0), // Green
warning: Color::Rgb(181, 137, 0), // Yellow
error: Color::Rgb(220, 50, 47), // Red
info: Color::Rgb(42, 161, 152), // Cyan
text: Color::Rgb(131, 148, 150),
text_dim: Color::Rgb(147, 161, 161),
text_muted: Color::Rgb(88, 110, 117),
bg: Color::Rgb(0, 43, 54),
bg_secondary: Color::Rgb(7, 54, 66),
bg_highlight: Color::Rgb(0, 57, 71),
border: Color::Rgb(38, 139, 210),
border_active: Color::Rgb(42, 161, 152),
}
}
}
/// Theme manager for runtime theme switching
pub struct ThemeManager {
current_preset: ThemePreset,
colors: ThemeColors,
}
impl ThemeManager {
pub fn new() -> Self {
let preset = ThemePreset::AzulDark;
Self {
current_preset: preset,
colors: ThemeColors::from_preset(preset),
}
}
pub fn with_preset(preset: ThemePreset) -> Self {
Self {
current_preset: preset,
colors: ThemeColors::from_preset(preset),
}
}
/// Get current theme preset
pub fn preset(&self) -> ThemePreset {
self.current_preset
}
/// Get current theme colors
pub fn colors(&self) -> &ThemeColors {
&self.colors
}
/// Cycle to next theme, returns the new theme name
pub fn cycle_next(&mut self) -> &'static str {
self.current_preset = self.current_preset.next();
self.colors = ThemeColors::from_preset(self.current_preset);
self.current_preset.name()
}
/// Cycle to previous theme, returns the new theme name
pub fn cycle_prev(&mut self) -> &'static str {
self.current_preset = self.current_preset.prev();
self.colors = ThemeColors::from_preset(self.current_preset);
self.current_preset.name()
}
/// Set a specific theme
pub fn set_theme(&mut self, preset: ThemePreset) {
self.current_preset = preset;
self.colors = ThemeColors::from_preset(preset);
}
}
impl Default for ThemeManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_theme_cycling() {
let mut tm = ThemeManager::new();
assert_eq!(tm.preset(), ThemePreset::AzulDark);
tm.cycle_next();
assert_eq!(tm.preset(), ThemePreset::Monokai);
tm.cycle_prev();
assert_eq!(tm.preset(), ThemePreset::AzulDark);
}
#[test]
fn test_all_themes_have_colors() {
for preset in ThemePreset::all() {
let colors = ThemeColors::from_preset(*preset);
// Just ensure we can access all color fields
let _ = colors.primary;
let _ = colors.text;
let _ = colors.bg;
}
}
}