1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3use oxiui_core::{Color, FontSpec, Palette, Theme};
12
13pub mod high_contrast;
14pub use high_contrast::{cooljapan_high_contrast, cooljapan_high_contrast_light};
15
16pub mod anim_tokens;
17pub mod breakpoint;
18pub mod builder;
19pub mod color;
20pub mod compile;
21pub mod gallery;
22pub mod icons;
23pub mod inheritance;
24pub mod lazy_palette;
25pub mod manager;
26pub mod overlay;
27pub mod palette_ext;
28pub mod serial;
29pub mod spec;
30pub mod style_cache;
31pub mod stylesheet;
32pub mod tokens;
33pub mod typography;
34
35pub use anim_tokens::{
36 fade_in, scale_up, slide_in, AnimationKeyframe, AnimationSpec, EasingKind, FillMode,
37 IterationCount, TransitionSpec,
38};
39pub use breakpoint::Breakpoint;
40pub use builder::{ContrastWarning, PaletteBuilder, ValidationResult, WcagLevel};
41pub use compile::CompiledStyleSheet;
42pub use gallery::{
43 make_catppuccin_latte, make_catppuccin_mocha, make_dracula, make_material_dark,
44 make_material_light, make_nord_dark, make_nord_light, make_solarized_dark,
45 make_solarized_light,
46};
47pub use icons::{BuiltinIcons, IconName, IconSet, IconVariant};
48pub use inheritance::resolve as resolve_inheritance;
49pub use lazy_palette::LazyPaletteVariants;
50pub use manager::{ThemeListener, ThemeManager};
51pub use overlay::{overlay, PartialTheme};
52pub use palette_ext::ExtendedPalette;
53pub use serial::{deserialize_theme, serialize_theme, ThemeSnapshot};
54pub use spec::{
55 elevation_shadow, elevation_shadows, elevation_to_shadow, BorderSpec, BorderSpecs, BorderStyle,
56 ShadowSpec,
57};
58pub use style_cache::StyleCache;
59pub use stylesheet::{
60 ComputedStyle, CssValue, ParseDiagnostic, ParseResult, Rule, Selector, SelectorPart,
61 Specificity, StyleSheet,
62};
63pub use tokens::{DesignTokens, RadiusStep, SpacingStep};
64pub use typography::{TextStyleToken, TypographyScale};
65
66#[derive(Clone, Debug)]
71pub struct CooljapanTheme {
72 palette: Palette,
73 font: FontSpec,
74}
75
76impl CooljapanTheme {
77 pub fn new(palette: Palette, font: FontSpec) -> Self {
79 Self { palette, font }
80 }
81}
82
83impl Theme for CooljapanTheme {
84 fn palette(&self) -> &Palette {
85 &self.palette
86 }
87 fn font(&self) -> &FontSpec {
88 &self.font
89 }
90}
91
92pub trait ThemeExt: Theme {
98 fn tokens(&self) -> DesignTokens {
103 DesignTokens::default()
104 }
105
106 fn design_tokens(&self) -> &DesignTokens {
113 static DEFAULT: std::sync::OnceLock<DesignTokens> = std::sync::OnceLock::new();
114 DEFAULT.get_or_init(DesignTokens::default)
115 }
116
117 fn typography(&self) -> TypographyScale {
121 TypographyScale::default()
122 }
123
124 fn typography_ref(&self) -> &TypographyScale {
129 static DEFAULT: std::sync::OnceLock<TypographyScale> = std::sync::OnceLock::new();
130 DEFAULT.get_or_init(TypographyScale::default)
131 }
132
133 fn is_high_contrast(&self) -> bool {
137 false
138 }
139
140 fn effective_palette(&self) -> Palette {
149 let prefs_high_contrast = os_prefers_high_contrast();
150 if prefs_high_contrast && !self.is_high_contrast() {
151 let mut p = self.palette().clone();
152 p.background = blend_to_black(p.background, 0.1);
153 p.surface = blend_to_black(p.surface, 0.05);
154 p
155 } else {
156 self.palette().clone()
157 }
158 }
159
160 fn extended_palette(&self) -> ExtendedPalette {
165 let p = self.palette();
166 let bg = p.background;
167 let luma = color::wcag_luminance(bg.0, bg.1, bg.2);
168 ExtendedPalette::derive(p.clone(), luma < 0.5)
169 }
170}
171
172impl<T: Theme + ?Sized> ThemeExt for T {}
173
174pub fn os_prefers_high_contrast() -> bool {
183 std::env::var("OXIUI_HIGH_CONTRAST")
184 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
185 .unwrap_or(false)
186}
187
188pub fn os_prefers_reduced_motion() -> bool {
194 std::env::var("OXIUI_REDUCED_MOTION")
195 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
196 .unwrap_or(false)
197}
198
199fn blend_to_black(color: oxiui_core::Color, factor: f32) -> oxiui_core::Color {
206 let factor = factor.clamp(0.0, 1.0);
207 let scale = 1.0 - factor;
208 oxiui_core::Color(
209 (color.0 as f32 * scale).round() as u8,
210 (color.1 as f32 * scale).round() as u8,
211 (color.2 as f32 * scale).round() as u8,
212 color.3,
213 )
214}
215
216fn make_dark() -> Box<dyn Theme> {
217 Box::new(CooljapanTheme::new(
218 Palette {
219 background: Color(26, 27, 38, 255), surface: Color(36, 40, 59, 255), primary: Color(122, 162, 247, 255), on_primary: Color(26, 27, 38, 255), text: Color(192, 202, 245, 255), muted: Color(86, 95, 137, 255), },
226 FontSpec::new("Inter", 14.0, 400),
227 ))
228}
229
230fn make_light() -> Box<dyn Theme> {
231 Box::new(CooljapanTheme::new(
232 Palette {
233 background: Color(216, 218, 228, 255), surface: Color(255, 255, 255, 255), primary: Color(68, 100, 200, 255), on_primary: Color(255, 255, 255, 255),
237 text: Color(30, 35, 60, 255), muted: Color(120, 130, 155, 255),
239 },
240 FontSpec::new("Inter", 14.0, 400),
241 ))
242}
243
244pub fn cooljapan_default() -> Box<dyn Theme> {
246 make_dark()
247}
248
249pub fn dark() -> Box<dyn Theme> {
251 make_dark()
252}
253
254pub fn light() -> Box<dyn Theme> {
256 make_light()
257}