feat: reapply frosted glass feature

This commit is contained in:
Ashley Wulber 2026-07-10 13:06:17 -04:00 • committed by GitHub
parent ae5c750662
commit fff1485876
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
125 changed files with 1883 additions and 424 deletions

View file

@ -2,9 +2,10 @@ use cosmic::app::{ContextDrawer, context_drawer};
use cosmic::config::CosmicTk;
use cosmic::cosmic_config::{Config, ConfigSet};
use cosmic::cosmic_theme::Spacing;
use cosmic::iced::Alignment;
use cosmic::iced::core::{Color, Length};
use cosmic::widget::color_picker::ColorPickerUpdate;
use cosmic::widget::{ColorPickerModel, container, flex_row, settings, text};
use cosmic::widget::{ColorPickerModel, container, flex_row, row, settings, slider, text};
use cosmic::{Apply, Element, Task, task, widget};
use cosmic_config::ConfigGet;
use std::sync::Arc;
@ -35,6 +36,12 @@ pub struct Content {
icon_themes: IconThemes,
icon_handles: IconHandles,
tk_config: Option<Config>,
frosted: u8,
glass: f32,
frosted_system_interface: bool,
frosted_windows: bool,
frosted_panel: bool,
frosted_applets: bool,
comp_config: cosmic_config::Config,
#[cfg(feature = "cosmic-comp-config")]
@ -89,7 +96,7 @@ impl From<&theme_manager::Manager> for Content {
application_background: ColorPickerModel::new(
&*HEX,
&*RGB,
Some(theme.background.base.into()),
Some(theme.background(false).base.into()),
theme_manager.get_color(&ContextView::ApplicationBackground),
),
container_background: ColorPickerModel::new(
@ -101,7 +108,7 @@ impl From<&theme_manager::Manager> for Content {
interface_text: ColorPickerModel::new(
&*HEX,
&*RGB,
Some(theme.background.on.into()),
Some(theme.background(false).on.into()),
theme_manager.get_color(&ContextView::InterfaceText),
),
control_component: ColorPickerModel::new(
@ -117,6 +124,12 @@ impl From<&theme_manager::Manager> for Content {
theme_manager.get_color(&ContextView::AccentWindowHint),
),
font_config: font_config::Model::new(),
frosted: theme.frosted as u8,
glass: theme.alpha_map.guess_offset(),
frosted_system_interface: theme.frosted_system_interface,
frosted_windows: theme.frosted_windows,
frosted_panel: theme.frosted_panel,
frosted_applets: theme.frosted_applets,
icons_fetched: false,
icon_global: cosmic::config::apply_theme_global(),
icon_fetch_handle: None,
@ -280,6 +293,40 @@ impl Content {
Task::none()
}
pub fn update_frosted_system_interface(&mut self, v: bool) -> Task<app::Message> {
self.frosted_system_interface = v;
Task::none()
}
pub fn update_frosted_windows(&mut self, v: bool) -> Task<app::Message> {
self.frosted_windows = v;
Task::none()
}
pub fn update_frosted_panel(&mut self, v: bool) -> Task<app::Message> {
self.frosted_panel = v;
Task::none()
}
pub fn update_frosted_applets(&mut self, v: bool) -> Task<app::Message> {
self.frosted_applets = v;
Task::none()
}
pub fn update_blur(&mut self, v: u8) -> Task<app::Message> {
self.frosted = v;
Task::none()
}
pub fn update_glass(&mut self, v: f32) -> Task<app::Message> {
self.glass = v;
Task::none()
}
pub fn on_open(&mut self, context_view: &ContextView) -> Task<app::Message> {
match *context_view {
ContextView::IconsAndToolkit => {
@ -327,7 +374,7 @@ impl Content {
self.application_background = ColorPickerModel::new(
&*HEX,
&*RGB,
Some(manager.theme().background.base.into()),
Some(manager.theme().background(false).base.into()),
manager.get_color(&ContextView::ApplicationBackground),
);
self.custom_accent = ColorPickerModel::new(
@ -345,7 +392,7 @@ impl Content {
self.interface_text = ColorPickerModel::new(
&*HEX,
&*RGB,
Some(manager.theme().background.on.into()),
Some(manager.theme().background(false).on.into()),
manager.get_color(&ContextView::InterfaceText),
);
self.control_component = ColorPickerModel::new(
@ -471,6 +518,10 @@ impl Content {
self.shadow_and_corners(),
crate::pages::Message::CloseContextDrawer,
),
ContextView::FrostedGlass => context_drawer(
self.frosted_glass(),
crate::pages::Message::CloseContextDrawer,
),
})
}
@ -554,4 +605,66 @@ impl Content {
.apply(Element::from)
.map(crate::pages::Message::Appearance)
}
pub fn frosted_glass(&self) -> Element<'_, crate::pages::Message> {
settings::section()
.add(
settings::item::builder(fl!("style", "frosted-panels"))
.description(fl!("style", "frosted-panels-desc"))
.toggler(self.frosted_panel, Message::FrostedPanel),
)
.add(
settings::item::builder(fl!("style", "frosted-applets"))
.description(fl!("style", "frosted-applets-desc"))
.toggler(self.frosted_applets, Message::FrostedApplets),
)
.add(
settings::item::builder(fl!("style", "frosted-system-interface"))
.description(fl!("style", "frosted-system-interface-desc"))
.toggler(
self.frosted_system_interface,
Message::FrostedSystemInterface,
),
)
.add(
settings::item::builder(fl!("style", "frosted-windows"))
.description(fl!("style", "frosted-windows-desc"))
.toggler(self.frosted_windows, Message::FrostedWindows),
)
.add(
settings::item::builder(fl!("style", "frosted-thickness")).flex_control({
row::with_children(vec![
text::body(fl!("style", "less")).into(),
slider(0..=13, self.frosted, Message::Blur)
.width(Length::Fill)
.apply(cosmic::widget::container)
.max_width(250)
.into(),
text::body(fl!("style", "more")).into(),
])
.align_y(Alignment::Center)
.spacing(8)
.width(Length::Fill)
}),
)
.add(
settings::item::builder(fl!("style", "glass-opacity")).flex_control({
row::with_children(vec![
text::body(fl!("style", "less")).into(),
slider(0.0..=1.0, self.glass, Message::Glass)
.step(0.05)
.width(Length::Fill)
.apply(cosmic::widget::container)
.max_width(250)
.into(),
text::body(fl!("style", "more")).into(),
])
.align_y(Alignment::Center)
.spacing(8)
.width(Length::Fill)
}),
)
.apply(Element::from)
.map(crate::pages::Message::Appearance)
}
}

View file

@ -41,6 +41,7 @@ pub enum ContextView {
ApplicationBackground,
ContainerBackground,
ControlComponent,
FrostedGlass,
#[cfg(feature = "cosmic-comp-config")]
ShadowAndCorners,
CustomAccent,
@ -159,6 +160,12 @@ pub enum Message {
UseDefaultWindowHint(bool),
WindowHintSize(u32),
Daytime(bool),
Blur(u8),
Glass(f32),
FrostedPanel(bool),
FrostedApplets(bool),
FrostedSystemInterface(bool),
FrostedWindows(bool),
}
impl From<Message> for crate::app::Message {
@ -193,6 +200,7 @@ impl Page {
Message::DrawerOpen(context_view) => {
self.context_view = Some(context_view);
self.drawer = drawer::Content::from(&self.theme_manager);
tasks.push(cosmic::task::message(
crate::app::Message::OpenContextDrawer(self.entity),
));
@ -528,6 +536,66 @@ impl Page {
});
}
}
Message::Blur(b) => {
let Ok(blur_strength) = b.try_into() else {
tracing::error!("Invalid blur strength: {b}");
return Task::none();
};
theme_staged = self.theme_manager.set_frosted(blur_strength).map(|_| {
self.drawer.reset(&self.theme_manager);
theme_manager::ThemeStaged::Current
});
tasks.push(self.drawer.update_blur(blur_strength as u8));
}
Message::Glass(glass) => {
let mut alpha_map = cosmic::cosmic_theme::AlphaMap::default();
let glass_offset_min = alpha_map.extremely_high_2;
let glass_offset_max = 1.0 - alpha_map.extremely_low;
let offset = (glass - 0.5) / 0.5
* if glass < 0.5 {
glass_offset_min
} else {
glass_offset_max
};
alpha_map = alpha_map.offset(offset);
// TODO optimize and apply to both light and dark themes
theme_staged = self.theme_manager.set_glass(alpha_map).map(|_| {
self.drawer.reset(&self.theme_manager);
theme_manager::ThemeStaged::Current
});
tasks.push(self.drawer.update_glass(glass));
}
Message::FrostedSystemInterface(v) => {
theme_staged = self.theme_manager.set_frosted_system_interface(v).map(|_| {
self.drawer.reset(&self.theme_manager);
theme_manager::ThemeStaged::Current
});
tasks.push(self.drawer.update_frosted_system_interface(v));
}
Message::FrostedWindows(v) => {
theme_staged = self.theme_manager.set_frosted_windows(v).map(|_| {
self.drawer.reset(&self.theme_manager);
theme_manager::ThemeStaged::Current
});
tasks.push(self.drawer.update_frosted_windows(v));
}
Message::FrostedPanel(v) => {
theme_staged = self.theme_manager.set_frosted_panel(v).map(|_| {
self.drawer.reset(&self.theme_manager);
theme_manager::ThemeStaged::Current
});
tasks.push(self.drawer.update_frosted_panel(v));
}
Message::FrostedApplets(v) => {
theme_staged = self.theme_manager.set_frosted_applets(v).map(|_| {
self.drawer.reset(&self.theme_manager);
theme_manager::ThemeStaged::Current
});
tasks.push(self.drawer.update_frosted_applets(v));
}
}
let mut tasks = cosmic::Task::batch(tasks);

View file

@ -5,6 +5,8 @@ use cosmic::widget::{button, container, settings, text};
use cosmic::{Apply, Element};
use cosmic_settings_page::Section;
use crate::pages::desktop::appearance::ContextView;
use super::{Message, Page, Roundness};
#[allow(clippy::too_many_lines)]
@ -13,6 +15,7 @@ pub fn section() -> Section<crate::pages::Message> {
round = fl!("style", "round");
slightly_round = fl!("style", "slightly-round");
square = fl!("style", "square");
frosted_glass = fl!("style", "frosted-glass");
});
let dark_round_style = from_name("illustration-appearance-dark-style-round").handle();
@ -137,6 +140,10 @@ pub fn section() -> Section<crate::pages::Message> {
)
.center_x(Length::Fill),
)
.add(crate::widget::go_next_item(
&descriptions[frosted_glass],
Message::DrawerOpen(ContextView::FrostedGlass),
))
.apply(Element::from)
.map(crate::pages::Message::Appearance)
})

View file

@ -1,8 +1,8 @@
use cosmic::cosmic_config::{Config, ConfigSet, CosmicConfigEntry};
use cosmic::cosmic_theme::palette::{Srgb, Srgba};
use cosmic::cosmic_theme::{
CornerRadii, DARK_THEME_BUILDER_ID, LIGHT_THEME_BUILDER_ID, Spacing, Theme, ThemeBuilder,
ThemeMode,
AlphaMap, BlurStrength, CornerRadii, DARK_THEME_BUILDER_ID, LIGHT_THEME_BUILDER_ID, Spacing,
Theme, ThemeBuilder, ThemeMode,
};
use cosmic::iced::core::Color;
@ -74,7 +74,7 @@ impl From<(Option<Config>, Option<Config>, Option<Vec<Srgba>>)> for ThemeCustomi
.success(theme.success.base.color)
.warning(theme.warning.base.color)
.neutral_tint(theme.palette.neutral_5.color)
.text_tint(theme.background.on.color);
.text_tint(theme.background(false).on.color);
theme_builder.gaps = theme.gaps;
@ -160,20 +160,6 @@ impl Default for Manager {
impl Manager {
pub fn build_theme(&mut self, stage: ThemeStaged) -> Task<app::Message> {
macro_rules! theme_transaction {
($config:ident, $current_theme:ident, $new_theme:ident, { $($name:ident;)+ }) => {
let tx = $config.transaction();
$(
if $current_theme.$name != $new_theme.$name {
_ = tx.set(stringify!($name), $new_theme.$name.clone());
}
)+
_ = tx.commit();
}
}
let map_data_fn = |customizer: &ThemeCustomizer| {
(customizer.builder.0.clone(), customizer.theme.1.clone())
};
@ -199,32 +185,80 @@ impl Manager {
cosmic::task::future(async move {
for (builder, config) in data.by_ref() {
if let Some(config) = config {
let current_theme = match Theme::get_entry(&config) {
let cur = match Theme::get_entry(&config) {
Ok(theme) => theme,
Err((_errs, theme)) => theme,
};
let new_theme = builder.build();
theme_transaction!(config, current_theme, new_theme, {
accent;
accent_button;
background;
button;
destructive;
destructive_button;
link_button;
icon_button;
palette;
primary;
secondary;
shade;
success;
text_button;
warning;
warning_button;
window_hint;
accent_text;
});
let new = builder.build();
// do this without the macro
let tx = config.transaction();
if cur.accent != new.accent {
_ = tx.set("accent", new.accent.clone());
}
if cur.accent_button != new.accent_button {
_ = tx.set("accent_button", new.accent_button.clone());
}
if cur.background(false) != new.background(false) {
_ = tx.set("background", new.background(false).clone());
}
if cur.background(true) != new.background(true) {
_ = tx.set("transparent_background", new.background(true).clone());
}
if cur.button != new.button {
_ = tx.set("button", new.button.clone());
}
if cur.destructive != new.destructive {
_ = tx.set("destructive", new.destructive.clone());
}
if cur.destructive_button != new.destructive_button {
_ = tx.set("destructive_button", new.destructive_button.clone());
}
if cur.link_button != new.link_button {
_ = tx.set("link_button", new.link_button.clone());
}
if cur.icon_button != new.icon_button {
_ = tx.set("icon_button", new.icon_button.clone());
}
if cur.palette != new.palette {
_ = tx.set("palette", new.palette.clone());
}
if cur.primary(false) != new.primary(false) {
_ = tx.set("primary", new.primary(false).clone());
}
if cur.secondary(false) != new.secondary(false) {
_ = tx.set("secondary", new.secondary(false).clone());
}
if cur.primary(true) != new.primary(true) {
_ = tx.set("transparent_primary", new.primary(true).clone());
}
if cur.secondary(true) != new.secondary(true) {
_ = tx.set("transparent_secondary", new.secondary(true).clone());
}
if cur.shade != new.shade {
_ = tx.set("shade", new.shade.clone());
}
if cur.success != new.success {
_ = tx.set("success", new.success.clone());
}
if cur.text_button != new.text_button {
_ = tx.set("text_button", new.text_button.clone());
}
if cur.warning != new.warning {
_ = tx.set("warning", new.warning.clone());
}
if cur.warning_button != new.warning_button {
_ = tx.set("warning_button", new.warning_button.clone());
}
if cur.window_hint != new.window_hint {
_ = tx.set("window_hint", new.window_hint.clone());
}
if cur.accent_text != new.accent_text {
_ = tx.set("accent_text", new.accent_text.clone());
}
_ = tx.commit();
}
}
@ -360,6 +394,39 @@ impl Manager {
Some(ThemeStaged::Both)
}
pub fn set_frosted(&mut self, frosted: BlurStrength) -> Option<ThemeStaged> {
self.dark.set_frosted(frosted)?;
self.light.set_frosted(frosted)?;
Some(ThemeStaged::Both)
}
pub fn set_glass(&mut self, alpha_map: AlphaMap) -> Option<ThemeStaged> {
self.dark.set_glass(alpha_map)?;
self.light.set_glass(alpha_map)?;
Some(ThemeStaged::Both)
}
pub fn set_frosted_panel(&mut self, enabled: bool) -> Option<ThemeStaged> {
self.dark.set_frosted_panel(enabled)?;
self.light.set_frosted_panel(enabled)?;
Some(ThemeStaged::Both)
}
pub fn set_frosted_applets(&mut self, enabled: bool) -> Option<ThemeStaged> {
self.dark.set_frosted_applets(enabled)?;
self.light.set_frosted_applets(enabled)?;
Some(ThemeStaged::Both)
}
pub fn set_frosted_system_interface(&mut self, enabled: bool) -> Option<ThemeStaged> {
self.dark.set_frosted_system_interface(enabled)?;
self.light.set_frosted_system_interface(enabled)?;
Some(ThemeStaged::Both)
}
pub fn set_frosted_windows(&mut self, enabled: bool) -> Option<ThemeStaged> {
self.dark.set_frosted_windows(enabled)?;
self.light.set_frosted_windows(enabled)?;
Some(ThemeStaged::Both)
}
pub fn get_color(&self, context: &ContextView) -> Option<Color> {
match *context {
ContextView::CustomAccent => self.custom_accent().map(Color::from),
@ -540,6 +607,81 @@ impl ThemeCustomizer {
Some(ThemeStaged::Current)
}
pub fn set_frosted(&mut self, frosted: BlurStrength) -> Option<ThemeStaged> {
let config = self.builder.1.as_ref()?;
self.builder.0.set_frosted(config, frosted).ok()?;
self.theme
.0
.set_frosted(self.theme.1.as_ref()?, frosted)
.ok()?;
Some(ThemeStaged::Current)
}
pub fn set_glass(&mut self, alpha_map: AlphaMap) -> Option<ThemeStaged> {
let config = self.builder.1.as_ref()?;
self.builder.0.set_alpha_map(config, alpha_map).ok()?;
self.theme
.0
.set_alpha_map(self.theme.1.as_ref()?, alpha_map)
.ok()?;
Some(ThemeStaged::Current)
}
pub fn set_frosted_windows(&mut self, enabled: bool) -> Option<ThemeStaged> {
let config = self.builder.1.as_ref()?;
self.builder.0.set_frosted_windows(config, enabled).ok()?;
self.theme
.0
.set_frosted_windows(self.theme.1.as_ref()?, enabled)
.ok()?;
Some(ThemeStaged::Current)
}
pub fn set_frosted_system_interface(&mut self, enabled: bool) -> Option<ThemeStaged> {
let config = self.builder.1.as_ref()?;
self.builder
.0
.set_frosted_system_interface(config, enabled)
.ok()?;
self.theme
.0
.set_frosted_system_interface(self.theme.1.as_ref()?, enabled)
.ok()?;
Some(ThemeStaged::Current)
}
pub fn set_frosted_applets(&mut self, enabled: bool) -> Option<ThemeStaged> {
let config = self.builder.1.as_ref()?;
self.builder.0.set_frosted_applets(config, enabled).ok()?;
self.theme
.0
.set_frosted_applets(self.theme.1.as_ref()?, enabled)
.ok()?;
Some(ThemeStaged::Current)
}
pub fn set_frosted_panel(&mut self, enabled: bool) -> Option<ThemeStaged> {
let config = self.builder.1.as_ref()?;
self.builder.0.set_frosted_panel(config, enabled).ok()?;
self.theme
.0
.set_frosted_panel(self.theme.1.as_ref()?, enabled)
.ok()?;
Some(ThemeStaged::Current)
}
// set active hints is set on all themes to be consistent between dark & light themes.
pub fn set_active_hint(&mut self, active_hint: u32) -> Option<ThemeStaged> {
let config = self.builder.1.as_ref()?;

View file

@ -36,7 +36,7 @@ impl Info {
// Use wgpu to enumerate GPUs. Works cross-platform and doesn't require external tools
let instance = wgpu::Instance::default();
let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
let adapters = instance.enumerate_adapters(wgpu::Backends::all());
// Track seen GPUs by (vendor, device) and by name to handle different scenarios:
// - Same GPU via different backends (Vulkan/OpenGL) -> deduplicate by device ID or name
@ -45,7 +45,7 @@ impl Info {
let mut seen_devices = HashSet::new();
let mut seen_names = HashSet::new();
for adapter in adapters {
for adapter in adapters.await {
let adapter_info = adapter.get_info();
if adapter_info.device_type == wgpu::DeviceType::Cpu {