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