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 spec;
29pub mod style_cache;
30pub mod stylesheet;
31pub mod tokens;
32pub mod typography;
33
34pub use anim_tokens::{
35 fade_in, scale_up, slide_in, AnimationKeyframe, AnimationSpec, EasingKind, FillMode,
36 IterationCount, TransitionSpec,
37};
38pub use breakpoint::Breakpoint;
39pub use builder::{ContrastWarning, PaletteBuilder, ValidationResult, WcagLevel};
40pub use compile::CompiledStyleSheet;
41pub use gallery::{
42 make_catppuccin_latte, make_catppuccin_mocha, make_dracula, make_material_dark,
43 make_material_light, make_nord_dark, make_nord_light, make_solarized_dark,
44 make_solarized_light,
45};
46pub use icons::{BuiltinIcons, IconName, IconSet, IconVariant};
47pub use inheritance::resolve as resolve_inheritance;
48pub use lazy_palette::LazyPaletteVariants;
49pub use manager::{ThemeListener, ThemeManager};
50pub use overlay::{overlay, PartialTheme};
51pub use palette_ext::ExtendedPalette;
52pub use spec::{
53 elevation_shadow, elevation_shadows, elevation_to_shadow, BorderSpec, BorderSpecs, BorderStyle,
54 ShadowSpec,
55};
56pub use style_cache::StyleCache;
57pub use stylesheet::{
58 ComputedStyle, CssValue, ParseDiagnostic, ParseResult, Rule, Selector, SelectorPart,
59 Specificity, StyleSheet,
60};
61pub use tokens::{DesignTokens, RadiusStep, SpacingStep};
62pub use typography::{TextStyleToken, TypographyScale};
63
64#[derive(Clone, Debug)]
69pub struct CooljapanTheme {
70 palette: Palette,
71 font: FontSpec,
72}
73
74impl CooljapanTheme {
75 pub fn new(palette: Palette, font: FontSpec) -> Self {
77 Self { palette, font }
78 }
79}
80
81impl Theme for CooljapanTheme {
82 fn palette(&self) -> &Palette {
83 &self.palette
84 }
85 fn font(&self) -> &FontSpec {
86 &self.font
87 }
88}
89
90pub trait ThemeExt: Theme {
96 fn tokens(&self) -> DesignTokens {
101 DesignTokens::default()
102 }
103
104 fn design_tokens(&self) -> &DesignTokens {
111 static DEFAULT: std::sync::OnceLock<DesignTokens> = std::sync::OnceLock::new();
112 DEFAULT.get_or_init(DesignTokens::default)
113 }
114
115 fn typography(&self) -> TypographyScale {
119 TypographyScale::default()
120 }
121
122 fn typography_ref(&self) -> &TypographyScale {
127 static DEFAULT: std::sync::OnceLock<TypographyScale> = std::sync::OnceLock::new();
128 DEFAULT.get_or_init(TypographyScale::default)
129 }
130
131 fn is_high_contrast(&self) -> bool {
135 false
136 }
137
138 fn effective_palette(&self) -> Palette {
147 let prefs_high_contrast = os_prefers_high_contrast();
148 if prefs_high_contrast && !self.is_high_contrast() {
149 let mut p = self.palette().clone();
150 p.background = blend_to_black(p.background, 0.1);
151 p.surface = blend_to_black(p.surface, 0.05);
152 p
153 } else {
154 self.palette().clone()
155 }
156 }
157
158 fn extended_palette(&self) -> ExtendedPalette {
163 let p = self.palette();
164 let bg = p.background;
165 let luma = color::wcag_luminance(bg.0, bg.1, bg.2);
166 ExtendedPalette::derive(p.clone(), luma < 0.5)
167 }
168}
169
170impl<T: Theme + ?Sized> ThemeExt for T {}
171
172pub fn os_prefers_high_contrast() -> bool {
181 std::env::var("OXIUI_HIGH_CONTRAST")
182 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
183 .unwrap_or(false)
184}
185
186pub fn os_prefers_reduced_motion() -> bool {
192 std::env::var("OXIUI_REDUCED_MOTION")
193 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
194 .unwrap_or(false)
195}
196
197fn blend_to_black(color: oxiui_core::Color, factor: f32) -> oxiui_core::Color {
204 let factor = factor.clamp(0.0, 1.0);
205 let scale = 1.0 - factor;
206 oxiui_core::Color(
207 (color.0 as f32 * scale).round() as u8,
208 (color.1 as f32 * scale).round() as u8,
209 (color.2 as f32 * scale).round() as u8,
210 color.3,
211 )
212}
213
214fn make_dark() -> Box<dyn Theme> {
215 Box::new(CooljapanTheme::new(
216 Palette {
217 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), },
224 FontSpec::new("Inter", 14.0, 400),
225 ))
226}
227
228fn make_light() -> Box<dyn Theme> {
229 Box::new(CooljapanTheme::new(
230 Palette {
231 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),
235 text: Color(30, 35, 60, 255), muted: Color(120, 130, 155, 255),
237 },
238 FontSpec::new("Inter", 14.0, 400),
239 ))
240}
241
242pub fn cooljapan_default() -> Box<dyn Theme> {
244 make_dark()
245}
246
247pub fn dark() -> Box<dyn Theme> {
249 make_dark()
250}
251
252pub fn light() -> Box<dyn Theme> {
254 make_light()
255}