libcosmic/src/theme/mod.rs
Ashley Wulber 7cc791a3f5
feat: add support for dark / light mode switching (#178)
* feat: add support for dark / light mode switching and simultaneouscustom light / dark mode themes

* refactor(color-picker): optional initial color and fallback color

* refactor: used FixedPortion for layout of the settings item

This makes sure that the control always has at least the specified portion of the available space

* refactor: make all members of the ThemeBuilder public

* refactor: add and update palette colors

* fix(theme): typo and derive PartialEq for ThemeBuilder

* fix: update color picker usage

* feat: add more variables to the theme

* fix: radius on headerbar

* fix: Theme CosmicConfigEntry impl

* chore: specify rev of taffy

* fix: theme CosmicConfigEntry missing variables

* fix: apply theme type when theme mode changes

* wip: add plus icon to empty color picker button

* chore: fix rev and imports

* refactor(color-picker): allow custom size for the icon

* refactor(color_picker): make color_button public

* update iced
2023-10-16 16:19:04 -04:00

240 lines
6.8 KiB
Rust

// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Contains the [`Theme`] type and its widget stylesheet implementations.
pub mod style;
use cosmic_theme::ThemeMode;
pub use style::*;
use cosmic_config::config_subscription;
use cosmic_config::CosmicConfigEntry;
use cosmic_theme::Component;
use cosmic_theme::LayeredTheme;
use iced_futures::Subscription;
use palette::Srgba;
use std::cell::RefCell;
use std::sync::Arc;
pub type CosmicColor = ::palette::rgb::Srgba;
pub type CosmicComponent = cosmic_theme::Component<CosmicColor>;
pub type CosmicTheme = cosmic_theme::Theme<CosmicColor>;
lazy_static::lazy_static! {
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<CosmicColor> = Component {
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),
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),
};
}
thread_local! {
pub(crate) static THEME: RefCell<Theme> = RefCell::new(Theme { theme_type: ThemeType::Dark, layer: cosmic_theme::Layer::Background });
}
/// Currently-defined theme.
pub fn active() -> Theme {
THEME.with(|theme| theme.borrow().clone())
}
/// Currently-defined theme type.
pub fn active_type() -> ThemeType {
THEME.with(|theme| theme.borrow().theme_type.clone())
}
/// Whether the active theme has a dark preference.
#[must_use]
pub fn is_dark() -> bool {
active_type().is_dark()
}
/// Whether the active theme is high contrast.
#[must_use]
pub fn is_high_contrast() -> bool {
active_type().is_high_contrast()
}
/// Watches for changes to the system's theme preference.
pub fn subscription(id: u64, is_dark: bool) -> Subscription<crate::theme::Theme> {
config_subscription::<_, crate::cosmic_theme::Theme<Srgba>>(
(id, is_dark),
if is_dark {
cosmic_theme::DARK_THEME_ID
} else {
cosmic_theme::LIGHT_THEME_ID
}
.into(),
crate::cosmic_theme::Theme::<Srgba>::version(),
)
.map(|(_, res)| {
let theme = res.unwrap_or_else(|(errors, theme)| {
for err in errors {
tracing::error!("{:?}", err);
}
theme
});
Theme::system(Arc::new(theme))
})
}
/// 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();
};
let helper = if is_dark {
crate::cosmic_theme::Theme::<Srgba>::dark_config()
} else {
crate::cosmic_theme::Theme::<Srgba>::light_config()
};
let Ok(helper) = helper else {
return Theme::dark();
};
let t = crate::cosmic_theme::Theme::get_entry(&helper).unwrap_or_else(|(errors, theme)| {
for err in errors {
tracing::error!("{:?}", err);
}
theme
});
Theme::system(Arc::new(t))
}
#[must_use]
#[derive(Debug, Clone, PartialEq, Default)]
pub enum ThemeType {
#[default]
Dark,
Light,
HighContrastDark,
HighContrastLight,
Custom(Arc<CosmicTheme>),
System(Arc<CosmicTheme>),
}
impl ThemeType {
/// Whether the theme has a dark preference.
#[must_use]
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.
#[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,
}
}
}
#[must_use]
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Theme {
pub theme_type: ThemeType,
pub layer: cosmic_theme::Layer,
}
impl Theme {
#[must_use]
pub fn cosmic(&self) -> &cosmic_theme::Theme<Srgba> {
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(ref t) => t.as_ref(),
}
}
pub fn dark() -> Self {
Self {
theme_type: ThemeType::Dark,
..Default::default()
}
}
pub fn light() -> Self {
Self {
theme_type: ThemeType::Light,
..Default::default()
}
}
pub fn dark_hc() -> Self {
Self {
theme_type: ThemeType::HighContrastDark,
..Default::default()
}
}
pub fn light_hc() -> Self {
Self {
theme_type: ThemeType::HighContrastLight,
..Default::default()
}
}
pub fn custom(theme: Arc<CosmicTheme>) -> Self {
Self {
theme_type: ThemeType::Custom(theme),
..Default::default()
}
}
pub fn system(theme: Arc<CosmicTheme>) -> Self {
Self {
theme_type: ThemeType::System(theme),
..Default::default()
}
}
/// get current container
/// can be used in a component that is intended to be a child of a `CosmicContainer`
#[must_use]
pub fn current_container(&self) -> &cosmic_theme::Container<Srgba> {
match self.layer {
cosmic_theme::Layer::Background => &self.cosmic().background,
cosmic_theme::Layer::Primary => &self.cosmic().primary,
cosmic_theme::Layer::Secondary => &self.cosmic().secondary,
}
}
/// set the theme
pub fn set_theme(&mut self, theme: ThemeType) {
self.theme_type = theme;
}
}
impl LayeredTheme for Theme {
fn set_layer(&mut self, layer: cosmic_theme::Layer) {
self.layer = layer;
}
}