1#[cfg(xdg_portal)]
7pub mod portal;
8pub mod style;
9
10use ::iced::Alignment;
11use cosmic_config::{CosmicConfigEntry, config_subscription};
12use cosmic_theme::{Component, LayeredTheme, Spacing, ThemeMode};
13use iced_futures::Subscription;
14use iced_runtime::{Appearance, DefaultStyle};
15use std::sync::{Arc, LazyLock, Mutex};
16pub use style::*;
17
18pub type CosmicColor = ::palette::rgb::Srgba;
19pub type CosmicComponent = cosmic_theme::Component;
20pub type CosmicTheme = cosmic_theme::Theme;
21
22pub static COSMIC_DARK: LazyLock<CosmicTheme> = LazyLock::new(CosmicTheme::dark_default);
23
24pub static COSMIC_HC_DARK: LazyLock<CosmicTheme> =
25 LazyLock::new(CosmicTheme::high_contrast_dark_default);
26
27pub static COSMIC_LIGHT: LazyLock<CosmicTheme> = LazyLock::new(CosmicTheme::light_default);
28
29pub static COSMIC_HC_LIGHT: LazyLock<CosmicTheme> =
30 LazyLock::new(CosmicTheme::high_contrast_light_default);
31
32pub static TRANSPARENT_COMPONENT: LazyLock<Component> = LazyLock::new(|| Component {
33 base: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
34 hover: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
35 pressed: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
36 selected: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
37 selected_text: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
38 focus: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
39 disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
40 on: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
41 on_disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
42 divider: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
43 border: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
44 disabled_border: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
45});
46
47pub(crate) static THEME: Mutex<Theme> = Mutex::new(Theme {
48 theme_type: ThemeType::Dark,
49 layer: cosmic_theme::Layer::Background,
50 transparent: false,
51 list_item_position: None,
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
88pub fn system_dark() -> Theme {
89 let Ok(helper) = crate::cosmic_theme::Theme::dark_config() else {
90 return Theme::dark();
91 };
92
93 let t = crate::cosmic_theme::Theme::get_entry(&helper).unwrap_or_else(|(errors, theme)| {
94 for error in errors.into_iter().filter(cosmic_config::Error::is_err) {
95 tracing::error!(?error, "error loading system dark theme");
96 }
97 theme
98 });
99
100 Theme::system(Arc::new(t))
101}
102
103pub fn system_light() -> Theme {
104 let Ok(helper) = crate::cosmic_theme::Theme::light_config() else {
105 return Theme::light();
106 };
107
108 let t = crate::cosmic_theme::Theme::get_entry(&helper).unwrap_or_else(|(errors, theme)| {
109 for error in errors.into_iter().filter(cosmic_config::Error::is_err) {
110 tracing::error!(?error, "error loading system light theme");
111 }
112 theme
113 });
114
115 Theme::system(Arc::new(t))
116}
117
118pub fn system_preference() -> Theme {
120 let Ok(mode_config) = ThemeMode::config() else {
121 return Theme::dark();
122 };
123
124 let Ok(is_dark) = ThemeMode::is_dark(&mode_config) else {
125 return Theme::dark();
126 };
127 if is_dark {
128 system_dark()
129 } else {
130 system_light()
131 }
132}
133
134#[must_use]
135#[derive(Debug, Clone, PartialEq, Default)]
136pub enum ThemeType {
137 #[default]
138 Dark,
139 Light,
140 HighContrastDark,
141 HighContrastLight,
142 Custom(Arc<CosmicTheme>),
143 System {
144 prefer_dark: Option<bool>,
145 theme: Arc<CosmicTheme>,
146 },
147}
148
149impl ThemeType {
150 #[must_use]
152 #[inline]
153 pub fn is_dark(&self) -> bool {
154 match self {
155 Self::Dark | Self::HighContrastDark => true,
156 Self::Light | Self::HighContrastLight => false,
157 Self::Custom(theme) | Self::System { theme, .. } => theme.is_dark,
158 }
159 }
160
161 #[inline]
163 #[must_use]
164 pub fn is_high_contrast(&self) -> bool {
165 match self {
166 Self::Dark | Self::Light => false,
167 Self::HighContrastDark | Self::HighContrastLight => true,
168 Self::Custom(theme) | Self::System { theme, .. } => theme.is_high_contrast,
169 }
170 }
171
172 #[inline]
173 pub fn prefer_dark(&mut self, new_prefer_dark: Option<bool>) {
176 if let Self::System { prefer_dark, .. } = self {
177 *prefer_dark = new_prefer_dark;
178 }
179 }
180}
181
182#[must_use]
183#[derive(Debug, Clone, PartialEq, Default)]
184pub struct Theme {
185 pub theme_type: ThemeType,
186 pub layer: cosmic_theme::Layer,
187 pub transparent: bool,
188 pub list_item_position: Option<(Alignment, usize)>,
190}
191
192impl Theme {
193 #[inline]
194 pub fn cosmic(&self) -> &cosmic_theme::Theme {
195 match self.theme_type {
196 ThemeType::Dark => &COSMIC_DARK,
197 ThemeType::Light => &COSMIC_LIGHT,
198 ThemeType::HighContrastDark => &COSMIC_HC_DARK,
199 ThemeType::HighContrastLight => &COSMIC_HC_LIGHT,
200 ThemeType::Custom(ref t) | ThemeType::System { theme: ref t, .. } => t.as_ref(),
201 }
202 }
203
204 #[inline]
205 pub fn dark() -> Self {
206 Self {
207 theme_type: ThemeType::Dark,
208 ..Default::default()
209 }
210 }
211
212 #[inline]
213 pub fn light() -> Self {
214 Self {
215 theme_type: ThemeType::Light,
216 ..Default::default()
217 }
218 }
219
220 #[inline]
221 pub fn dark_hc() -> Self {
222 Self {
223 theme_type: ThemeType::HighContrastDark,
224 ..Default::default()
225 }
226 }
227
228 #[inline]
229 pub fn light_hc() -> Self {
230 Self {
231 theme_type: ThemeType::HighContrastLight,
232 ..Default::default()
233 }
234 }
235
236 #[inline]
237 pub fn custom(theme: Arc<CosmicTheme>) -> Self {
238 Self {
239 theme_type: ThemeType::Custom(theme),
240 ..Default::default()
241 }
242 }
243
244 #[inline]
245 pub fn system(theme: Arc<CosmicTheme>) -> Self {
246 Self {
247 theme_type: ThemeType::System {
248 theme,
249 prefer_dark: None,
250 },
251 ..Default::default()
252 }
253 }
254
255 #[inline]
256 pub fn current_container(&self) -> &cosmic_theme::Container {
259 match self.layer {
260 cosmic_theme::Layer::Background => self.cosmic().background(self.transparent),
261 cosmic_theme::Layer::Primary => self.cosmic().primary(self.transparent),
262 cosmic_theme::Layer::Secondary => self.cosmic().secondary(self.transparent),
263 }
264 }
265
266 #[inline]
267 pub fn set_theme(&mut self, theme: ThemeType) {
269 self.theme_type = theme;
270 }
271
272 #[inline]
273 pub fn with_list_item_position(&self, position: Option<(Alignment, usize)>) -> Self {
275 let mut new = self.clone();
276 new.list_item_position = position;
277 new
278 }
279}
280
281impl LayeredTheme for Theme {
282 #[inline]
283 fn set_layer(&mut self, layer: cosmic_theme::Layer) {
284 self.layer = layer;
285 }
286}
287
288impl DefaultStyle for Theme {
289 fn default_style(&self) -> Appearance {
290 let cosmic = self.cosmic();
291 Appearance {
292 icon_color: cosmic.on_bg_color().into(),
293 background_color: cosmic.bg_color().into(),
294 text_color: cosmic.on_bg_color().into(),
295 }
296 }
297}