use std::cell::OnceCell;
use super::theme::{Theme, ThemeMode};
use super::tokens::{CosmicVariant, TokenResolver};
#[derive(Debug)]
pub struct CosmicTheme {
resolver: TokenResolver,
variant: CosmicVariant,
cached_theme: OnceCell<Theme>,
}
impl Clone for CosmicTheme {
fn clone(&self) -> Self {
Self {
resolver: self.resolver.clone(),
variant: self.variant,
cached_theme: OnceCell::new(), }
}
}
impl Default for CosmicTheme {
fn default() -> Self {
Self::new(CosmicVariant::default())
}
}
impl CosmicTheme {
pub fn new(variant: CosmicVariant) -> Self {
Self {
resolver: variant.resolver(),
variant,
cached_theme: OnceCell::new(),
}
}
pub fn variant(&self) -> CosmicVariant {
self.variant
}
pub fn as_theme(&self) -> Theme {
self.cached_theme
.get_or_init(|| Theme::from_resolver(&self.resolver))
.clone()
}
pub fn resolver(&self) -> &TokenResolver {
&self.resolver
}
pub fn cycle(&mut self) {
self.variant = self.variant.cycle();
self.resolver = self.variant.resolver();
self.cached_theme.take(); }
pub fn set_variant(&mut self, variant: CosmicVariant) {
self.variant = variant;
self.resolver = variant.resolver();
self.cached_theme.take(); }
pub fn label(&self) -> &'static str {
self.variant.label()
}
pub fn is_dark(&self) -> bool {
self.resolver.semantic().is_dark()
}
}
impl From<ThemeMode> for CosmicVariant {
fn from(mode: ThemeMode) -> Self {
match mode {
ThemeMode::Dark => CosmicVariant::CosmicDark,
ThemeMode::Light => CosmicVariant::CosmicLight,
ThemeMode::Solarized => CosmicVariant::CosmicDark, }
}
}
impl From<CosmicVariant> for ThemeMode {
fn from(variant: CosmicVariant) -> Self {
match variant {
CosmicVariant::CosmicDark => ThemeMode::Dark,
CosmicVariant::CosmicLight => ThemeMode::Light,
CosmicVariant::CosmicViolet => ThemeMode::Dark, }
}
}
impl Theme {
pub fn from_resolver(resolver: &TokenResolver) -> Self {
let semantic = resolver.semantic();
Self {
realm_shared: semantic.accent_secondary, realm_org: semantic.status_success,
trait_defined: semantic.text_muted, trait_authored: semantic.accent_primary, trait_imported: semantic.verb_exec, trait_generated: semantic.status_success, trait_retrieved: semantic.accent_secondary,
status_pending: semantic.text_muted,
status_running: semantic.status_warning,
status_success: semantic.status_success,
status_failed: semantic.status_error,
status_paused: semantic.accent_secondary,
phase_launch: semantic.accent_tertiary, phase_orbital: semantic.status_info, phase_rendezvous: semantic.accent_primary,
mcp_describe: semantic.status_info, mcp_context: semantic.accent_tertiary, mcp_search: semantic.verb_exec, mcp_audit: semantic.accent_primary, mcp_write: semantic.status_success,
border_normal: semantic.border_default,
border_focused: semantic.border_focused,
text: semantic.text_primary, text_primary: semantic.text_primary,
text_secondary: semantic.text_secondary,
text_muted: semantic.text_muted,
background: semantic.bg_primary,
highlight: semantic.accent_primary,
selection: semantic.accent_secondary,
git_added: semantic.status_success, git_modified: semantic.status_warning, git_deleted: semantic.status_error,
scrollbar_thumb: semantic.scrollbar_thumb,
scrollbar_track: semantic.scrollbar_track,
scrollbar_arrows: semantic.scrollbar_arrows,
}
}
pub fn cosmic_dark() -> Self {
Self::from_resolver(&TokenResolver::cosmic_dark())
}
pub fn cosmic_light() -> Self {
Self::from_resolver(&TokenResolver::cosmic_light())
}
pub fn cosmic_violet() -> Self {
Self::from_resolver(&TokenResolver::cosmic_violet())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::style::Color;
#[test]
fn test_cosmic_theme_new_default() {
let cosmic = CosmicTheme::default();
assert_eq!(cosmic.variant(), CosmicVariant::CosmicDark);
}
#[test]
fn test_cosmic_theme_new_with_variant() {
let cosmic = CosmicTheme::new(CosmicVariant::CosmicLight);
assert_eq!(cosmic.variant(), CosmicVariant::CosmicLight);
}
#[test]
fn test_cosmic_theme_cycle() {
let mut cosmic = CosmicTheme::new(CosmicVariant::CosmicDark);
cosmic.cycle();
assert_eq!(cosmic.variant(), CosmicVariant::CosmicLight);
cosmic.cycle();
assert_eq!(cosmic.variant(), CosmicVariant::CosmicViolet);
cosmic.cycle();
assert_eq!(cosmic.variant(), CosmicVariant::CosmicDark);
}
#[test]
fn test_cosmic_theme_set_variant() {
let mut cosmic = CosmicTheme::default();
cosmic.set_variant(CosmicVariant::CosmicViolet);
assert_eq!(cosmic.variant(), CosmicVariant::CosmicViolet);
}
#[test]
fn test_cosmic_theme_label() {
let cosmic = CosmicTheme::new(CosmicVariant::CosmicDark);
assert!(cosmic.label().contains("Dark"));
let cosmic = CosmicTheme::new(CosmicVariant::CosmicLight);
assert!(cosmic.label().contains("Light"));
let cosmic = CosmicTheme::new(CosmicVariant::CosmicViolet);
assert!(cosmic.label().contains("Violet"));
}
#[test]
fn test_cosmic_theme_is_dark() {
let dark = CosmicTheme::new(CosmicVariant::CosmicDark);
assert!(dark.is_dark());
let light = CosmicTheme::new(CosmicVariant::CosmicLight);
assert!(!light.is_dark());
let violet = CosmicTheme::new(CosmicVariant::CosmicViolet);
assert!(violet.is_dark());
}
#[test]
fn test_cosmic_theme_as_theme() {
let cosmic = CosmicTheme::new(CosmicVariant::CosmicDark);
let theme = cosmic.as_theme();
let resolver = cosmic.resolver();
assert_eq!(theme.background, resolver.bg_primary());
assert_eq!(theme.text_primary, resolver.text_primary());
}
#[test]
fn test_cosmic_theme_resolver_access() {
let cosmic = CosmicTheme::new(CosmicVariant::CosmicLight);
let resolver = cosmic.resolver();
assert!(!resolver.semantic().is_dark());
}
#[test]
fn test_theme_mode_to_cosmic_variant() {
assert_eq!(
CosmicVariant::from(ThemeMode::Dark),
CosmicVariant::CosmicDark
);
assert_eq!(
CosmicVariant::from(ThemeMode::Light),
CosmicVariant::CosmicLight
);
assert_eq!(
CosmicVariant::from(ThemeMode::Solarized),
CosmicVariant::CosmicDark
);
}
#[test]
fn test_cosmic_variant_to_theme_mode() {
assert_eq!(ThemeMode::from(CosmicVariant::CosmicDark), ThemeMode::Dark);
assert_eq!(
ThemeMode::from(CosmicVariant::CosmicLight),
ThemeMode::Light
);
assert_eq!(
ThemeMode::from(CosmicVariant::CosmicViolet),
ThemeMode::Dark
);
}
#[test]
fn test_theme_from_resolver_cosmic_dark() {
let theme = Theme::cosmic_dark();
assert_eq!(theme.background, Color::Rgb(15, 23, 42)); }
#[test]
fn test_theme_from_resolver_cosmic_light() {
let theme = Theme::cosmic_light();
assert_eq!(theme.background, Color::Rgb(248, 250, 252)); }
#[test]
fn test_theme_from_resolver_cosmic_violet() {
let theme = Theme::cosmic_violet();
assert_eq!(theme.background, Color::Rgb(46, 16, 101)); }
#[test]
fn test_theme_from_resolver_has_all_fields() {
let resolver = TokenResolver::cosmic_dark();
let theme = Theme::from_resolver(&resolver);
assert_ne!(theme.realm_shared, Color::Reset);
assert_ne!(theme.realm_org, Color::Reset);
assert_ne!(theme.trait_defined, Color::Reset);
assert_ne!(theme.status_pending, Color::Reset);
assert_ne!(theme.phase_launch, Color::Reset);
assert_ne!(theme.mcp_describe, Color::Reset);
assert_ne!(theme.border_normal, Color::Reset);
assert_ne!(theme.scrollbar_thumb, Color::Reset);
}
#[test]
fn test_theme_cosmic_variants_differ() {
let dark = Theme::cosmic_dark();
let light = Theme::cosmic_light();
let violet = Theme::cosmic_violet();
assert_ne!(dark.background, light.background);
assert_ne!(dark.background, violet.background);
assert_ne!(light.background, violet.background);
}
#[test]
fn test_as_theme_caches_result() {
let cosmic = CosmicTheme::new(CosmicVariant::CosmicDark);
let theme1 = cosmic.as_theme();
let theme2 = cosmic.as_theme();
assert_eq!(theme1.background, theme2.background);
assert_eq!(theme1.text_primary, theme2.text_primary);
}
#[test]
fn test_cycle_invalidates_cache() {
let mut cosmic = CosmicTheme::new(CosmicVariant::CosmicDark);
let dark_theme = cosmic.as_theme();
assert_eq!(dark_theme.background, Color::Rgb(15, 23, 42));
cosmic.cycle();
let light_theme = cosmic.as_theme();
assert_eq!(light_theme.background, Color::Rgb(248, 250, 252)); assert_ne!(dark_theme.background, light_theme.background);
}
#[test]
fn test_set_variant_invalidates_cache() {
let mut cosmic = CosmicTheme::new(CosmicVariant::CosmicDark);
let dark_theme = cosmic.as_theme();
cosmic.set_variant(CosmicVariant::CosmicViolet);
let violet_theme = cosmic.as_theme();
assert_eq!(violet_theme.background, Color::Rgb(46, 16, 101)); assert_ne!(dark_theme.background, violet_theme.background);
}
#[test]
fn test_clone_gets_fresh_cache() {
let cosmic1 = CosmicTheme::new(CosmicVariant::CosmicDark);
let _ = cosmic1.as_theme();
let cosmic2 = cosmic1.clone();
let theme1 = cosmic1.as_theme();
let theme2 = cosmic2.as_theme();
assert_eq!(theme1.background, theme2.background);
}
}