From 9d51e8fda4d4268a589e7d0bd5c1c24eba4cfdd0 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 14 Apr 2026 23:44:01 -0400 Subject: [PATCH] feat(blur): better align with designs and remove transparency from theme when not on wayland --- cosmic-theme/src/model/theme.rs | 84 +++++++++++++++++--------- iced | 2 +- src/app/cosmic.rs | 104 +++++++++++++++++++++++++++++--- src/applet/mod.rs | 5 +- src/core.rs | 25 ++++++++ src/theme/mod.rs | 23 +++++++ src/theme/style/iced.rs | 18 +++++- src/theme/style/menu_bar.rs | 4 +- 8 files changed, 223 insertions(+), 42 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 56654480..0b2976c9 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -10,7 +10,7 @@ use palette::{ IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb, }; use serde::{Deserialize, Serialize}; -use std::num::NonZeroUsize; +use std::{default, num::NonZeroUsize}; /// ID for the current dark `ThemeBuilder` config pub const DARK_THEME_BUILDER_ID: &str = "com.system76.CosmicTheme.Dark.Builder"; @@ -90,8 +90,15 @@ pub struct Theme { /// cosmic-comp custom window hint color pub window_hint: Option, /// enables blurred transparency - /// If None, frosted effect is disabled. - pub frosted: Option, + pub frosted: BlurStrength, + /// frosted windows + pub frosted_windows: bool, + /// frosted system interface + pub frosted_system_interface: bool, + /// frosted panel + pub frosted_panel: bool, + /// frosted applet popups + pub frosted_applets: bool, /// shade color for dialogs #[serde(with = "color_serde")] #[cosmic_config_entry(with = ColorRepr)] @@ -815,7 +822,7 @@ pub struct ThemeBuilder { #[cosmic_config_entry(with = ColorReprOption)] pub destructive: Option, /// enabled blurred transparency - pub frosted: Option, + pub frosted: BlurStrength, /// cosmic-comp window gaps size (outer, inner) pub gaps: (u32, u32), /// cosmic-comp active hint window outline width @@ -824,6 +831,14 @@ pub struct ThemeBuilder { #[serde(with = "color_serde_option")] #[cosmic_config_entry(with = ColorReprOption)] pub window_hint: Option, + /// frosted windows + pub frosted_windows: bool, + /// frosted system interface + pub frosted_system_interface: bool, + /// frosted panel + pub frosted_panel: bool, + /// frosted applet popups + pub frosted_applets: bool, } impl Default for ThemeBuilder { @@ -841,11 +856,15 @@ impl Default for ThemeBuilder { success: Default::default(), warning: Default::default(), destructive: Default::default(), - frosted: None, + frosted: BlurStrength::default(), // cosmic-comp theme settings gaps: (0, 8), active_hint: 3, window_hint: None, + frosted_windows: false, + frosted_system_interface: false, + frosted_panel: false, + frosted_applets: false, } } } @@ -988,9 +1007,13 @@ impl ThemeBuilder { active_hint, window_hint, frosted, + frosted_windows, + frosted_system_interface, + frosted_panel, + frosted_applets, } = self; - let container_alpha = frosted.map_or(1.0, |f| f.alpha()); + let container_alpha = frosted.alpha(); let is_dark = palette.is_dark(); let is_high_contrast = palette.is_high_contrast(); @@ -1080,7 +1103,7 @@ impl ThemeBuilder { } else { get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1]) }; - container_bg.alpha = container_alpha; + container_bg.alpha = (container_alpha + if is_dark { 0.3 } else { 0.25 }).min(1.0); let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap()); let base_index: usize = color_index(container_bg, step_array.len()); @@ -1203,7 +1226,7 @@ impl ThemeBuilder { } else { get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2]) }; - container_bg.alpha = container_alpha; + container_bg.alpha = (container_alpha + if is_dark { 0.6 } else { 0.5 }).min(1.0); let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap()); let base_index = color_index(container_bg, step_array.len()); @@ -1359,6 +1382,10 @@ impl ThemeBuilder { accent_text, control_tint: neutral_tint, text_tint, + frosted_windows, + frosted_system_interface, + frosted_panel, + frosted_applets, }; theme.spacing = spacing; theme.corner_radii = corner_radii; @@ -1378,15 +1405,18 @@ impl ThemeBuilder { } } +/// Actual blur radius is decided by cosmic-comp, +/// but this represents the strength of the blur effect. #[repr(u8)] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum BlurStrength { - ExtremelyLow = 1, + ExtremelyLow, ExtremelyLow2, VeryLow, VeryLow2, Low, Low2, + #[default] Medium, Medium2, High, @@ -1402,7 +1432,7 @@ impl BlurStrength { /// Lower alpha values correspond to stronger blur effects, and higher alpha values correspond to weaker blur effects. The mapping is as follows: pub fn alpha(&self) -> f32 { match self { - Self::ExtremelyLow => 0.95, + Self::ExtremelyLow => 0.90, Self::ExtremelyLow2 => 0.85, Self::VeryLow => 0.8, Self::VeryLow2 => 0.75, @@ -1414,8 +1444,8 @@ impl BlurStrength { Self::High2 => 0.45, Self::VeryHigh => 0.4, Self::VeryHigh2 => 0.35, - Self::ExtremelyHigh => 0.2, - Self::ExtremelyHigh2 => 0.05, + Self::ExtremelyHigh => 0.25, + Self::ExtremelyHigh2 => 0.2, } } } @@ -1425,20 +1455,20 @@ impl TryFrom for BlurStrength { fn try_from(value: u8) -> Result { match value { - 1 => Ok(BlurStrength::ExtremelyLow), - 2 => Ok(BlurStrength::ExtremelyLow2), - 3 => Ok(BlurStrength::VeryLow), - 4 => Ok(BlurStrength::VeryLow2), - 5 => Ok(BlurStrength::Low), - 6 => Ok(BlurStrength::Low2), - 7 => Ok(BlurStrength::Medium), - 8 => Ok(BlurStrength::Medium2), - 9 => Ok(BlurStrength::High), - 10 => Ok(BlurStrength::High2), - 11 => Ok(BlurStrength::VeryHigh), - 12 => Ok(BlurStrength::VeryHigh2), - 13 => Ok(BlurStrength::ExtremelyHigh), - 14 => Ok(BlurStrength::ExtremelyHigh2), + 0 => Ok(BlurStrength::ExtremelyLow), + 1 => Ok(BlurStrength::ExtremelyLow2), + 2 => Ok(BlurStrength::VeryLow), + 3 => Ok(BlurStrength::VeryLow2), + 4 => Ok(BlurStrength::Low), + 5 => Ok(BlurStrength::Low2), + 6 => Ok(BlurStrength::Medium), + 7 => Ok(BlurStrength::Medium2), + 8 => Ok(BlurStrength::High), + 9 => Ok(BlurStrength::High2), + 10 => Ok(BlurStrength::VeryHigh), + 11 => Ok(BlurStrength::VeryHigh2), + 12 => Ok(BlurStrength::ExtremelyHigh), + 13 => Ok(BlurStrength::ExtremelyHigh2), _ => Err(()), } } diff --git a/iced b/iced index 1bf1e333..13b8d3ea 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 1bf1e33317b8da299ffd9a620e7e0e099c0c3478 +Subproject commit 13b8d3eab67df7f40d3d9e932a9412f85ff8413c diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 6b7b04ba..88ff025a 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -115,8 +115,8 @@ where ( Self::new(model), Task::batch([ - command, iced_runtime::window::run_with_handle(id, init_windowing_system), + command, ]), ) } @@ -776,7 +776,17 @@ impl Cosmic { } } - let new_blur = theme.cosmic().frosted.is_some(); + let new_blur = WINDOWING_SYSTEM.get() == Some(&WindowingSystem::Wayland) && { + let t = theme.cosmic(); + match self.app.core().app_type() { + crate::core::AppType::Window => t.frosted_windows, + crate::core::AppType::System => t.frosted_system_interface, + crate::core::AppType::Applet => t.frosted_applets, + } + }; + if !new_blur { + theme = theme.into_opaque(); + } THEME.lock().unwrap().set_theme(theme.theme_type); let core = self.app.core(); @@ -979,9 +989,19 @@ impl Cosmic { // Only apply update if the theme is set to load a system theme if let ThemeType::System { .. } = cosmic_theme.theme_type { - // TODO adjust theme container alphas to remove transparency? - // if auto-blur is disabled & theme is frosted, should we make container colors in theme opaque? - let new_blur = new_theme.cosmic().frosted.is_some(); + let new_blur = + WINDOWING_SYSTEM.get() == Some(&WindowingSystem::Wayland) && { + let t = new_theme.cosmic(); + match self.app.core().app_type() { + crate::core::AppType::Window => t.frosted_windows, + crate::core::AppType::System => t.frosted_system_interface, + crate::core::AppType::Applet => t.frosted_applets, + } + }; + if !new_blur { + new_theme = new_theme.into_opaque(); + } + cosmic_theme.set_theme(new_theme.theme_type); #[cfg(all(feature = "wayland", target_os = "linux"))] if self.app.core().sync_window_border_radii_to_theme() { @@ -1181,7 +1201,7 @@ impl Cosmic { if changed { core.theme_sub_counter += 1; - let new_theme = if is_dark { + let mut new_theme = if is_dark { crate::theme::system_dark() } else { crate::theme::system_light() @@ -1195,7 +1215,21 @@ impl Cosmic { let mut cmds = Vec::with_capacity(1 + self.tracked_windows.len()); if core.auto_blur { - let blur = if new_theme.cosmic().frosted.is_some() { + let new_blur = + WINDOWING_SYSTEM.get() == Some(&WindowingSystem::Wayland) && { + let t = new_theme.cosmic(); + match self.app.core().app_type() { + crate::core::AppType::Window => t.frosted_windows, + crate::core::AppType::System => { + t.frosted_system_interface + } + crate::core::AppType::Applet => t.frosted_applets, + } + }; + if !new_blur { + new_theme = new_theme.into_opaque(); + } + let blur = if new_blur { iced::window::enable_blur } else { iced::window::disable_blur @@ -1316,7 +1350,7 @@ impl Cosmic { use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; - let theme = THEME.lock().unwrap(); + let mut theme = THEME.lock().unwrap(); let t = theme.cosmic(); let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); let cur_rad = CornerRadius { @@ -1328,8 +1362,19 @@ impl Cosmic { // TODO do we need per window sharp corners? let rounded = !self.app.core().window.sharp_corners; let core = self.app.core(); + let new_blur = WINDOWING_SYSTEM.get() == Some(&WindowingSystem::Wayland) && { + let t = theme.cosmic(); + match self.app.core().app_type() { + crate::core::AppType::Window => t.frosted_windows, + crate::core::AppType::System => t.frosted_system_interface, + crate::core::AppType::Applet => t.frosted_applets, + } + }; + if !new_blur { + *theme = theme.into_opaque(); + } let blur_cmd = if core.auto_blur { - let blur = if t.frosted.is_some() { + let blur = if new_blur { iced::window::enable_blur } else { iced::window::disable_blur @@ -1343,6 +1388,7 @@ impl Cosmic { } else { Task::none() }; + let t = theme.cosmic(); return Task::batch([ blur_cmd, corner_radius( @@ -1365,7 +1411,45 @@ impl Cosmic { } return iced_runtime::window::run_with_handle(id, init_windowing_system); } - _ => {} + Action::WindowingSystemInitialized => { + let core = self.app.core(); + let new_blur = WINDOWING_SYSTEM.get() == Some(&WindowingSystem::Wayland) && { + let t = core.system_theme.cosmic(); + match self.app.core().app_type() { + crate::core::AppType::Window => t.frosted_windows, + crate::core::AppType::System => t.frosted_system_interface, + crate::core::AppType::Applet => t.frosted_applets, + } + }; + let mut t = THEME.lock().unwrap(); + + if let ThemeType::System { prefer_dark, theme } = &t.theme_type + && new_blur + { + let mut reloaded = if theme.is_dark { + crate::theme::system_dark() + } else { + crate::theme::system_light() + }; + reloaded.theme_type.prefer_dark(*prefer_dark); + *t = reloaded; + } + if core.auto_blur { + let blur = if new_blur { + iced::window::enable_blur + } else { + iced::window::disable_blur + }; + let mut cmds = Vec::with_capacity(1 + self.tracked_windows.len()); + if let Some(main_id) = core.main_window_id() { + cmds.push(blur(main_id)); + } + for id in &self.tracked_windows { + cmds.push(blur(*id)); + } + return Task::batch(cmds); + } + } } iced::Task::none() diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 48721e1c..6df99620 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -378,9 +378,11 @@ impl Context { Container::::new(content).style(|theme| { let cosmic = theme.cosmic(); let corners = cosmic.corner_radii; + let mut bg = cosmic.background.base; + bg.alpha = (bg.alpha + if cosmic.is_dark { 0.6 } else { 0.5 }).min(1.); iced_widget::container::Style { text_color: Some(cosmic.background.on.into()), - background: Some(Color::from(cosmic.background.base).into()), + background: Some(Color::from(bg).into()), border: iced::Border { radius: corners.radius_m.into(), width: 1.0, @@ -565,6 +567,7 @@ pub fn run(flags: App::Flags) -> iced::Result { core.window.show_maximize = false; core.window.show_minimize = false; core.window.use_template = false; + core.app_type = crate::core::AppType::Applet; window_settings.decorations = false; window_settings.exit_on_close_request = true; diff --git a/src/core.rs b/src/core.rs index 2ea0890b..f1c3946d 100644 --- a/src/core.rs +++ b/src/core.rs @@ -103,6 +103,18 @@ pub struct Core { pub(crate) sync_window_border_radii_to_theme: bool, pub(crate) auto_blur: bool, + + pub(crate) app_type: AppType, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AppType { + /// A regular application + Window, + /// A system application + System, + /// An applet + Applet, } impl Default for Core { @@ -164,6 +176,7 @@ impl Default for Core { #[cfg(all(feature = "wayland", target_os = "linux"))] sync_window_border_radii_to_theme: true, auto_blur: true, + app_type: AppType::Window, } } } @@ -509,4 +522,16 @@ impl Core { pub fn set_auto_blur(&mut self, auto_blur: bool) { self.auto_blur = auto_blur; } + + pub fn auto_blur(&self) -> bool { + self.auto_blur + } + + pub fn set_app_type(&mut self, app_type: AppType) { + self.app_type = app_type; + } + + pub fn app_type(&self) -> AppType { + self.app_type + } } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index b7e85237..a04b105d 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -294,6 +294,29 @@ impl Theme { pub fn set_theme(&mut self, theme: ThemeType) { self.theme_type = theme; } + + pub fn into_opaque(&self) -> Self { + let mut new_theme = Theme { + theme_type: match &self.theme_type { + ThemeType::System { theme, prefer_dark } => { + let mut new_t = (**theme).clone(); + new_t.background.base.alpha = 1.0; + new_t.primary.base.alpha = 1.0; + new_t.secondary.base.alpha = 1.0; + ThemeType::System { + theme: Arc::new(new_t), + prefer_dark: *prefer_dark, + } + } + other => other.clone(), + }, + layer: self.layer, + }; + let cosmic = new_theme.cosmic(); + // copy theme but make all container colors opaque + + new_theme + } } impl LayeredTheme for Theme { diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 3a57408a..aa0d290a 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -478,7 +478,21 @@ impl iced_container::Catalog for Theme { let window_corner_radius = cosmic.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); match class { - Container::Transparent => iced_container::Style::default(), + Container::Transparent => { + let component = &self.current_container().component; + + iced_container::Style { + icon_color: Some(component.on.into()), + text_color: Some(component.on.into()), + background: None, + border: Border { + radius: 0.into(), + ..Default::default() + }, + shadow: Shadow::default(), + snap: true, + } + } Container::Custom(f) => f(self), @@ -566,7 +580,7 @@ impl iced_container::Catalog for Theme { Container::ContextDrawer => { let mut a = Container::primary(cosmic); if let Some(Background::Color(ref mut color)) = a.background { - color.a = 1.; + color.a = (color.a + if cosmic.is_dark { 0.60 } else { 0.5 }).min(1.); } if cosmic.is_high_contrast { diff --git a/src/theme/style/menu_bar.rs b/src/theme/style/menu_bar.rs index ed0e657a..6e192bf3 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -65,10 +65,12 @@ impl StyleSheet for Theme { fn appearance(&self, style: &Self::Style) -> Appearance { let cosmic = self.cosmic(); let component = &cosmic.background.component; + let mut bg = component.base; + bg.alpha = (bg.alpha + if cosmic.is_dark { 0.6 } else { 0.5 }).min(1.); match style { MenuBarStyle::Default => Appearance { - background: component.base.into(), + background: bg.into(), border_width: 1.0, bar_border_radius: cosmic.corner_radii.radius_xl, menu_border_radius: cosmic.corner_radii.radius_s.map(|x| x + 2.0),