wip: support desktop portal color-scheme, and accent variables
This commit is contained in:
parent
ce45af20f8
commit
c390b2614d
7 changed files with 264 additions and 25 deletions
|
|
@ -57,7 +57,7 @@ xdg-portal = ["ashpd"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
apply = "0.3.0"
|
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 }
|
async-fs = { version = "2.1", optional = true }
|
||||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "e65fa5e", optional = true }
|
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "e65fa5e", optional = true }
|
||||||
cosmic-config = { path = "cosmic-config" }
|
cosmic-config = { path = "cosmic-config" }
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
DARK_PALETTE, LIGHT_PALETTE, NAME,
|
DARK_PALETTE, LIGHT_PALETTE, NAME,
|
||||||
};
|
};
|
||||||
use cosmic_config::{Config, CosmicConfigEntry};
|
use cosmic_config::{Config, CosmicConfigEntry};
|
||||||
use palette::{IntoColor, Srgb, Srgba};
|
use palette::{IntoColor, Oklcha, Srgb, Srgba};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
|
@ -495,6 +495,31 @@ impl Theme {
|
||||||
.map_err(|e| (vec![e], Self::default()))?;
|
.map_err(|e| (vec![e], Self::default()))?;
|
||||||
Self::get_entry(&config)
|
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 {
|
impl From<CosmicPalette> for Theme {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
apply = "0.3.0"
|
apply = "0.3.0"
|
||||||
fraction = "0.14.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"
|
once_cell = "1.18"
|
||||||
slotmap = "1.0.6"
|
slotmap = "1.0.6"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use std::collections::HashMap;
|
||||||
use cosmic_config::CosmicConfigEntry;
|
use cosmic_config::CosmicConfigEntry;
|
||||||
use cosmic_theme::ThemeMode;
|
use cosmic_theme::ThemeMode;
|
||||||
use iced_core::window::Id;
|
use iced_core::window::Id;
|
||||||
|
use palette::Srgba;
|
||||||
|
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
|
|
||||||
|
|
@ -59,9 +60,15 @@ pub struct Core {
|
||||||
/// Last known system theme
|
/// Last known system theme
|
||||||
pub(super) system_theme: Theme,
|
pub(super) system_theme: Theme,
|
||||||
|
|
||||||
/// Theme mode
|
/// Configured theme mode
|
||||||
pub(super) system_theme_mode: ThemeMode,
|
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(super) title: HashMap<Id, String>,
|
||||||
|
|
||||||
pub window: Window,
|
pub window: Window,
|
||||||
|
|
@ -121,6 +128,9 @@ impl Default for Core {
|
||||||
single_instance: false,
|
single_instance: false,
|
||||||
#[cfg(feature = "dbus-config")]
|
#[cfg(feature = "dbus-config")]
|
||||||
settings_daemon: None,
|
settings_daemon: None,
|
||||||
|
portal_is_dark: None,
|
||||||
|
portal_accent: None,
|
||||||
|
portal_is_high_contrast: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -260,4 +270,11 @@ impl Core {
|
||||||
T::VERSION,
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ use iced_futures::event::listen_with;
|
||||||
use iced_runtime::command::Action;
|
use iced_runtime::command::Action;
|
||||||
#[cfg(not(feature = "wayland"))]
|
#[cfg(not(feature = "wayland"))]
|
||||||
use iced_runtime::window::Action as WindowAction;
|
use iced_runtime::window::Action as WindowAction;
|
||||||
|
use palette::color_difference::EuclideanDistance;
|
||||||
|
|
||||||
/// A message managed internally by COSMIC.
|
/// A message managed internally by COSMIC.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -72,6 +73,8 @@ pub enum Message {
|
||||||
/// Activate the application
|
/// Activate the application
|
||||||
Activate(String),
|
Activate(String),
|
||||||
ShowWindowMenu,
|
ShowWindowMenu,
|
||||||
|
#[cfg(feature = "xdg-portal")]
|
||||||
|
DesktopSettings(crate::theme::portal::Desktop),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -210,6 +213,10 @@ where
|
||||||
.single_instance
|
.single_instance
|
||||||
.then(|| super::single_instance_subscription::<T>())
|
.then(|| super::single_instance_subscription::<T>())
|
||||||
.unwrap_or_else(Subscription::none),
|
.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 {
|
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.
|
// Apply last-known system theme if the system theme is preferred.
|
||||||
if let ThemeType::System(_) = theme.theme_type {
|
if let ThemeType::System(_) = theme.theme_type {
|
||||||
self.app.core_mut().theme_sub_counter += 1;
|
self.app.core_mut().theme_sub_counter += 1;
|
||||||
|
|
||||||
theme = self.app.core().system_theme.clone();
|
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| {
|
THEME.with(move |t| {
|
||||||
|
|
@ -375,12 +390,23 @@ impl<T: Application> Cosmic<T> {
|
||||||
Message::SystemThemeChange(theme) => {
|
Message::SystemThemeChange(theme) => {
|
||||||
// Record the last-known system theme in event that the current theme is custom.
|
// Record the last-known system theme in event that the current theme is custom.
|
||||||
self.app.core_mut().system_theme = theme.clone();
|
self.app.core_mut().system_theme = theme.clone();
|
||||||
|
let portal_accent = self.app.core().portal_accent;
|
||||||
THEME.with(move |t| {
|
THEME.with(move |t| {
|
||||||
let mut cosmic_theme = t.borrow_mut();
|
let mut cosmic_theme = t.borrow_mut();
|
||||||
|
|
||||||
// Only 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 {
|
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) => {
|
Message::SystemThemeModeChange(mode) => {
|
||||||
let core = self.app.core_mut();
|
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.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 {
|
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();
|
core.system_theme = new_theme.clone();
|
||||||
THEME.with(move |t| {
|
THEME.with(move |t| {
|
||||||
let mut cosmic_theme = t.borrow_mut();
|
let mut cosmic_theme = t.borrow_mut();
|
||||||
|
|
@ -428,6 +472,64 @@ impl<T: Application> Cosmic<T> {
|
||||||
#[cfg(not(feature = "wayland"))]
|
#[cfg(not(feature = "wayland"))]
|
||||||
return window::show_window_menu(window::Id::MAIN);
|
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()
|
iced::Command::none()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
//! Contains the [`Theme`] type and its widget stylesheet implementations.
|
//! Contains the [`Theme`] type and its widget stylesheet implementations.
|
||||||
|
|
||||||
|
#[cfg(feature = "xdg-portal")]
|
||||||
|
pub mod portal;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
use cosmic_theme::ThemeMode;
|
use cosmic_theme::ThemeMode;
|
||||||
pub use style::*;
|
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_dark() -> Theme {
|
||||||
pub fn system_preference() -> Theme {
|
let Ok(helper) = crate::cosmic_theme::Theme::dark_config() 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();
|
return Theme::dark();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -124,6 +111,37 @@ pub fn system_preference() -> Theme {
|
||||||
Theme::system(Arc::new(t))
|
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]
|
#[must_use]
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
pub enum ThemeType {
|
pub enum ThemeType {
|
||||||
|
|
|
||||||
77
src/theme/portal.rs
Normal file
77
src/theme/portal.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue