wip: support desktop portal color-scheme, and accent variables

This commit is contained in:
Ashley Wulber 2024-02-21 17:34:42 -05:00 committed by Ashley Wulber
parent ce45af20f8
commit c390b2614d
7 changed files with 264 additions and 25 deletions

View file

@ -57,7 +57,7 @@ xdg-portal = ["ashpd"]
[dependencies]
apply = "0.3.0"
ashpd = { version = "0.6.8", default-features = false, optional = true }
ashpd = { version = "0.7.0", default-features = false, optional = true }
async-fs = { version = "2.1", optional = true }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "e65fa5e", optional = true }
cosmic-config = { path = "cosmic-config" }

View file

@ -5,7 +5,7 @@ use crate::{
DARK_PALETTE, LIGHT_PALETTE, NAME,
};
use cosmic_config::{Config, CosmicConfigEntry};
use palette::{IntoColor, Srgb, Srgba};
use palette::{IntoColor, Oklcha, Srgb, Srgba};
use serde::{Deserialize, Serialize};
use std::num::NonZeroUsize;
@ -495,6 +495,31 @@ impl Theme {
.map_err(|e| (vec![e], Self::default()))?;
Self::get_entry(&config)
}
#[must_use]
/// Rebuild the current theme with the provided accent
pub fn with_accent(&self, c: Srgba) -> Self {
let mut oklcha: Oklcha = c.into_color();
let cur_oklcha: Oklcha = self.accent_color().into_color();
oklcha.l = cur_oklcha.l;
let adjusted_c: Srgb = oklcha.into_color();
let is_dark = self.is_dark;
let mut builder = if is_dark {
ThemeBuilder::dark_config()
.ok()
.and_then(|h| ThemeBuilder::get_entry(&h).ok())
.unwrap_or_else(ThemeBuilder::dark)
} else {
ThemeBuilder::light_config()
.ok()
.and_then(|h| ThemeBuilder::get_entry(&h).ok())
.unwrap_or_else(ThemeBuilder::light)
};
builder = builder.accent(adjusted_c);
builder.build()
}
}
impl From<CosmicPalette> for Theme {

View file

@ -8,7 +8,7 @@ publish = false
[dependencies]
apply = "0.3.0"
fraction = "0.14.0"
libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "dbus-config", "a11y", "wgpu" ] }
libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "dbus-config", "a11y", "wgpu", "xdg-portal"] }
once_cell = "1.18"
slotmap = "1.0.6"
env_logger = "0.10"

View file

@ -6,6 +6,7 @@ use std::collections::HashMap;
use cosmic_config::CosmicConfigEntry;
use cosmic_theme::ThemeMode;
use iced_core::window::Id;
use palette::Srgba;
use crate::Theme;
@ -59,9 +60,15 @@ pub struct Core {
/// Last known system theme
pub(super) system_theme: Theme,
/// Theme mode
/// Configured theme mode
pub(super) system_theme_mode: ThemeMode,
pub(super) portal_is_dark: Option<bool>,
pub(super) portal_accent: Option<Srgba>,
pub(super) portal_is_high_contrast: Option<bool>,
pub(super) title: HashMap<Id, String>,
pub window: Window,
@ -121,6 +128,9 @@ impl Default for Core {
single_instance: false,
#[cfg(feature = "dbus-config")]
settings_daemon: None,
portal_is_dark: None,
portal_accent: None,
portal_is_high_contrast: None,
}
}
}
@ -260,4 +270,11 @@ impl Core {
T::VERSION,
)
}
/// Whether the application should use a dark theme, according to the system
#[must_use]
pub fn system_is_dark(&self) -> bool {
self.portal_is_dark
.unwrap_or(self.system_theme_mode.is_dark)
}
}

View file

