use super::ColorPalette;
use ratatui::style::Color;
#[derive(Debug, Clone)]
pub struct SemanticColors {
pub bg_primary: Color,
pub bg_secondary: Color,
pub bg_tertiary: Color,
pub bg_hover: Color,
pub bg_active: Color,
pub text_primary: Color,
pub text_secondary: Color,
pub text_muted: Color,
pub text_disabled: Color,
pub text_inverse: Color,
pub border_default: Color,
pub border_focused: Color,
pub border_subtle: Color,
pub accent_primary: Color,
pub accent_secondary: Color,
pub accent_tertiary: Color,
pub status_success: Color,
pub status_warning: Color,
pub status_error: Color,
pub status_info: Color,
pub verb_infer: Color,
pub verb_exec: Color,
pub verb_fetch: Color,
pub verb_invoke: Color,
pub verb_agent: Color,
pub scrollbar_thumb: Color,
pub scrollbar_track: Color,
pub scrollbar_arrows: Color,
}
impl SemanticColors {
pub fn cosmic_dark(palette: &ColorPalette) -> Self {
Self {
bg_primary: palette.slate_900, bg_secondary: palette.slate_800, bg_tertiary: palette.slate_700, bg_hover: palette.slate_700, bg_active: palette.violet_900,
text_primary: palette.slate_50, text_secondary: palette.slate_300, text_muted: palette.slate_500, text_disabled: palette.slate_600, text_inverse: palette.slate_900,
border_default: palette.slate_700, border_focused: palette.violet_500, border_subtle: palette.slate_800,
accent_primary: palette.violet_500, accent_secondary: palette.cyan_400, accent_tertiary: palette.pink_500,
status_success: palette.emerald_500, status_warning: palette.amber_500, status_error: palette.red_500, status_info: palette.blue_500,
verb_infer: palette.violet_500, verb_exec: palette.amber_500, verb_fetch: palette.cyan_500, verb_invoke: palette.emerald_500, verb_agent: palette.rose_500,
scrollbar_thumb: palette.violet_600, scrollbar_track: palette.slate_800, scrollbar_arrows: palette.slate_400, }
}
pub fn cosmic_light(palette: &ColorPalette) -> Self {
Self {
bg_primary: palette.slate_50, bg_secondary: palette.slate_100, bg_tertiary: palette.slate_200, bg_hover: palette.slate_100, bg_active: palette.violet_100,
text_primary: palette.slate_900, text_secondary: palette.slate_600, text_muted: palette.slate_400, text_disabled: palette.slate_300, text_inverse: palette.slate_50,
border_default: palette.slate_300, border_focused: palette.violet_600, border_subtle: palette.slate_200,
accent_primary: palette.violet_600, accent_secondary: palette.cyan_600, accent_tertiary: palette.pink_600,
status_success: palette.emerald_600, status_warning: palette.amber_600, status_error: palette.red_600, status_info: palette.blue_600,
verb_infer: palette.violet_600, verb_exec: palette.amber_600, verb_fetch: palette.cyan_600, verb_invoke: palette.emerald_600, verb_agent: palette.rose_600,
scrollbar_thumb: palette.slate_400, scrollbar_track: palette.slate_200, scrollbar_arrows: palette.slate_500, }
}
pub fn cosmic_violet(palette: &ColorPalette) -> Self {
Self {
bg_primary: palette.violet_950, bg_secondary: palette.violet_900, bg_tertiary: palette.violet_800, bg_hover: palette.violet_800, bg_active: palette.cyan_900,
text_primary: palette.violet_50, text_secondary: palette.violet_200, text_muted: palette.violet_400, text_disabled: palette.violet_600, text_inverse: palette.violet_950,
border_default: palette.violet_700, border_focused: palette.cyan_400, border_subtle: palette.violet_800,
accent_primary: palette.cyan_400, accent_secondary: palette.pink_400, accent_tertiary: palette.violet_300,
status_success: palette.emerald_400, status_warning: palette.amber_400, status_error: palette.rose_400, status_info: palette.cyan_400,
verb_infer: palette.violet_300, verb_exec: palette.amber_400, verb_fetch: palette.cyan_400, verb_invoke: palette.emerald_400, verb_agent: palette.rose_400,
scrollbar_thumb: palette.cyan_500, scrollbar_track: palette.violet_800, scrollbar_arrows: palette.violet_200, }
}
pub fn verb(&self, name: &str) -> Color {
match name.to_lowercase().as_str() {
"infer" => self.verb_infer,
"exec" => self.verb_exec,
"fetch" => self.verb_fetch,
"invoke" => self.verb_invoke,
"agent" => self.verb_agent,
_ => self.text_muted,
}
}
pub fn status(&self, state: &str) -> Color {
match state.to_lowercase().as_str() {
"success" | "completed" | "done" => self.status_success,
"warning" | "pending" | "running" => self.status_warning,
"error" | "failed" => self.status_error,
"info" => self.status_info,
_ => self.text_muted,
}
}
pub fn is_dark(&self) -> bool {
if let Color::Rgb(r, g, b) = self.bg_primary {
let luminance = (r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000;
luminance < 128
} else {
true }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cosmic_dark() {
let palette = ColorPalette::tailwind();
let semantic = SemanticColors::cosmic_dark(&palette);
assert_eq!(semantic.bg_primary, palette.slate_900);
assert_eq!(semantic.accent_primary, palette.violet_500);
assert!(semantic.is_dark());
}
#[test]
fn test_cosmic_light() {
let palette = ColorPalette::tailwind();
let semantic = SemanticColors::cosmic_light(&palette);
assert_eq!(semantic.bg_primary, palette.slate_50);
assert_eq!(semantic.accent_primary, palette.violet_600);
assert!(!semantic.is_dark());
}
#[test]
fn test_cosmic_violet() {
let palette = ColorPalette::tailwind();
let semantic = SemanticColors::cosmic_violet(&palette);
assert_eq!(semantic.bg_primary, palette.violet_950);
assert!(semantic.is_dark());
}
#[test]
fn test_verb_helper() {
let palette = ColorPalette::tailwind();
let semantic = SemanticColors::cosmic_dark(&palette);
assert_eq!(semantic.verb("infer"), palette.violet_500);
assert_eq!(semantic.verb("exec"), palette.amber_500);
assert_eq!(semantic.verb("fetch"), palette.cyan_500);
assert_eq!(semantic.verb("invoke"), palette.emerald_500);
assert_eq!(semantic.verb("agent"), palette.rose_500);
assert_eq!(semantic.verb("unknown"), semantic.text_muted);
}
#[test]
fn test_status_helper() {
let palette = ColorPalette::tailwind();
let semantic = SemanticColors::cosmic_dark(&palette);
assert_eq!(semantic.status("success"), palette.emerald_500);
assert_eq!(semantic.status("completed"), palette.emerald_500);
assert_eq!(semantic.status("warning"), palette.amber_500);
assert_eq!(semantic.status("error"), palette.red_500);
assert_eq!(semantic.status("info"), palette.blue_500);
}
#[test]
fn test_all_themes_have_complete_verbs() {
let palette = ColorPalette::tailwind();
for semantic in [
SemanticColors::cosmic_dark(&palette),
SemanticColors::cosmic_light(&palette),
SemanticColors::cosmic_violet(&palette),
] {
let verbs = [
semantic.verb_infer,
semantic.verb_exec,
semantic.verb_fetch,
semantic.verb_invoke,
semantic.verb_agent,
];
for (i, v1) in verbs.iter().enumerate() {
for (j, v2) in verbs.iter().enumerate() {
if i != j {
assert_ne!(v1, v2, "Verb colors must be distinct");
}
}
}
}
}
}