use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ThemeMode {
Light,
Dark,
System,
}
#[derive(Debug, Clone)]
pub enum ThemeEvent {
ThemeChanged(ThemeMode),
ColorsUpdated,
}
pub struct ThemeWatcher {
current_theme: ThemeMode,
listeners: Vec<Arc<dyn Fn(&ThemeEvent) + Send + Sync>>,
enabled: bool,
}
impl ThemeWatcher {
pub fn new() -> Self {
Self {
current_theme: ThemeMode::System,
listeners: Vec::new(),
enabled: true,
}
}
pub fn with_theme(theme: ThemeMode) -> Self {
Self {
current_theme: theme,
listeners: Vec::new(),
enabled: true,
}
}
pub fn current_theme(&self) -> &ThemeMode {
&self.current_theme
}
pub fn set_theme(&mut self, theme: ThemeMode) {
if self.current_theme != theme {
self.current_theme = theme.clone();
self.notify_listeners(&ThemeEvent::ThemeChanged(theme));
}
}
pub fn toggle_theme(&mut self) {
let next = match &self.current_theme {
ThemeMode::Light => ThemeMode::Dark,
ThemeMode::Dark => ThemeMode::System,
ThemeMode::System => ThemeMode::Light,
};
self.set_theme(next);
}
pub fn on_change<F>(&mut self, callback: F)
where
F: Fn(&ThemeEvent) + Send + Sync + 'static,
{
self.listeners.push(Arc::new(callback));
}
fn notify_listeners(&self, event: &ThemeEvent) {
if !self.enabled {
return;
}
for listener in &self.listeners {
listener(event);
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
impl Default for ThemeWatcher {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ThemeColors {
pub background: String,
pub surface: String,
pub text: String,
pub text_secondary: String,
pub accent: String,
pub border: String,
}
impl ThemeColors {
pub fn light() -> Self {
Self {
background: "#ffffff".to_string(),
surface: "#f5f5f5".to_string(),
text: "#000000".to_string(),
text_secondary: "#666666".to_string(),
accent: "#0066ff".to_string(),
border: "#e0e0e0".to_string(),
}
}
pub fn dark() -> Self {
Self {
background: "#1e1e1e".to_string(),
surface: "#2d2d2d".to_string(),
text: "#ffffff".to_string(),
text_secondary: "#999999".to_string(),
accent: "#4d9fff".to_string(),
border: "#404040".to_string(),
}
}
pub fn for_theme(mode: &ThemeMode) -> Self {
match mode {
ThemeMode::Light => Self::light(),
ThemeMode::Dark => Self::dark(),
ThemeMode::System => Self::dark(), }
}
}
pub struct ThemeManager {
watcher: ThemeWatcher,
colors: ThemeColors,
}
impl ThemeManager {
pub fn new() -> Self {
let watcher = ThemeWatcher::new();
let colors = ThemeColors::for_theme(watcher.current_theme());
Self { watcher, colors }
}
pub fn with_theme(theme: ThemeMode) -> Self {
let watcher = ThemeWatcher::with_theme(theme.clone());
let colors = ThemeColors::for_theme(&theme);
Self { watcher, colors }
}
pub fn colors(&self) -> &ThemeColors {
&self.colors
}
pub fn watcher(&self) -> &ThemeWatcher {
&self.watcher
}
pub fn watcher_mut(&mut self) -> &mut ThemeWatcher {
&mut self.watcher
}
pub fn set_theme(&mut self, theme: ThemeMode) {
self.watcher.set_theme(theme.clone());
self.colors = ThemeColors::for_theme(&theme);
}
pub fn on_change<F>(&mut self, callback: F)
where
F: Fn(&ThemeEvent) + Send + Sync + 'static,
{
self.watcher.on_change(callback);
}
}
impl Default for ThemeManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn test_theme_watcher_creation() {
let watcher = ThemeWatcher::new();
assert_eq!(*watcher.current_theme(), ThemeMode::System);
assert!(watcher.is_enabled());
}
#[test]
fn test_set_theme() {
let mut watcher = ThemeWatcher::new();
watcher.set_theme(ThemeMode::Dark);
assert_eq!(*watcher.current_theme(), ThemeMode::Dark);
}
#[test]
fn test_toggle_theme() {
let mut watcher = ThemeWatcher::with_theme(ThemeMode::Light);
watcher.toggle_theme();
assert_eq!(*watcher.current_theme(), ThemeMode::Dark);
}
#[test]
fn test_on_change_callback() {
let mut watcher = ThemeWatcher::new();
let called = Arc::new(AtomicBool::new(false));
let called_clone = called.clone();
watcher.on_change(move |_| {
called_clone.store(true, Ordering::SeqCst);
});
watcher.set_theme(ThemeMode::Dark);
assert!(called.load(Ordering::SeqCst));
}
#[test]
fn test_theme_colors() {
let light = ThemeColors::light();
assert_eq!(light.background, "#ffffff");
let dark = ThemeColors::dark();
assert_eq!(dark.background, "#1e1e1e");
}
#[test]
fn test_theme_manager() {
let mut manager = ThemeManager::new();
assert_eq!(*manager.watcher().current_theme(), ThemeMode::System);
manager.set_theme(ThemeMode::Light);
assert_eq!(manager.colors().background, "#ffffff");
}
}