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