/* ===================================================================== * color.rs — 颜色/背景解析纯函数(对应 store.ts 的颜色函数) * * 这些函数供 Rust 端的渲染/序列化使用(B2 store 迁移时调用)。 * 当前阶段保持与 TS 版本完全一致的逻辑。 * ===================================================================== */ pub fn hex_to_rgb(hex: &str) -> Option<(u8, u8, u8)> { let c = hex.trim_start_matches('#'); let full = if c.len() == 3 { format!( "{}{}{}{}{}{}", &c[0..1], &c[0..1], &c[1..2], &c[1..2], &c[2..3], &c[2..3] ) } else { c.to_string() }; if full.len() < 6 { return None; } let r = u8::from_str_radix(&full[0..2], 16).ok()?; let g = u8::from_str_radix(&full[2..4], 16).ok()?; let b = u8::from_str_radix(&full[4..6], 16).ok()?; Some((r, g, b)) } pub fn is_dark_hex(hex: &str) -> bool { match hex_to_rgb(hex) { Some((r, g, b)) => (0.299 * r as f64 + 0.587 * g as f64 + 0.114 * b as f64) < 145.0, None => false, } } pub fn shade(hex: &str, pct: f64) -> String { let (r, g, b) = match hex_to_rgb(hex) { Some(rgb) => rgb, None => return hex.to_string(), }; let f = if pct < 0.0 { 0.0 } else { 255.0 }; let p = pct.abs() / 100.0; let nr = ((f - r as f64) * p + r as f64).round() as u8; let ng = ((f - g as f64) * p + g as f64).round() as u8; let nb = ((f - b as f64) * p + b as f64).round() as u8; format!("#{:02x}{:02x}{:02x}", nr, ng, nb) } pub fn is_dark_bg(bg: &str) -> bool { if bg.is_empty() { return false; } if bg.starts_with('#') { return is_dark_hex(bg); } if bg == "primary" || bg == "accent" { return true; } if bg.starts_with("g-") { return true; } false } pub fn is_valid_hex(s: &str) -> bool { s.starts_with('#') && s.len() >= 4 && s[1..].chars().all(|c| c.is_ascii_hexdigit()) } /// 颜色解析:dark=true 时深色文字键反相为浅色 pub fn resolve_color(key: &str, dark: bool, theme: &crate::model::Theme) -> String { if key.is_empty() { return String::new(); } if key.starts_with('#') { if !is_valid_hex(key) { return if dark { "#ffffff".into() } else { theme.text.clone() }; } if dark && is_dark_hex(key) { return "#ffffff".into(); } return key.into(); } if dark { match key { "text" => "#ffffff".into(), "muted" => "rgba(255,255,255,0.72)".into(), "primary" => "#ffffff".into(), "accent" => theme.accent.clone(), _ => "#ffffff".into(), } } else { match key { "primary" => theme.primary.clone(), "accent" => theme.accent.clone(), "text" => theme.text.clone(), "muted" => theme.muted.clone(), _ => theme.text.clone(), } } } /// 背景解析:渐变键 → CSS gradient pub fn resolve_bg(key: &str, theme: &crate::model::Theme) -> String { if key.is_empty() { return "#ffffff".into(); } if key.starts_with('#') { return if is_valid_hex(key) { key.into() } else { "#ffffff".into() }; } match key { "g-primary" => format!( "linear-gradient(135deg, {} 0%, {} 100%)", theme.primary, theme.accent ), "g-deep" => format!( "linear-gradient(160deg, {} 0%, {} 100%)", shade(&theme.primary, -28.0), theme.primary ), "g-soft" => format!( "linear-gradient(135deg, {} 0%, {} 100%)", theme.panel.clone(), theme.bg.clone() ), "primary" => theme.primary.clone(), "accent" => theme.accent.clone(), "panel" => theme.panel.clone(), "bg" => theme.bg.clone(), _ => "#ffffff".into(), } } #[cfg(test)] mod tests { use super::*; #[test] fn test_hex_to_rgb() { assert_eq!(hex_to_rgb("#4f46e5"), Some((79, 70, 229))); assert_eq!(hex_to_rgb("#fff"), Some((255, 255, 255))); assert_eq!(hex_to_rgb("invalid"), None); } #[test] fn test_is_dark_hex() { assert!(is_dark_hex("#0f172a")); // 深色 assert!(is_dark_hex("#4f46e5")); // 青蓝偏深 assert!(!is_dark_hex("#ffffff")); // 白色 assert!(!is_dark_hex("#f59e0b")); // 琥珀亮色 } #[test] fn test_is_dark_bg() { assert!(is_dark_bg("primary")); assert!(is_dark_bg("g-deep")); assert!(!is_dark_bg("bg")); assert!(!is_dark_bg("panel")); } #[test] fn test_shade() { let result = shade("#000000", 50.0); assert_eq!(result, "#808080"); // 黑色提亮 50% → 灰色 } #[test] fn test_resolve_color_light() { use crate::model::Theme; let theme = Theme { name: "test".into(), primary: "#4f46e5".into(), accent: "#06b6d4".into(), bg: "#ffffff".into(), panel: "#f8fafc".into(), text: "#1e293b".into(), muted: "#64748b".into(), }; assert_eq!(resolve_color("primary", false, &theme), "#4f46e5"); assert_eq!(resolve_color("text", false, &theme), "#1e293b"); } #[test] fn test_resolve_color_dark() { use crate::model::Theme; let theme = Theme { name: "test".into(), primary: "#4f46e5".into(), accent: "#06b6d4".into(), bg: "#ffffff".into(), panel: "#f8fafc".into(), text: "#1e293b".into(), muted: "#64748b".into(), }; // 深色背景下 text 键 → 白色 assert_eq!(resolve_color("text", true, &theme), "#ffffff"); // primary 键在深色背景下也 → 白色 assert_eq!(resolve_color("primary", true, &theme), "#ffffff"); } }