1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// =============================================================================
// THEME CONTEXT -- Thread-local theme access for components
// =============================================================================
//
// Components call `use_theme()` to get the current SemanticColors.
// The native renderer sets this via `set_current_theme()` before each frame.
// Falls back to dark theme defaults if no theme has been set.
//
// We store SemanticColors directly (not the full Theme) to avoid depending
// on cvkg-themes from cvkg-core. The colors are cloned into thread-local storage.
use crate::SemanticColors;
use std::cell::RefCell;
thread_local! {
/// Thread-local theme context for the current frame.
static THEME_CONTEXT: RefCell<Option<ThemeContext>> = const { RefCell::new(None) };
}
/// Theme context available to components during render.
/// Includes both semantic colors and visual effect flags.
#[derive(Debug, Clone)]
pub struct ThemeContext {
/// Semantic colors for the current theme.
pub colors: SemanticColors,
/// If true, components may use glassmorphic effects (frosted glass, blur).
/// If false, components should render with solid backgrounds.
pub glassmorphism_enabled: bool,
}
impl ThemeContext {
/// Create a dark theme context with glassmorphism enabled.
pub fn dark() -> Self {
Self {
colors: SemanticColors::dark(),
glassmorphism_enabled: true,
}
}
/// Create a light theme context with glassmorphism disabled.
pub fn light() -> Self {
Self {
colors: SemanticColors::light(),
glassmorphism_enabled: false,
}
}
/// Build a `ThemeContext` from a GPU `ColorTheme`, mapping its WGSL-uniform
/// fields to the closest semantic equivalents for CPU-side `theme::*()` helpers.
pub fn from_color_theme(ct: &crate::ColorTheme) -> Self {
fn to_color(a: [f32; 4]) -> crate::Color {
crate::Color::new(a[0], a[1], a[2], a[3])
}
Self {
glassmorphism_enabled: ct.glass_blur_strength > 0.0,
colors: SemanticColors {
primary: to_color(ct.primary_neon),
secondary: to_color(ct.ember_core),
accent: to_color(ct.shatter_neon),
background: to_color(ct.background_deep),
surface: to_color(ct.glass_base),
error: crate::Color::new(1.0, 0.2, 0.2, 1.0),
warning: crate::Color::new(1.0, 0.8, 0.0, 1.0),
success: crate::Color::new(0.0, 1.0, 0.5, 1.0),
text: to_color(ct.rune_glow),
text_dim: {
let c = to_color(ct.rune_glow);
crate::Color::new(c.r * 0.6, c.g * 0.6, c.b * 0.6, c.a)
},
},
}
}
}
/// Set the current theme context for this thread.
/// Called by the native renderer before each frame.
pub fn set_current_theme(colors: SemanticColors) {
THEME_CONTEXT.with(|cell| {
let is_light =
(colors.background.r + colors.background.g + colors.background.b) / 3.0 > 0.5;
let glassmorphism = !is_light; // light themes default to no glassmorphism
*cell.borrow_mut() = Some(ThemeContext {
colors,
glassmorphism_enabled: glassmorphism,
});
});
}
/// Set the full theme context (including glassmorphism flag).
pub fn set_theme_context(ctx: ThemeContext) {
THEME_CONTEXT.with(|cell| {
*cell.borrow_mut() = Some(ctx);
});
}
/// Clear the current theme. Called after each frame.
pub fn clear_current_theme() {
THEME_CONTEXT.with(|cell| {
*cell.borrow_mut() = None;
});
}
/// Access the current semantic colors from within a component's `render()` method.
///
/// Returns the colors set by the most recent `set_current_theme()` call.
/// Falls back to dark theme defaults if no theme has been set.
///
/// # Example
/// ```no_run
/// use cvkg_core::{use_theme, Renderer, Rect};
///
/// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
/// let colors = use_theme();
/// renderer.fill_rounded_rect(rect, 8.0, [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
/// }
/// ```
pub fn use_theme() -> SemanticColors {
THEME_CONTEXT.with(|cell| {
cell.borrow()
.clone()
.map(|ctx| ctx.colors)
.unwrap_or_else(SemanticColors::dark)
})
}
/// Access the full theme context from within a component's `render()` method.
///
/// Returns the current `ThemeContext` including both colors and effect flags.
/// Falls back to dark theme defaults if no theme has been set.
pub fn use_theme_context() -> ThemeContext {
THEME_CONTEXT.with(|cell| cell.borrow().clone().unwrap_or_else(ThemeContext::dark))
}
/// Returns true if glassmorphic effects are enabled in the current theme.
/// Components should check this before calling `renderer.bifrost()`.
pub fn glassmorphism_enabled() -> bool {
THEME_CONTEXT.with(|cell| {
cell.borrow()
.as_ref()
.map(|ctx| ctx.glassmorphism_enabled)
.unwrap_or(true) // default: glassmorphism on (dark theme)
})
}