From b1b32b867c96897913685a6d7b7cf7442bf18794 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 3 Oct 2023 13:26:35 -0400 Subject: [PATCH] feat: add support for dark / light mode switching and simultaneouscustom light / dark mode themes --- cosmic-config-derive/src/lib.rs | 8 ++--- cosmic-config/src/lib.rs | 2 -- cosmic-theme/Cargo.toml | 2 +- cosmic-theme/src/model/mod.rs | 2 ++ cosmic-theme/src/model/mode.rs | 46 ++++++++++++++++++++++++++ cosmic-theme/src/model/theme.rs | 57 +++++++++++++++++++++++++++++++-- src/app/core.rs | 27 ++++++++++++++++ src/app/cosmic.rs | 29 +++++++++++++++-- src/theme/mod.rs | 34 ++++++++++++++------ 9 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 cosmic-theme/src/model/mode.rs diff --git a/cosmic-config-derive/src/lib.rs b/cosmic-config-derive/src/lib.rs index 88984fa7..0e27afbb 100644 --- a/cosmic-config-derive/src/lib.rs +++ b/cosmic-config-derive/src/lib.rs @@ -28,7 +28,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let write_each_config_field = fields.iter().map(|field| { let field_name = &field.ident; quote! { - config.set(stringify!(#field_name), &self.#field_name)?; + cosmic_config::ConfigSet::set(config, stringify!(#field_name), &self.#field_name)?; } }); @@ -36,7 +36,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let field_name = &field.ident; let field_type = &field.ty; quote! { - match config.get::<#field_type>(stringify!(#field_name)) { + match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) { Ok(#field_name) => default.#field_name = #field_name, Err(e) => errors.push(e), } @@ -60,13 +60,13 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let gen = quote! { impl CosmicConfigEntry for #name { - fn write_entry(&self, config: &Config) -> Result<(), cosmic_config::Error> { + fn write_entry(&self, config: &cosmic_config::Config) -> Result<(), cosmic_config::Error> { let tx = config.transaction(); #(#write_each_config_field)* tx.commit() } - fn get_entry(config: &Config) -> Result, Self)> { + fn get_entry(config: &cosmic_config::Config) -> Result, Self)> { let mut default = Self::default(); let mut errors = Vec::new(); diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 9abff0b0..374bf29d 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -390,8 +390,6 @@ async fn start_listening< Ok(w) => w, Err(_) => return ConfigState::Failed, }; - let msg = T::get_entry(&config); - _ = output.send((id, msg)).await; match T::get_entry(&config) { Ok(t) => { diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index a6613217..0ded5411 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -23,4 +23,4 @@ serde = { version = "1.0.129", features = ["derive"] } ron = "0.8" lazy_static = "1.4.0" csscolorparser = {version = "0.6.2", features = ["serde"]} -cosmic-config = { path = "../cosmic-config/", default-features = false, features = ["subscription"] } +cosmic-config = { path = "../cosmic-config/", default-features = false, features = ["subscription", "macro"] } diff --git a/cosmic-theme/src/model/mod.rs b/cosmic-theme/src/model/mod.rs index 5751e231..19370dee 100644 --- a/cosmic-theme/src/model/mod.rs +++ b/cosmic-theme/src/model/mod.rs @@ -1,11 +1,13 @@ pub use corner::*; pub use cosmic_palette::*; pub use derivation::*; +pub use mode::*; pub use spacing::*; pub use theme::*; mod corner; mod cosmic_palette; mod derivation; +mod mode; mod spacing; mod theme; diff --git a/cosmic-theme/src/model/mode.rs b/cosmic-theme/src/model/mode.rs new file mode 100644 index 00000000..85853dd6 --- /dev/null +++ b/cosmic-theme/src/model/mode.rs @@ -0,0 +1,46 @@ +use cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry}; + +/// ID for the ThemeMode config +pub const THEME_MODE_ID: &str = "com.system76.CosmicTheme.Mode"; + +/// The config for cosmic theme dark / light settings +#[derive( + Debug, Clone, Copy, PartialEq, Eq, cosmic_config::cosmic_config_derive::CosmicConfigEntry, +)] +pub struct ThemeMode { + /// The theme dark mode setting. + pub is_dark: bool, + /// The theme auto-switch dark and light mode setting. + pub auto_switch: bool, +} + +impl Default for ThemeMode { + fn default() -> Self { + Self { + is_dark: true, + auto_switch: false, + } + } +} + +impl ThemeMode { + /// Check if the theme is currently using dark mode + pub fn is_dark(config: &Config) -> Result { + config.get::("is_dark") + } + + /// version of the theme + pub fn version() -> u64 { + 1 + } + + /// Set auto-switch from light to dark mode + pub fn set_auto_switch(config: &Config, value: bool) -> Result<(), cosmic_config::Error> { + config.set("auto_switch", value) + } + + /// Get the config for the theme mode + pub fn config() -> Result { + Config::new(THEME_MODE_ID, Self::version()) + } +} diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 1411e98a..abe7f757 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,12 +1,24 @@ use crate::{ composite::over, steps::*, Component, Container, CornerRadii, CosmicPalette, - CosmicPaletteInner, Spacing, DARK_PALETTE, LIGHT_PALETTE, NAME, + CosmicPaletteInner, Spacing, ThemeMode, DARK_PALETTE, LIGHT_PALETTE, NAME, }; use cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry}; use palette::{IntoColor, Srgb, Srgba}; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; +/// ID for the current dark ThemeBuilder config +pub const DARK_THEME_BUILDER_ID: &str = "com.system76.CosmicTheme.Dark.Builder"; + +/// ID for the current dark Theme config +pub const DARK_THEME_ID: &str = "com.system76.CosmicTheme.Dark"; + +/// ID for the current light ThemeBuilder config +pub const LIGHT_THEME_BUILDER_ID: &str = "com.system76.CosmicTheme.Light.Builder"; + +/// ID for the current light Theme config +pub const LIGHT_THEME_ID: &str = "com.system76.CosmicTheme.Light"; + #[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] /// Theme layer type pub enum Layer { @@ -66,7 +78,7 @@ pub struct Theme { pub is_high_contrast: bool, } -impl CosmicConfigEntry for Theme { +impl cosmic_config::CosmicConfigEntry for Theme { fn write_entry(&self, config: &Config) -> Result<(), cosmic_config::Error> { let self_ = self.clone(); // TODO do as transaction @@ -176,6 +188,16 @@ impl Theme { pub fn id() -> &'static str { NAME } + + /// Get the config for the current dark theme + pub fn dark_config() -> Result { + Config::new(DARK_THEME_ID, Self::version()) + } + + /// Get the config for the current light theme + pub fn light_config() -> Result { + Config::new(LIGHT_THEME_ID, Self::version()) + } } impl Theme { @@ -410,6 +432,20 @@ impl Theme { pub fn radius_xl(&self) -> [f32; 4] { self.corner_radii.radius_xl } + + /// get the active theme + pub fn get_active() -> Result, Self)> { + let config = + Config::new(Self::id(), Self::version()).map_err(|e| (vec![e], Self::default()))?; + let is_dark = ThemeMode::is_dark(&config).map_err(|e| (vec![e], Self::default()))?; + let config = if is_dark { + Self::dark_config() + } else { + Self::light_config() + } + .map_err(|e| (vec![e], Self::default()))?; + Self::get_entry(&config) + } } impl From> for Theme @@ -422,7 +458,7 @@ where } /// Helper for building customized themes -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, cosmic_config::cosmic_config_derive::CosmicConfigEntry)] pub struct ThemeBuilder { palette: CosmicPalette, spacing: Spacing, @@ -890,4 +926,19 @@ impl ThemeBuilder { theme.corner_radii = corner_radii; theme } + + /// Get the builder for the dark config + pub fn dark_config() -> Result { + Config::new(DARK_THEME_BUILDER_ID, Self::version()) + } + + /// Get the builder for the light config + pub fn light_config() -> Result { + Config::new(LIGHT_THEME_BUILDER_ID, Self::version()) + } + + /// version of the theme builder + pub fn version() -> u64 { + 1 + } } diff --git a/src/app/core.rs b/src/app/core.rs index f23217c9..7cab04cc 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -1,6 +1,9 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +use cosmic_config::CosmicConfigEntry; +use cosmic_theme::ThemeMode; + use crate::Theme; /// Status of the nav bar and its panels. @@ -49,6 +52,9 @@ pub struct Core { /// Last known system theme pub(super) system_theme: Theme, + /// Theme mode + pub(super) system_theme_mode: ThemeMode, + pub(super) title: String, pub window: Window, @@ -70,6 +76,16 @@ impl Default for Core { scale_factor: 1.0, title: String::new(), system_theme: crate::theme::active(), + system_theme_mode: ThemeMode::config() + .map(|c| { + ThemeMode::get_entry(&c).unwrap_or_else(|(errors, mode)| { + for e in errors { + tracing::error!("{e}"); + } + mode + }) + }) + .unwrap_or_default(), window: Window { context_title: String::new(), header_title: String::new(), @@ -169,4 +185,15 @@ impl Core { self.window.width = new_width; self.is_condensed_update(); } + + /// Get the current system theme + pub fn system_theme(&self) -> &Theme { + &self.system_theme + } + + #[must_use] + /// Get the current system theme mode + pub fn system_theme_mode(&self) -> ThemeMode { + self.system_theme_mode + } } diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index e1cc257f..5ddde355 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -5,6 +5,7 @@ use super::{command, Application, ApplicationExt, Core, Subscription}; use crate::theme::{self, Theme, ThemeType, THEME}; use crate::widget::nav_bar; use crate::{keyboard_nav, Element}; +use cosmic_theme::ThemeMode; #[cfg(feature = "wayland")] use iced::event::wayland::{self, WindowEvent}; #[cfg(feature = "wayland")] @@ -44,6 +45,8 @@ pub enum Message { ToggleNavBarCondensed, /// Notification of system theme changes. SystemThemeChange(Theme), + /// Notification of system theme mode changes. + SystemThemeModeChange(ThemeMode), /// Updates the tracked window geometry. WindowResize(window::Id, u32, u32), /// Tracks updates to window state. @@ -152,9 +155,27 @@ where keyboard_nav::subscription() .map(Message::KeyboardNav) .map(super::Message::Cosmic), - theme::subscription(0) + theme::subscription(0, self.app.core().system_theme_mode.is_dark) .map(Message::SystemThemeChange) .map(super::Message::Cosmic), + cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>( + 0, + cosmic_theme::THEME_MODE_ID.into(), + cosmic_theme::ThemeMode::version(), + ) + .map(|(_, u)| { + tracing::error!("{u:?}"); + match u { + Ok(t) => Message::SystemThemeModeChange(t), + Err((errors, t)) => { + for e in errors { + tracing::error!("{e}"); + } + Message::SystemThemeModeChange(t) + } + } + }) + .map(super::Message::Cosmic), window_events.map(super::Message::Cosmic), ]) } @@ -295,7 +316,7 @@ impl Cosmic { THEME.with(move |t| { let mut cosmic_theme = t.borrow_mut(); - // Anly apply update if the theme is set to load a system theme + // Only apply update if the theme is set to load a system theme if let ThemeType::System(_) = cosmic_theme.theme_type { cosmic_theme.set_theme(theme.theme_type); } @@ -310,6 +331,10 @@ impl Cosmic { self.app.on_app_exit(); return self.close(); } + Message::SystemThemeModeChange(mode) => { + let core = self.app.core_mut(); + core.system_theme_mode = mode; + } } iced::Command::none() diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 979e941c..db691170 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -4,11 +4,11 @@ //! 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::util::CssColor; use cosmic_theme::Component; use cosmic_theme::LayeredTheme; use iced_futures::Subscription; @@ -68,10 +68,15 @@ pub fn is_high_contrast() -> bool { } /// Watches for changes to the system's theme preference. -pub fn subscription(id: u64) -> Subscription { - config_subscription::>( - id, - crate::cosmic_theme::NAME.into(), +pub fn subscription(id: u64, is_dark: bool) -> Subscription { + config_subscription::<_, crate::cosmic_theme::Theme>( + (id, is_dark), + if is_dark { + cosmic_theme::DARK_THEME_ID + } else { + cosmic_theme::LIGHT_THEME_ID + } + .into(), crate::cosmic_theme::Theme::::version(), ) .map(|(_, res)| { @@ -88,10 +93,21 @@ pub fn subscription(id: u64) -> Subscription { /// Loads the preferred system theme from `cosmic-config`. pub fn system_preference() -> Theme { - let Ok(helper) = crate::cosmic_config::Config::new( - crate::cosmic_theme::NAME, - crate::cosmic_theme::Theme::::version(), - ) else { + 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::::dark_config() + } else { + crate::cosmic_theme::Theme::::light_config() + }; + + let Ok(helper) = helper else { return Theme::dark(); };