use std::sync::OnceLock;
use rosace_state::GlobalAtom;
use rosace_trace::event::AtomId;
use crate::built_in;
use crate::theme::ThemeData;
static THEME_PAIR: OnceLock<(ThemeData, ThemeData)> = OnceLock::new();
pub fn register_theme_pair(light: ThemeData, dark: ThemeData) {
let _ = THEME_PAIR.set((light, dark));
}
fn theme_pair() -> (ThemeData, ThemeData) {
THEME_PAIR.get().cloned().unwrap_or_else(|| (built_in::light_theme(), built_in::dark_theme()))
}
const THEME_ATOM_ID: AtomId = AtomId(0xFFFF);
const THEME_MODE_ATOM_ID: AtomId = AtomId(0xFFFC);
static CURRENT_THEME: GlobalAtom<ThemeData> =
GlobalAtom::new(THEME_ATOM_ID, built_in::light_theme);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThemeMode {
System,
Light,
Dark,
}
static CURRENT_THEME_MODE: GlobalAtom<ThemeMode> = GlobalAtom::new(THEME_MODE_ATOM_ID, || ThemeMode::System);
pub fn use_theme_mode() -> ThemeMode {
CURRENT_THEME_MODE.get()
}
pub fn set_theme_mode(mode: ThemeMode) {
CURRENT_THEME_MODE.set(mode);
if mode != ThemeMode::System {
let (light, dark) = theme_pair();
set_theme(if mode == ThemeMode::Dark { dark } else { light });
rosace_state::reset_to_global_dirty();
}
}
pub fn sync_system_theme() {
if use_theme_mode() != ThemeMode::System {
return;
}
let is_dark = rosace_core::media_query::use_media_query().is_dark;
let (light, dark) = theme_pair();
set_theme(if is_dark { dark } else { light });
rosace_state::reset_to_global_dirty();
}
pub fn use_theme() -> ThemeData {
CURRENT_THEME.get()
}
pub fn set_animations(enabled: bool) {
let mut t = use_theme();
t.animation.enabled = enabled;
set_theme(t);
}
pub fn set_theme(theme: ThemeData) {
CURRENT_THEME.set_always(theme); }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn use_theme_returns_valid_theme() {
let theme = use_theme();
assert!(theme.spacing.md > 0.0, "spacing.md should be positive");
assert!(theme.radius.md >= 0.0, "radius.md should be non-negative");
}
#[test]
fn set_theme_updates_the_global() {
use crate::built_in::dark_theme;
let dark = dark_theme();
set_theme(dark);
let current = use_theme();
assert!(current.is_dark, "theme should now be dark after set_theme");
set_theme(crate::built_in::light_theme());
}
#[test]
fn use_theme_typography_is_consistent() {
let theme = use_theme();
assert!(
theme.typography.display_large.size > theme.typography.body_large.size,
"display_large should be larger than body_large in the active theme"
);
}
#[test]
fn sync_system_theme_follows_os_brightness_in_system_mode() {
set_theme_mode(ThemeMode::System);
let mut mq = rosace_core::media_query::use_media_query();
mq.is_dark = true;
rosace_core::set_media_query(mq);
sync_system_theme();
assert!(use_theme().is_dark, "System mode should follow OS dark push");
mq.is_dark = false;
rosace_core::set_media_query(mq);
sync_system_theme();
assert!(!use_theme().is_dark, "System mode should follow OS light push");
}
#[test]
fn sync_system_theme_leaves_a_pinned_mode_alone() {
set_theme_mode(ThemeMode::Dark);
let mut mq = rosace_core::media_query::use_media_query();
mq.is_dark = false; rosace_core::set_media_query(mq);
sync_system_theme(); assert!(use_theme().is_dark, "pinned ThemeMode::Dark must ignore OS brightness pushes");
set_theme_mode(ThemeMode::System); set_theme(crate::built_in::light_theme());
}
}