libcosmic/src/theme/mod.rs

312 lines
8.6 KiB
Rust
Raw Normal View History

// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Contains the [`Theme`] type and its widget stylesheet implementations.
2023-08-15 10:51:59 +02:00
#[cfg(feature = "xdg-portal")]
pub mod portal;
pub mod style;
2023-06-12 12:08:14 -04:00
use cosmic_config::CosmicConfigEntry;
2025-03-19 19:24:09 +01:00
use cosmic_config::config_subscription;
2022-11-17 20:49:20 -05:00
use cosmic_theme::Component;
2023-02-27 17:42:17 -05:00
use cosmic_theme::LayeredTheme;
use cosmic_theme::Spacing;
use cosmic_theme::ThemeMode;
2023-06-12 12:08:14 -04:00
use iced_futures::Subscription;
2024-10-16 20:36:46 -04:00
use iced_runtime::{Appearance, DefaultStyle};
use std::sync::{Arc, Mutex};
pub use style::*;
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
pub type CosmicColor = ::palette::rgb::Srgba;
pub type CosmicComponent = cosmic_theme::Component;
pub type CosmicTheme = cosmic_theme::Theme;
lazy_static::lazy_static! {
2023-08-03 19:30:08 -04:00
pub static ref COSMIC_DARK: CosmicTheme = CosmicTheme::dark_default();
pub static ref COSMIC_HC_DARK: CosmicTheme = CosmicTheme::high_contrast_dark_default();
pub static ref COSMIC_LIGHT: CosmicTheme = CosmicTheme::light_default();
pub static ref COSMIC_HC_LIGHT: CosmicTheme = CosmicTheme::high_contrast_light_default();
pub static ref TRANSPARENT_COMPONENT: Component = Component {
2022-11-17 20:49:20 -05:00
base: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
hover: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
pressed: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
selected: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
selected_text: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
focus: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
on: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
2023-02-27 17:42:17 -05:00
on_disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
divider: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
border: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
disabled_border: CosmicColor::new(0.0, 0.0, 0.0, 0.0),
2022-11-17 20:49:20 -05:00
};
}
pub(crate) static THEME: Mutex<Theme> = Mutex::new(Theme {
theme_type: ThemeType::Dark,
layer: cosmic_theme::Layer::Background,
});
/// Currently-defined theme.
#[inline]
#[allow(clippy::missing_panics_doc)]
pub fn active() -> Theme {
THEME.lock().unwrap().clone()
}
/// Currently-defined theme type.
#[inline]
#[allow(clippy::missing_panics_doc)]
pub fn active_type() -> ThemeType {
THEME.lock().unwrap().theme_type.clone()
}
2025-03-19 19:24:09 +01:00
/// Preferred interface spacing parameters defined by the active theme.
#[inline]
2025-03-19 19:24:09 +01:00
pub fn spacing() -> Spacing {
active().cosmic().spacing
}
/// Whether the active theme has a dark preference.
#[inline]
#[must_use]
pub fn is_dark() -> bool {
active_type().is_dark()
}
/// Whether the active theme is high contrast.
#[inline]
#[must_use]
pub fn is_high_contrast() -> bool {
active_type().is_high_contrast()
}
2025-06-23 17:50:28 +02:00
// /// Watches for changes to the system's theme preference.
// #[cold]
// pub fn subscription(is_dark: bool) -> Subscription<crate::theme::Theme> {
// config_subscription::<_, crate::cosmic_theme::Theme>(
// (
// std::any::TypeId::of::<crate::cosmic_theme::Theme>(),
// is_dark,
// ),
// if is_dark {
// cosmic_theme::DARK_THEME_ID
// } else {
// cosmic_theme::LIGHT_THEME_ID
// }
// .into(),
// crate::cosmic_theme::Theme::VERSION,
// )
// .map(|res| {
// for error in res.errors.into_iter().filter(cosmic_config::Error::is_err) {
// tracing::error!(
// ?error,
// "error while watching system theme preference changes"
// );
// }
2024-01-18 19:01:11 -05:00
2025-06-23 17:50:28 +02:00
// Theme::system(Arc::new(res.config))
// })
// }
pub fn system_dark() -> Theme {
let Ok(helper) = crate::cosmic_theme::Theme::dark_config() else {
return Theme::dark();
};
let t = crate::cosmic_theme::Theme::get_entry(&helper).unwrap_or_else(|(errors, theme)| {
for error in errors.into_iter().filter(cosmic_config::Error::is_err) {
tracing::error!(?error, "error loading system dark theme");
}
theme
});
Theme::system(Arc::new(t))
}
pub fn system_light() -> Theme {
let Ok(helper) = crate::cosmic_theme::Theme::light_config() else {
return Theme::light();
};
let t = crate::cosmic_theme::Theme::get_entry(&helper).unwrap_or_else(|(errors, theme)| {
for error in errors.into_iter().filter(cosmic_config::Error::is_err) {
tracing::error!(?error, "error loading system light theme");
}
theme
});
Theme::system(Arc::new(t))
}
/// Loads the preferred system theme from `cosmic-config`.
pub fn system_preference() -> Theme {
let Ok(mode_config) = ThemeMode::config() else {
return Theme::dark();
};
let Ok(is_dark) = ThemeMode::is_dark(&mode_config) else {
return Theme::dark();
};
if is_dark {
system_dark()
} else {
system_light()
}
}
#[must_use]
2023-06-09 17:13:01 -04:00
#[derive(Debug, Clone, PartialEq, Default)]
2023-02-27 19:56:53 -05:00
pub enum ThemeType {
#[default]
Dark,
Light,
HighContrastDark,
HighContrastLight,
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
Custom(Arc<CosmicTheme>),
System {
prefer_dark: Option<bool>,
theme: Arc<CosmicTheme>,
},
2023-02-27 17:42:17 -05:00
}
impl ThemeType {
/// Whether the theme has a dark preference.
#[must_use]
#[inline]
pub fn is_dark(&self) -> bool {
match self {
Self::Dark | Self::HighContrastDark => true,
Self::Light | Self::HighContrastLight => false,
Self::Custom(theme) | Self::System { theme, .. } => theme.is_dark,
}
}
/// Whether the theme has a high contrast.
#[inline]
#[must_use]
pub fn is_high_contrast(&self) -> bool {
match self {
Self::Dark | Self::Light => false,
Self::HighContrastDark | Self::HighContrastLight => true,
Self::Custom(theme) | Self::System { theme, .. } => theme.is_high_contrast,
}
}
#[inline]
/// Prefer dark or light theme.
/// If `None`, the system preference is used.
pub fn prefer_dark(&mut self, new_prefer_dark: Option<bool>) {
if let Self::System { prefer_dark, .. } = self {
*prefer_dark = new_prefer_dark;
}
}
}
#[must_use]
2023-06-09 17:13:01 -04:00
#[derive(Debug, Clone, PartialEq, Default)]
2023-02-27 19:56:53 -05:00
pub struct Theme {
pub theme_type: ThemeType,
pub layer: cosmic_theme::Layer,
}
impl Theme {
#[inline]
pub fn cosmic(&self) -> &cosmic_theme::Theme {
2023-02-27 19:56:53 -05:00
match self.theme_type {
ThemeType::Dark => &COSMIC_DARK,
ThemeType::Light => &COSMIC_LIGHT,
ThemeType::HighContrastDark => &COSMIC_HC_DARK,
ThemeType::HighContrastLight => &COSMIC_HC_LIGHT,
ThemeType::Custom(ref t) | ThemeType::System { theme: ref t, .. } => t.as_ref(),
2023-02-27 19:56:53 -05:00
}
2023-02-27 17:42:17 -05:00
}
#[inline]
2023-02-27 19:56:53 -05:00
pub fn dark() -> Self {
Self {
theme_type: ThemeType::Dark,
..Default::default()
}
2023-02-27 17:42:17 -05:00
}
#[inline]
2023-02-27 19:56:53 -05:00
pub fn light() -> Self {
Self {
theme_type: ThemeType::Light,
..Default::default()
}
2023-02-27 17:42:17 -05:00
}
#[inline]
2023-02-27 19:56:53 -05:00
pub fn dark_hc() -> Self {
Self {
theme_type: ThemeType::HighContrastDark,
..Default::default()
}
2023-02-27 17:42:17 -05:00
}
#[inline]
2023-02-27 19:56:53 -05:00
pub fn light_hc() -> Self {
2023-02-27 17:42:17 -05:00
Self {
2023-02-27 19:56:53 -05:00
theme_type: ThemeType::HighContrastLight,
..Default::default()
2023-02-27 17:42:17 -05:00
}
}
#[inline]
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
pub fn custom(theme: Arc<CosmicTheme>) -> Self {
Self {
theme_type: ThemeType::Custom(theme),
..Default::default()
}
}
#[inline]
pub fn system(theme: Arc<CosmicTheme>) -> Self {
Self {
theme_type: ThemeType::System {
theme,
prefer_dark: None,
},
..Default::default()
}
}
#[inline]
2023-02-27 19:56:53 -05:00
/// get current container
/// can be used in a component that is intended to be a child of a `CosmicContainer`
pub fn current_container(&self) -> &cosmic_theme::Container {
2023-02-27 19:56:53 -05:00
match self.layer {
cosmic_theme::Layer::Background => &self.cosmic().background,
cosmic_theme::Layer::Primary => &self.cosmic().primary,
cosmic_theme::Layer::Secondary => &self.cosmic().secondary,
}
}
#[inline]
/// set the theme
pub fn set_theme(&mut self, theme: ThemeType) {
self.theme_type = theme;
}
}
2023-02-27 17:42:17 -05:00
impl LayeredTheme for Theme {
#[inline]
2023-02-27 17:42:17 -05:00
fn set_layer(&mut self, layer: cosmic_theme::Layer) {
2023-02-27 19:56:53 -05:00
self.layer = layer;
}
}
2024-10-16 20:36:46 -04:00
impl DefaultStyle for Theme {
fn default_style(&self) -> Appearance {
let cosmic = self.cosmic();
Appearance {
icon_color: cosmic.bg_color().into(),
background_color: cosmic.bg_color().into(),
text_color: cosmic.on_bg_color().into(),
}
}
}