use crate::palette::Palette;
use crate::schema::{ColorScheme, DesignSystem, SystemColorScheme, Theme};
use std::cell::{Cell, RefCell};
use std::sync::Arc;
thread_local! {
static CURRENT: Cell<Theme> = const { Cell::new(Theme::unified(DesignSystem::Material3)) };
static PENDING: Cell<Option<Theme>> = const { Cell::new(None) };
static OWNED: RefCell<Vec<&'static Palette>> = const { RefCell::new(Vec::new()) };
}
pub fn current_theme() -> Theme {
PENDING
.with(Cell::get)
.unwrap_or_else(|| CURRENT.with(Cell::get))
}
pub fn set_theme(theme: Theme) {
#[cfg(debug_assertions)]
if theme.palette != current_theme().palette {
for line in crate::boundary::palette_report(&theme) {
eprintln!("{line}");
}
}
PENDING.with(|pending| pending.set(Some(theme)));
}
pub fn own_palette(palette: Palette) -> &'static Palette {
OWNED.with_borrow_mut(|owned| {
if let Some(existing) = owned.iter().find(|existing| ***existing == palette) {
return *existing;
}
let home: &'static Palette = Box::leak(Box::new(palette));
owned.push(home);
home
})
}
#[doc(hidden)]
pub fn owned_palette_count() -> usize {
OWNED.with_borrow(Vec::len)
}
pub(crate) fn install(theme: Theme) {
CURRENT.with(|current| current.set(theme));
PENDING.with(|pending| pending.set(None));
}
pub(crate) fn take_pending() -> Option<Theme> {
let theme = PENDING.with(Cell::take)?;
CURRENT.with(|current| current.set(theme));
Some(theme)
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ThemeHandle {
_private: (),
}
impl ThemeHandle {
pub fn get(&self) -> Theme {
current_theme()
}
pub fn set(&self, theme: Theme) {
set_theme(theme);
}
pub fn set_palette(&self, palette: Palette) {
let theme = current_theme().with_palette(own_palette(palette));
set_theme(theme);
}
pub fn clear_palette(&self) {
let mut theme = current_theme();
theme.palette = None;
set_theme(theme);
}
pub fn set_color_scheme(&self, scheme: ColorScheme) {
set_theme(current_theme().with_color_scheme(scheme));
}
}
struct SchemeSubscriber {
id: u64,
notify: Arc<dyn Fn() + Send + Sync>,
}
thread_local! {
static SYSTEM_SCHEME: Cell<Option<SystemColorScheme>> = const { Cell::new(None) };
static SCHEME_SUBSCRIBERS: RefCell<Vec<SchemeSubscriber>> = const { RefCell::new(Vec::new()) };
static NEXT_SCHEME_SUBSCRIBER: Cell<u64> = const { Cell::new(1) };
}
pub fn system_color_scheme() -> Option<SystemColorScheme> {
SYSTEM_SCHEME.with(Cell::get)
}
pub fn resolved_color_scheme() -> Option<SystemColorScheme> {
match current_theme().color_scheme {
ColorScheme::Light => Some(SystemColorScheme::Light),
ColorScheme::Dark => Some(SystemColorScheme::Dark),
ColorScheme::FollowSystem => system_color_scheme(),
}
}
pub(crate) fn publish_system_color_scheme(scheme: SystemColorScheme) -> bool {
if SYSTEM_SCHEME.with(Cell::get) == Some(scheme) {
return false;
}
SYSTEM_SCHEME.with(|current| current.set(Some(scheme)));
let readers: Vec<Arc<dyn Fn() + Send + Sync>> = SCHEME_SUBSCRIBERS
.with_borrow(|subscribers| subscribers.iter().map(|s| s.notify.clone()).collect());
for notify in &readers {
notify();
}
!readers.is_empty()
}
pub struct SystemColorSchemeSubscription {
id: u64,
}
pub fn subscribe_system_color_scheme(
notify: Arc<dyn Fn() + Send + Sync>,
) -> SystemColorSchemeSubscription {
let id = NEXT_SCHEME_SUBSCRIBER.with(|next| {
let id = next.get();
next.set(id + 1);
id
});
SCHEME_SUBSCRIBERS
.with_borrow_mut(|subscribers| subscribers.push(SchemeSubscriber { id, notify }));
SystemColorSchemeSubscription { id }
}
impl Drop for SystemColorSchemeSubscription {
fn drop(&mut self) {
SCHEME_SUBSCRIBERS.with_borrow_mut(|subscribers| subscribers.retain(|s| s.id != self.id));
}
}
#[doc(hidden)]
pub fn reset_system_color_scheme() {
SYSTEM_SCHEME.with(|current| current.set(None));
SCHEME_SUBSCRIBERS.with_borrow_mut(Vec::clear);
}