1#[cfg(feature = "xdg-portal")]
7pub mod portal;
8pub mod style;
9
10use cosmic_config::CosmicConfigEntry;
11use cosmic_config::config_subscription;
12use cosmic_theme::Component;
13use cosmic_theme::LayeredTheme;
14use cosmic_theme::Spacing;
15use cosmic_theme::ThemeMode;
16use iced_futures::Subscription;
17use iced_runtime::{Appearance, DefaultStyle};
18use std::sync::{Arc, LazyLock, Mutex};
19pub use style::*;
20
21pub type CosmicColor = ::palette::rgb::Srgba;
22pub type CosmicComponent = cosmic_theme::Component;
23pub type CosmicTheme = cosmic_theme::Theme;
24
25pub static COSMIC_DARK: LazyLock<CosmicTheme> = LazyLock::new(|| CosmicTheme::dark_default());
26
27pub static COSMIC_HC_DARK: LazyLock<CosmicTheme> =
28 LazyLock::new(|| CosmicTheme::high_contrast_dark_default());
29
30pub static COSMIC_LIGHT: LazyLock<CosmicTheme> = LazyLock::new(|| CosmicTheme::light_default());
31
32pub static COSMIC_HC_LIGHT: LazyLock<CosmicTheme> =
33 LazyLock::new(|| CosmicTheme::high_contrast_light_default());
34
35pub static TRANSPARENT_COMPONENT: LazyLock<Component> = LazyLock::new(|| Component {
36 base: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
37 hover: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
38 pressed: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
39 selected: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
40 selected_text: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
41 focus: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
42 disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
43 on: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
44 on_disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
45 divider: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
46 border: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
47 disabled_border: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
48});
49
50pub(crate) static THEME: Mutex<Theme> = Mutex::new(Theme {
51 theme_type: ThemeType::Dark,
52 layer: cosmic_theme::Layer::Background,
53});
54
55#[inline]
57#[allow(clippy::missing_panics_doc)]
58pub fn active() -> Theme {
59 THEME.lock().unwrap().clone()
60}
61
62#[inline]
64#[allow(clippy::missing_panics_doc)]
65pub fn active_type() -> ThemeType {
66 THEME.lock().unwrap().theme_type.clone()
67}
68
69#[inline]
71pub fn spacing() -> Spacing {
72 active().cosmic().spacing
73}
74
75#[inline]
77#[must_use]
78pub fn is_dark() -> bool {
79 active_type().is_dark()
80}
81
82#[inline]
84#[must_use]
85pub fn is_high_contrast() -> bool {
86 active_type().is_high_contrast()
87}
88
89pub fn system_dark() -> Theme {
118 let Ok(helper) = crate::cosmic_theme::Theme::dark_config() else {
119 return Theme::dark();
120 };
121
122 let t = crate::cosmic_theme::Theme::get_entry(&helper).unwrap_or_else(|(errors, theme)| {
123 for error in errors.into_iter().filter(cosmic_config::Error::is_err) {
124 tracing::error!(?error, "error loading system dark theme");
125 }
126 theme
127 });
128
129 Theme::system(Arc::new(t))
130}
131
132pub fn system_light() -> Theme {
133 let Ok(helper) = crate::cosmic_theme::Theme::light_config() else {
134 return Theme::light();
135 };
136
137 let t = crate::cosmic_theme::Theme::get_entry(&helper).unwrap_or_else(|(errors, theme)| {
138 for error in errors.into_iter().filter(cosmic_config::Error::is_err) {
139 tracing::error!(?error, "error loading system light theme");
140 }
141 theme
142 });
143
144 Theme::system(Arc::new(t))
145}
146
147pub fn system_preference() -> Theme {
149 let Ok(mode_config) = ThemeMode::config() else {
150 return Theme::dark();
151 };
152
153 let Ok(is_dark) = ThemeMode::is_dark(&mode_config) else {
154 return Theme::dark();
155 };
156 if is_dark {
157 system_dark()
158 } else {
159 system_light()
160 }
161}
162
163#[must_use]
164#[derive(Debug, Clone, PartialEq, Default)]
165pub enum ThemeType {
166 #[default]
167 Dark,
168 Light,
169 HighContrastDark,
170 HighContrastLight,
171 Custom(Arc<CosmicTheme>),
172 System {
173 prefer_dark: Option<bool>,
174 theme: Arc<CosmicTheme>,
175 },
176}
177
178impl ThemeType {
179 #[must_use]
181 #[inline]
182 pub fn is_dark(&self) -> bool {
183 match self {
184 Self::Dark | Self::HighContrastDark => true,
185 Self::Light | Self::HighContrastLight => false,
186 Self::Custom(theme) | Self::System { theme, .. } => theme.is_dark,
187 }
188 }
189
190 #[inline]
192 #[must_use]
193 pub fn is_high_contrast(&self) -> bool {
194 match self {
195 Self::Dark | Self::Light => false,
196 Self::HighContrastDark | Self::HighContrastLight => true,
197 Self::Custom(theme) | Self::System { theme, .. } => theme.is_high_contrast,
198 }
199 }
200
201 #[inline]
202 pub fn prefer_dark(&mut self, new_prefer_dark: Option<bool>) {
205 if let Self::System { prefer_dark, .. } = self {
206 *prefer_dark = new_prefer_dark;
207 }
208 }
209}
210
211#[must_use]
212#[derive(Debug, Clone, PartialEq, Default)]
213pub struct Theme {
214 pub theme_type: ThemeType,
215 pub layer: cosmic_theme::Layer,
216}
217
218impl Theme {
219 #[inline]
220 pub fn cosmic(&self) -> &cosmic_theme::Theme {
221 match self.theme_type {
222 ThemeType::Dark => &COSMIC_DARK,
223 ThemeType::Light => &COSMIC_LIGHT,
224 ThemeType::HighContrastDark => &COSMIC_HC_DARK,
225 ThemeType::HighContrastLight => &COSMIC_HC_LIGHT,
226 ThemeType::Custom(ref t) | ThemeType::System { theme: ref t, .. } => t.as_ref(),
227 }
228 }
229
230 #[inline]
231 pub fn dark() -> Self {
232 Self {
233 theme_type: ThemeType::Dark,
234 ..Default::default()
235 }
236 }
237
238 #[inline]
239 pub fn light() -> Self {
240 Self {
241 theme_type: ThemeType::Light,
242 ..Default::default()
243 }
244 }
245
246 #[inline]
247 pub fn dark_hc() -> Self {
248 Self {
249 theme_type: ThemeType::HighContrastDark,
250 ..Default::default()
251 }
252 }
253
254 #[inline]
255 pub fn light_hc() -> Self {
256 Self {
257 theme_type: ThemeType::HighContrastLight,
258 ..Default::default()
259 }
260 }
261
262 #[inline]
263 pub fn custom(theme: Arc<CosmicTheme>) -> Self {
264 Self {
265 theme_type: ThemeType::Custom(theme),
266 ..Default::default()
267 }
268 }
269
270 #[inline]
271 pub fn system(theme: Arc<CosmicTheme>) -> Self {
272 Self {
273 theme_type: ThemeType::System {
274 theme,
275 prefer_dark: None,
276 },
277 ..Default::default()
278 }
279 }
280
281 #[inline]
282 pub fn current_container(&self) -> &cosmic_theme::Container {
285 match self.layer {
286 cosmic_theme::Layer::Background => &self.cosmic().background,
287 cosmic_theme::Layer::Primary => &self.cosmic().primary,
288 cosmic_theme::Layer::Secondary => &self.cosmic().secondary,
289 }
290 }
291
292 #[inline]
293 pub fn set_theme(&mut self, theme: ThemeType) {
295 self.theme_type = theme;
296 }
297}
298
299impl LayeredTheme for Theme {
300 #[inline]
301 fn set_layer(&mut self, layer: cosmic_theme::Layer) {
302 self.layer = layer;
303 }
304}
305
306impl DefaultStyle for Theme {
307 fn default_style(&self) -> Appearance {
308 let cosmic = self.cosmic();
309 Appearance {
310 icon_color: cosmic.bg_color().into(),
311 background_color: cosmic.bg_color().into(),
312 text_color: cosmic.on_bg_color().into(),
313 }
314 }
315}