1use crate::CooljapanTheme;
13use std::sync::atomic::{AtomicU64, Ordering};
14
15pub type ThemeListener = Box<dyn Fn(&CooljapanTheme) + Send + Sync>;
17
18pub type ListenerId = u64;
22
23static NEXT_LISTENER_ID: AtomicU64 = AtomicU64::new(1);
24
25pub struct ThemeManager {
56 active: CooljapanTheme,
57 listeners: Vec<(ListenerId, ThemeListener)>,
58}
59
60impl ThemeManager {
61 pub fn new(initial: CooljapanTheme) -> Self {
63 Self {
64 active: initial,
65 listeners: Vec::new(),
66 }
67 }
68
69 pub fn theme(&self) -> &CooljapanTheme {
71 &self.active
72 }
73
74 pub fn set_theme(&mut self, theme: CooljapanTheme) {
76 self.active = theme;
77 for (_, listener) in &self.listeners {
78 listener(&self.active);
79 }
80 }
81
82 pub fn subscribe(&mut self, f: ThemeListener) -> ListenerId {
87 let id = NEXT_LISTENER_ID.fetch_add(1, Ordering::Relaxed);
88 self.listeners.push((id, f));
89 id
90 }
91
92 pub fn unsubscribe(&mut self, id: ListenerId) {
96 self.listeners.retain(|(lid, _)| *lid != id);
97 }
98
99 pub fn listener_count(&self) -> usize {
101 self.listeners.len()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use oxiui_core::{Color, FontSpec, Palette};
109 use std::sync::{Arc, Mutex};
110
111 fn make_theme(bg: u8) -> CooljapanTheme {
112 CooljapanTheme::new(
113 Palette {
114 background: Color(bg, bg, bg, 255),
115 surface: Color(bg, bg, bg, 255),
116 primary: Color(0, 0, 200, 255),
117 on_primary: Color(255, 255, 255, 255),
118 text: Color(0, 0, 0, 255),
119 muted: Color(60, 60, 60, 255),
120 },
121 FontSpec::new("Inter", 14.0, 400),
122 )
123 }
124
125 #[test]
126 fn theme_manager_set_fires_listeners() {
127 let mut manager = ThemeManager::new(make_theme(0));
128 let called = Arc::new(Mutex::new(0u32));
129 let c = called.clone();
130 manager.subscribe(Box::new(move |_| {
131 *c.lock().unwrap() += 1;
132 }));
133 manager.set_theme(make_theme(255));
134 assert_eq!(
135 *called.lock().unwrap(),
136 1,
137 "listener should be called exactly once"
138 );
139 }
140
141 #[test]
142 fn theme_manager_unsubscribe() {
143 let mut manager = ThemeManager::new(make_theme(0));
144 let called = Arc::new(Mutex::new(0u32));
145 let c = called.clone();
146 let id = manager.subscribe(Box::new(move |_| {
147 *c.lock().unwrap() += 1;
148 }));
149 manager.unsubscribe(id);
150 manager.set_theme(make_theme(128));
151 assert_eq!(
152 *called.lock().unwrap(),
153 0,
154 "unsubscribed listener must not be called"
155 );
156 }
157
158 #[test]
159 fn theme_manager_multiple_listeners() {
160 let mut manager = ThemeManager::new(make_theme(0));
161 let counts: Vec<Arc<Mutex<u32>>> = (0..3).map(|_| Arc::new(Mutex::new(0u32))).collect();
162 for c in &counts {
163 let c = c.clone();
164 manager.subscribe(Box::new(move |_| {
165 *c.lock().unwrap() += 1;
166 }));
167 }
168 manager.set_theme(make_theme(42));
169 for (i, c) in counts.iter().enumerate() {
170 assert_eq!(*c.lock().unwrap(), 1, "listener {i} must be called once");
171 }
172 }
173
174 #[test]
175 fn theme_manager_theme_getter() {
176 use oxiui_core::Theme;
177 let theme = make_theme(100);
178 let manager = ThemeManager::new(theme.clone());
179 let active = manager.theme();
180 assert_eq!(active.palette().background, Color(100, 100, 100, 255));
181 }
182}