@ -26,6 +26,7 @@ use iced_futures::event::listen_with;
use iced_runtime::command::Action;
#[cfg(not(feature = "wayland"))]
use iced_runtime::window::Action as WindowAction;
use palette::color_difference::EuclideanDistance;
/// A message managed internally by COSMIC.
#[derive(Clone, Debug)]
@ -72,6 +73,8 @@ pub enum Message {
/// Activate the application
Activate(String),
ShowWindowMenu,
#[cfg(feature = "xdg-portal")]
DesktopSettings(crate::theme::portal::Desktop),
}
#[derive(Default)]
@ -210,6 +213,10 @@ where
.single_instance
.then(|| super::single_instance_subscription::<T>())
.unwrap_or_else(Subscription::none),
#[cfg(feature = "xdg-portal")]
crate::theme::portal::desktop_settings()
.map(|e| Message::DesktopSettings(e))
.map(super::Message::Cosmic),
];
if self.app.core().keyboard_nav {
@ -363,7 +370,15 @@ impl<T: Application> Cosmic<T> {
// Apply last-known system theme if the system theme is preferred.
if let ThemeType::System(_) = theme.theme_type {
self.app.core_mut().theme_sub_counter += 1;
theme = self.app.core().system_theme.clone();
let portal_accent = self.app.core().portal_accent;
if let Some(a) = portal_accent {
let t_inner = theme.cosmic();
if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
theme = Theme::system(Arc::new(t_inner.with_accent(a)));
}
};
}
THEME.with(move |t| {
@ -375,12 +390,23 @@ impl<T: Application> Cosmic<T> {
Message::SystemThemeChange(theme) => {
// Record the last-known system theme in event that the current theme is custom.
self.app.core_mut().system_theme = theme.clone();
let portal_accent = self.app.core().portal_accent;
THEME.with(move |t| {
let mut cosmic_theme = t.borrow_mut();
// 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);
let new_theme = if let Some(a) = portal_accent {
let t_inner = theme.cosmic();
if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
Theme::system(Arc::new(t_inner.with_accent(a)))
} else {
theme
}
} else {
theme
};
cosmic_theme.set_theme(new_theme.theme_type);
}
});
}
@ -395,11 +421,29 @@ impl<T: Application> Cosmic<T> {
}
Message::SystemThemeModeChange(mode) => {
let core = self.app.core_mut();
let changed = core.system_theme_mode.is_dark != mode.is_dark;
let prev_is_dark = core.system_is_dark();
core.system_theme_mode = mode;
core.theme_sub_counter += 1;
let is_dark = core.system_is_dark();
let changed = prev_is_dark != is_dark;
if changed {
let new_theme = crate::theme::system_preference();
core.theme_sub_counter += 1;
let mut new_theme = if is_dark {
crate::theme::system_dark()
} else {
crate::theme::system_light()
};
new_theme = if let Some(a) = core.portal_accent {
let t_inner = new_theme.cosmic();
if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
Theme::system(Arc::new(t_inner.with_accent(a)))
} else {
new_theme
}
} else {
new_theme
};
core.system_theme = new_theme.clone();
THEME.with(move |t| {
let mut cosmic_theme = t.borrow_mut();
@ -428,6 +472,64 @@ impl<T: Application> Cosmic<T> {
#[cfg(not(feature = "wayland"))]
return window::show_window_menu(window::Id::MAIN);
}
#[cfg(feature = "xdg-portal")]
Message::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => {
use ashpd::desktop::settings::ColorScheme;
let is_dark = match s {
ColorScheme::NoPreference => None,
ColorScheme::PreferDark => Some(true),
ColorScheme::PreferLight => Some(false),
};
let core = self.app.core_mut();
let prev_is_dark = core.system_is_dark();
core.portal_is_dark = is_dark;
let is_dark = core.system_is_dark();
let changed = prev_is_dark != is_dark;
if changed {
core.theme_sub_counter += 1;
let new_theme = if is_dark {
crate::theme::system_dark()
} else {
crate::theme::system_light()
};
core.system_theme = new_theme.clone();
THEME.with(move |t| {
let mut cosmic_theme = t.borrow_mut();
// 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(new_theme.theme_type);
}
});
}
}
#[cfg(feature = "xdg-portal")]
Message::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => {
use palette::{IntoColor, Oklch, Oklcha, Srgb, Srgba};
let c = Srgba::new(c.red() as f32, c.green() as f32, c.blue() as f32, 1.0);
let core = self.app.core_mut();
core.portal_accent = Some(c);
let cur_accent = core.system_theme.cosmic().accent_color();
if cur_accent.distance_squared(*c) < 0.00001 {
// skip calculations if we already have the same color
return iced::Command::none();
}
THEME.with(move |t| {
let mut cosmic_theme = t.borrow_mut();
// Only apply update if the theme is set to load a system theme
if let ThemeType::System(t) = cosmic_theme.theme_type.clone() {
cosmic_theme.set_theme(ThemeType::System(Arc::new(t.with_accent(c))));
}
});
}
#[cfg(feature = "xdg-portal")]
Message::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => {
// TODO when high contrast is integrated in settings and all custom themes
}
}
iced::Command::none()

View file

@ -3,6 +3,8 @@
//! Contains the [`Theme`] type and its widget stylesheet implementations.
#[cfg(feature = "xdg-portal")]
pub mod portal;
pub mod style;
use cosmic_theme::ThemeMode;
pub use style::*;
@ -94,23 +96,8 @@ pub fn subscription(is_dark: bool) -> Subscription<crate::theme::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::dark_config()
} else {
crate::cosmic_theme::Theme::light_config()
};
let Ok(helper) = helper else {
pub fn system_dark() -> Theme {
let Ok(helper) = crate::cosmic_theme::Theme::dark_config() else {
return Theme::dark();
};
@ -124,6 +111,37 @@ pub fn system_preference() -> Theme {
Theme::system(Arc::new(t))
}
pub fn system_light() -> 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 err in errors {
tracing::error!("{:?}", err);
}
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]
#[derive(Debug, Clone, PartialEq, Default)]
pub enum ThemeType {

77
src/theme/portal.rs Normal file
View file

@ -0,0 +1,77 @@
use ashpd::desktop::settings::{ColorScheme, Contrast};
use ashpd::desktop::Color;
use iced::futures::{self, select, FutureExt, SinkExt, StreamExt};
use iced_futures::subscription;
#[derive(Debug, Clone)]
pub enum Desktop {
Accent(Color),
ColorScheme(ColorScheme),
Contrast(Contrast),
}
pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
subscription::channel(std::any::TypeId::of::<Desktop>(), 10, |mut tx| {
async move {
let Ok(settings) = ashpd::desktop::settings::Settings::new().await else {
// wait forever
futures::future::pending::<()>().await;
unreachable!()
};
let mut color_scheme_stream = settings.receive_color_scheme_changed().await.ok();
let mut accent_stream = settings.receive_accent_color_changed().await.ok();
let mut contrast_stream = settings.receive_contrast_changed().await.ok();
loop {
let next_color_scheme = async {
if let Some(s) = color_scheme_stream.as_mut() {
return s.next().await;
}
futures::future::pending().await
};
let next_accent = async {
if let Some(s) = accent_stream.as_mut() {
// Item type is wrong in this version
// updating requires updating to zbus 4
return if s.next().await.is_some() {
settings.accent_color().await.ok()
} else {
None
};
}
futures::future::pending().await
};
let next_contrast = async {
if let Some(s) = contrast_stream.as_mut() {
return s.next().await;
}
futures::future::pending().await
};
select! {
s = next_color_scheme.fuse() => {
if let Some(s) = s {
_ = tx.send(Desktop::ColorScheme(s)).await;
} else {
color_scheme_stream = None;
}
},
a = next_accent.fuse() => {
if let Some(a) = a {
_ = tx.send(Desktop::Accent(a)).await;
} else {
accent_stream = None;
}
},
c = next_contrast.fuse() => {
if let Some(c) = c {
_ = tx.send(Desktop::Contrast(c)).await;
} else {
contrast_stream = None;
}
}
};
}
}
})
}