diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 8722a2a4..f77bf512 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -101,6 +101,7 @@ impl cosmic::Application for Window { Message::Surface(destroy_popup(id)) } else { Message::Surface(app_popup::( + |_| Default::default(), move |state: &mut Window| { let new_id = Id::unique(); state.popup = Some(new_id); diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 30fadcdc..edca03e6 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -8,6 +8,8 @@ use std::sync::Arc; use super::{Action, Application, ApplicationExt, Subscription}; #[cfg(all(feature = "wayland", target_os = "linux"))] use crate::core::Auto; +#[cfg(all(feature = "wayland", target_os = "linux"))] +use crate::surface::action::LiveSettings; use crate::theme::{THEME, Theme, ThemeType}; use crate::{Core, Element, keyboard_nav}; #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -21,7 +23,7 @@ use iced::Application as IcedApplication; use iced::event::wayland; use iced::{Task, theme, window}; use iced_futures::event::listen_with; -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[cfg(feature = "winit")] use iced_winit::SurfaceIdWrapper; use palette::color_difference::EuclideanDistance; @@ -87,16 +89,22 @@ fn init_windowing_system(handle: window::raw_window_handle::WindowHandle) -> #[derive(Default)] pub struct Cosmic { pub app: App, - #[cfg(all(feature = "wayland", target_os = "linux"))] pub surface_views: HashMap< window::Id, ( Option, SurfaceIdWrapper, - Box Fn(&'a App) -> Element<'a, crate::Action>>, + Box Fn(&'a App) -> crate::surface::action::LiveSettings>, + Option< + Box< + dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + + Send + + Sync + + 'static, + >, + >, ), >, - pub tracked_surfaces: HashMap, pub opened_surfaces: HashMap, blur_enabled: bool, } @@ -144,7 +152,7 @@ where #[cfg(feature = "surface-message")] match _surface_message { #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::AppSubsurface(settings, view) => { + crate::surface::Action::AppSubsurface(settings, live_settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { @@ -152,7 +160,9 @@ where return Task::none(); }; - if let Some(view) = view.and_then(|view| { + let settings = settings(&mut self.app); + + let view = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + Send @@ -165,25 +175,18 @@ where None } } - }) { - let settings = settings(&mut self.app); - - self.get_subsurface(settings, *view) - } else { - let settings = settings(&mut self.app); - self.tracked_surfaces - .insert(settings.id, SurfaceIdWrapper::Subsurface(settings.id)); - iced_winit::commands::subsurface::get_subsurface(settings) - } + }); + self.get_subsurface(settings, view.map(|v| *v)) } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::Subsurface(settings, view) => { + crate::surface::Action::Subsurface(settings, live_settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else { tracing::error!("Invalid settings for subsurface"); return Task::none(); }; + let settings = settings(); if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: { + crate::surface::Action::AppPopup(settings, live_settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { tracing::error!("Invalid settings for popup"); return Task::none(); }; + let Some(live_settings) = + std::sync::Arc::try_unwrap(live_settings) + .ok() + .and_then(|s| { + s.downcast:: LiveSettings + Send + Sync>>() + .ok() + }) + else { + tracing::error!("Invalid live settings for popup"); + return Task::none(); + }; - if let Some(view) = view.and_then(|view| { + let view = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + Send @@ -228,16 +237,10 @@ where None } } - }) { - let settings = settings(&mut self.app); + }); + let settings = settings(&mut self.app); - self.get_popup(settings, *view) - } else { - let settings = settings(&mut self.app); - self.tracked_surfaces - .insert(settings.id, SurfaceIdWrapper::Popup(settings.id)); - iced_winit::commands::popup::get_popup(settings) - } + self.get_popup(settings, *live_settings, view.map(|v| *v)) } #[cfg(all(feature = "wayland", target_os = "linux"))] crate::surface::Action::DestroyPopup(id) => { @@ -270,7 +273,7 @@ where iced::Task::none() } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::Popup(settings, view) => { + crate::surface::Action::Popup(settings, live_settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else { @@ -278,6 +281,20 @@ where return Task::none(); }; + let Some(live_settings) = + std::sync::Arc::try_unwrap(live_settings) + .ok() + .and_then(|s| { + s.downcast:: LiveSettings + Send + Sync>>() + .ok() + }) + else { + tracing::error!("Invalid live settings for popup"); + return Task::none(); + }; + let settings = settings(); + let live_settings = Box::new(move |app: &T| live_settings()); + if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, crate::Action> + Send + Sync, @@ -289,18 +306,13 @@ where } } }) { - let settings = settings(); - - self.get_popup(settings, Box::new(move |_| view())) + self.get_popup(settings, live_settings, Some(Box::new(move |_| view()))) } else { - let settings = settings(); - self.tracked_surfaces - .insert(settings.id, SurfaceIdWrapper::Popup(settings.id)); - iced_winit::commands::popup::get_popup(settings) + self.get_popup(settings, live_settings, None) } } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::AppWindow(id, settings, view) => { + crate::surface::Action::AppWindow(id, settings, live_settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { s.downcast:: iced::window::Settings + Send + Sync>>() .ok() @@ -308,8 +320,19 @@ where tracing::error!("Invalid settings for AppWindow"); return Task::none(); }; + let Some(live_settings) = + std::sync::Arc::try_unwrap(live_settings) + .ok() + .and_then(|s| { + s.downcast:: LiveSettings + Send + Sync>>() + .ok() + }) + else { + tracing::error!("Invalid live settings for popup"); + return Task::none(); + }; - if let Some(view) = view.and_then(|view| { + let view = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + Send @@ -321,24 +344,13 @@ where None } } - }) { - let settings = settings(&mut self.app); + }); + let settings = settings(&mut self.app); - self.get_window(id, settings, *view) - } else { - let settings = settings(&mut self.app); - self.tracked_surfaces - .insert(id, SurfaceIdWrapper::Window(id)); - iced_runtime::task::oneshot(|channel| { - iced_runtime::Action::Window(iced_runtime::window::Action::Open( - id, settings, channel, - )) - }) - .discard() - } + self.get_window(id, settings, *live_settings, view.map(|v| *v)) } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::Window(id, settings, view) => { + crate::surface::Action::Window(id, settings, live_settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| { s.downcast:: iced::window::Settings + Send + Sync>>() .ok() @@ -347,6 +359,18 @@ where return Task::none(); }; + let Some(live_settings) = + std::sync::Arc::try_unwrap(live_settings) + .ok() + .and_then(|s| { + s.downcast:: LiveSettings + Send + Sync>>() + .ok() + }) + else { + tracing::error!("Invalid live settings for popup"); + return Task::none(); + }; + if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, crate::Action> + Send + Sync, @@ -360,11 +384,14 @@ where }) { let settings = settings(); - self.get_window(id, settings, Box::new(move |_| view())) + self.get_window( + id, + settings, + Box::new(move |_| live_settings()), + Some(Box::new(move |_| view())), + ) } else { let settings = settings(); - self.tracked_surfaces - .insert(id, SurfaceIdWrapper::Window(id)); iced_runtime::task::oneshot(|channel| { iced_runtime::Action::Window(iced_runtime::window::Action::Open( @@ -380,15 +407,26 @@ where f().map(|sm| crate::Action::Cosmic(Action::Surface(sm))) } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::AppLayerShell(settings, view) => { + crate::surface::Action::AppLayerShell(settings, live_settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings + Send + Sync>>().ok()) else { tracing::error!("Invalid settings for layer surface"); return Task::none(); }; + let Some(live_settings) = + std::sync::Arc::try_unwrap(live_settings) + .ok() + .and_then(|s| { + s.downcast:: LiveSettings + Send + Sync>>() + .ok() + }) + else { + tracing::error!("Invalid live settings for popup"); + return Task::none(); + }; - if let Some(view) = view.and_then(|view| { + let view = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, crate::Action> + Send @@ -400,19 +438,14 @@ where None } } - }) { - let settings = settings(&mut self.app); + }); - self.get_layer_shell(settings, *view) - } else { - let settings = settings(&mut self.app); - self.tracked_surfaces - .insert(settings.id, SurfaceIdWrapper::LayerSurface(settings.id)); - iced_winit::commands::layer_surface::get_layer_surface(settings) - } + let settings = settings(&mut self.app); + + self.get_layer_shell(settings, *live_settings, view.map(|v| *v)) } #[cfg(all(feature = "wayland", target_os = "linux"))] - crate::surface::Action::LayerShell(settings, view) => { + crate::surface::Action::LayerShell(settings, live_settings, view) => { let Some(settings) = std::sync::Arc::try_unwrap(settings) .ok() .and_then(|s| s.downcast:: iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings + Send + Sync>>().ok()) else { @@ -420,6 +453,21 @@ where return Task::none(); }; + let Some(live_settings) = + std::sync::Arc::try_unwrap(live_settings) + .ok() + .and_then(|s| { + s.downcast:: LiveSettings + Send + Sync>>() + .ok() + }) + else { + tracing::error!("Invalid live settings for popup"); + return Task::none(); + }; + let settings = settings(); + let live_settings = live_settings(); + let live_settings = Box::new(move |_app: &T| live_settings); + if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, crate::Action> + Send + Sync, @@ -431,16 +479,9 @@ where } } }) { - let settings = settings(); - - self.get_layer_shell(settings, Box::new(move |_| view())) + self.get_layer_shell(settings, live_settings, Some(Box::new(move |_| view()))) } else { - let settings = settings(); - self.tracked_surfaces.insert( - settings.id, - iced_winit::SurfaceIdWrapper::LayerSurface(settings.id), - ); - iced_winit::commands::layer_surface::get_layer_surface(settings) + self.get_layer_shell(settings, live_settings, None) } } #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -657,8 +698,7 @@ where #[cfg(feature = "multi-window")] pub fn view(&self, id: window::Id) -> Element<'_, crate::Action> { - #[cfg(all(feature = "wayland", target_os = "linux"))] - if let Some((_, _, v)) = self.surface_views.get(&id) { + if let Some((_, _, _, Some(v))) = self.surface_views.get(&id) { return v(&self.app); } if self @@ -762,20 +802,8 @@ impl Cosmic { use iced_winit::platform_specific::commands::corner_radius::corner_radius; let theme = THEME.lock().unwrap(); - let rounded = (!self.app.core().window.sharp_corners - && self.app.core().sync_window_border_radii_to_theme()) - || self - .surface_views - .get(&id) - .is_some_and(|(_, surface_type, _)| { - if let SurfaceIdWrapper::Popup(_) - | SurfaceIdWrapper::LayerSurface(_) = surface_type - { - true - } else { - false - } - }); + let rounded = !self.app.core().window.sharp_corners + && self.app.core().sync_window_border_radii_to_theme(); let cur_rad = self.app.core().corners(&theme, rounded); return Task::batch([corner_radius(id, cur_rad).discard()]); @@ -887,13 +915,12 @@ impl Cosmic { )); } for (id, wrapper, ..) in &self.surface_views { - if core.blur(&theme, Some(wrapper.1)) { - cmds.push(blur(*id)); - } - } - for (id, wrapper) in &self.tracked_surfaces { - if core.blur(&theme, Some(*wrapper)) { + let overriden = wrapper.2(&self.app); + if core.blur(&theme, Some(wrapper.1)) || overriden.blur.unwrap_or_default() + { cmds.push(blur(*id)); + } else if overriden.blur.is_some_and(|b| !b) { + cmds.push(iced::window::disable_blur(*id)); } } return Task::batch(cmds); @@ -963,25 +990,18 @@ impl Cosmic { .unwrap_or(window::Id::RESERVED); let mut cmds = vec![corner_radius(main_window_id, cur_rad).discard()]; // Update radius for each tracked view with the window surface type - for (id, (_, surface_type, _)) in &self.surface_views { - let cur_rad = corners( - *surface_type, - rounded, - &cosmic_theme, - self.app.core().auto_corner_radius, - ); - if cur_rad.is_none() { - continue; - } - cmds.push(corner_radius(*id, cur_rad).discard()); - } - for (id, wrapper) in &self.tracked_surfaces { - let cur_rad = corners( - *wrapper, - rounded, - &cosmic_theme, - self.app.core().auto_corner_radius, - ); + for (id, (_, surface_type, live_settings, _)) in &self.surface_views { + let overriden = live_settings(&self.app); + let cur_rad = if let Some(c) = overriden.corners { + Some(c) + } else { + corners( + *surface_type, + rounded, + &cosmic_theme, + self.app.core().auto_corner_radius, + ) + }; if cur_rad.is_none() { continue; } @@ -1080,25 +1100,19 @@ impl Cosmic { let mut cmds = vec![corner_radius(main_window_id, cur_rad).discard()]; // Update radius for each tracked view with the window surface type - for (id, (_, surface_type, _)) in &self.surface_views { - let cur_rad = corners( - *surface_type, - rounded, - &cosmic_theme, - self.app.core().auto_corner_radius, - ); - if cur_rad.is_none() { - continue; - } - cmds.push(corner_radius(*id, cur_rad).discard()); - } - for (id, wrapper) in &self.tracked_surfaces { - let cur_rad = corners( - *wrapper, - rounded, - &cosmic_theme, - self.app.core().auto_corner_radius, - ); + for (id, (_, surface_type, live_settings, _)) in &self.surface_views + { + let overriden = live_settings(&self.app); + let cur_rad = if let Some(c) = overriden.corners { + Some(c) + } else { + corners( + *surface_type, + rounded, + &cosmic_theme, + self.app.core().auto_corner_radius, + ) + }; if cur_rad.is_none() { continue; } @@ -1121,13 +1135,13 @@ impl Cosmic { )); } for (id, wrapper, ..) in &self.surface_views { - if core.blur(&cosmic_theme, Some(wrapper.1)) { - cmds.push(blur(*id)); - } - } - for (id, wrapper) in &self.tracked_surfaces { - if core.blur(&cosmic_theme, Some(*wrapper)) { + let overriden = wrapper.2(&self.app); + if core.blur(&cosmic_theme, Some(wrapper.1)) + || overriden.blur.unwrap_or_default() + { cmds.push(blur(*id)); + } else if overriden.blur.is_some_and(|b| !b) { + cmds.push(iced::window::disable_blur(*id)); } } @@ -1171,12 +1185,9 @@ impl Cosmic { *v == 0 }) { self.opened_surfaces.remove(&id); - #[cfg(all(feature = "wayland", target_os = "linux"))] self.surface_views.remove(&id); } - self.tracked_surfaces.remove(&id); - self.tracked_surfaces - .shrink_to(self.tracked_surfaces.len() * 2); + self.surface_views.shrink_to(self.surface_views.len() * 2); let mut ret = if let Some(msg) = self.app.on_close_requested(id) { self.app.update(msg) @@ -1247,6 +1258,7 @@ impl Cosmic { new_theme.transparent = new_blur; } core.system_theme = new_theme.clone(); + let core = self.app.core(); { let mut cosmic_theme = THEME.lock().unwrap(); @@ -1266,14 +1278,15 @@ impl Cosmic { core.main_window_id().unwrap_or(window::Id::RESERVED), )); } + for (id, wrapper, ..) in &self.surface_views { - if core.blur(&cosmic_theme, Some(wrapper.1)) { - cmds.push(blur(*id)); - } - } - for (id, wrapper) in &self.tracked_surfaces { - if core.blur(&cosmic_theme, Some(*wrapper)) { + let overriden = wrapper.2(&self.app); + if core.blur(&cosmic_theme, Some(wrapper.1)) + || overriden.blur.unwrap_or_default() + { cmds.push(blur(*id)); + } else if overriden.blur.is_some_and(|b| !b) { + cmds.push(iced::window::disable_blur(*id)); } } } @@ -1339,6 +1352,7 @@ impl Cosmic { parent, SurfaceIdWrapper::Subsurface(_) | SurfaceIdWrapper::Popup(_), _, + _, )) = self.surface_views.get(&f) { // If the parent is already focused, push the new focus @@ -1355,7 +1369,7 @@ impl Cosmic { cur = self .surface_views .get(&p) - .and_then(|(parent, _, _)| *parent); + .and_then(|(parent, _, _, _)| *parent); } parent_chain.reverse(); self.app.core_mut().focused_window = parent_chain; @@ -1387,15 +1401,14 @@ impl Cosmic { // TODO do we need per window sharp corners? let rounded = (!self.app.core().window.sharp_corners && self.app.core().sync_window_border_radii_to_theme()) - || self - .surface_views - .get(&id) - .is_some_and(|(_, surface_type, _)| { + || self.surface_views.get(&id).is_some_and( + |(_, surface_type, live_settings, _)| { matches!( surface_type, SurfaceIdWrapper::Popup(_) | SurfaceIdWrapper::LayerSurface(_) ) - }); + }, + ); let new_blur = self.blur_enabled && { let t = theme.cosmic(); match self.app.core().app_type() { @@ -1405,11 +1418,7 @@ impl Cosmic { } }; - let wrapper = self - .surface_views - .get(&id) - .map(|s| s.1) - .or(self.tracked_surfaces.get(&id).copied()); + let wrapper = self.surface_views.get(&id).map(|s| s.1); // this will blur untracked windows as if they were the main window let blur_cmd = if self.app.core().blur(&theme, wrapper) { @@ -1426,15 +1435,18 @@ impl Cosmic { Task::none() }; - let corner_task = if let Some((_, cur_rad)) = self - .surface_views - .get(&id) - .map(|s| s.1) - .or(self.tracked_surfaces.get(&id).copied()) - .and_then(|s| { - corners(s, rounded, &theme, self.app.core().auto_corner_radius) - .map(|c| (s, c)) - }) { + let corner_task = if let Some((_, cur_rad)) = + self.surface_views.get(&id).map(|s| (s.1, &s.2)).and_then( + |(s, overriden)| { + let overriden = overriden(&self.app); + if let Some(c) = overriden.corners { + Some((s, c)) + } else { + corners(s, rounded, &theme, self.app.core().auto_corner_radius) + .map(|c| (s, c)) + } + }, + ) { corner_radius(id, Some(cur_rad)).discard() } else if id == self @@ -1490,13 +1502,13 @@ impl Cosmic { )); } for (id, wrapper, ..) in &self.surface_views { - if self.app.core().blur(&t, Some(wrapper.1)) { - cmds.push(blur(*id)); - } - } - for (id, wrapper) in &self.tracked_surfaces { - if self.app.core().blur(&t, Some(*wrapper)) { + let overriden = wrapper.2(&self.app); + if self.app.core().blur(&t, Some(wrapper.1)) + || overriden.blur.unwrap_or_default() + { cmds.push(blur(*id)); + } else if overriden.blur.is_some_and(|b| !b) { + cmds.push(iced::window::disable_blur(*id)); } } return Task::batch(cmds); @@ -1513,35 +1525,79 @@ impl Cosmic { pub fn new(app: App) -> Self { Self { app, - #[cfg(all(feature = "wayland", target_os = "linux"))] surface_views: HashMap::new(), opened_surfaces: HashMap::new(), - tracked_surfaces: HashMap::new(), blur_enabled: false, } } + #[cfg(all(feature = "wayland", feature = "winit", target_os = "linux"))] + /// Apply live setting overrides for a surface + pub fn apply_live_settings( + &mut self, + id_wrapper: SurfaceIdWrapper, + live_settings: &LiveSettings, + ) -> Task> { + use iced_winit::commands::corner_radius; + + let id = id_wrapper.inner(); + + let mut cmds = Vec::with_capacity(2); + let t = THEME.try_lock().unwrap(); + + if let Some(blur) = live_settings.blur { + let blur_cmd = if blur { + iced::window::enable_blur + } else { + iced::window::disable_blur + }; + cmds.push(blur_cmd(id)); + } else if self.app.core().blur(&*t, Some(id_wrapper)) { + cmds.push(iced::window::enable_blur(id)); + } + if let Some(corners) = live_settings.corners { + cmds.push(corner_radius::corner_radius(id, Some(corners)).discard()); + } else { + let rounded = !self.app.core().window.sharp_corners + && self.app.core().sync_window_border_radii_to_theme(); + if let Some(cur_rad) = + corners(id_wrapper, rounded, &*t, self.app.core().auto_corner_radius) + { + cmds.push(corner_radius::corner_radius(id, Some(cur_rad)).discard()) + } + } + Task::batch(cmds) + } + #[cfg(all(feature = "wayland", target_os = "linux"))] /// Create a subsurface pub fn get_subsurface( &mut self, settings: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings, - view: Box< - dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, + view: Option< + Box Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync>, >, ) -> Task> { use iced_winit::commands::subsurface::get_subsurface; *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1; + let live_settings_task = self.apply_live_settings( + SurfaceIdWrapper::Subsurface(settings.id), + &LiveSettings { + blur: Some(self.blur_enabled), + corners: None, + }, + ); self.surface_views.insert( settings.id, ( Some(settings.parent), SurfaceIdWrapper::Subsurface(settings.id), + Box::new(|_| LiveSettings::default()), view, ), ); - get_subsurface(settings) + Task::batch([get_subsurface(settings), live_settings_task]) } #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -1549,21 +1605,27 @@ impl Cosmic { pub fn get_popup( &mut self, settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings, - view: Box< - dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, + live_settings: Box Fn(&'a App) -> LiveSettings + Send + Sync>, + view: Option< + Box Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync>, >, ) -> Task> { use iced_winit::commands::popup::get_popup; *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1; + let live_settings_task = self.apply_live_settings( + SurfaceIdWrapper::Popup(settings.id), + &live_settings(&self.app), + ); self.surface_views.insert( settings.id, ( Some(settings.parent), SurfaceIdWrapper::Popup(settings.id), + live_settings, view, ), ); - get_popup(settings) + Task::batch([get_popup(settings), live_settings_task]) } #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -1572,46 +1634,62 @@ impl Cosmic { &mut self, id: iced::window::Id, settings: iced::window::Settings, - view: Box< - dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, + live_settings: Box Fn(&'a App) -> LiveSettings + Send + Sync>, + + view: Option< + Box Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync>, >, ) -> Task> { use iced_winit::SurfaceIdWrapper; *self.opened_surfaces.entry(id).or_insert(0) += 1; + let live_settings_task = + self.apply_live_settings(SurfaceIdWrapper::Window(id), &live_settings(&self.app)); self.surface_views.insert( id, ( None, // TODO parent for window, platform specific option maybe? SurfaceIdWrapper::Window(id), + live_settings, view, ), ); - iced_runtime::task::oneshot(|channel| { - iced_runtime::Action::Window(iced_runtime::window::Action::Open(id, settings, channel)) - }) - .discard() + Task::batch([ + iced_runtime::task::oneshot(|channel| { + iced_runtime::Action::Window(iced_runtime::window::Action::Open( + id, settings, channel, + )) + }) + .discard(), + live_settings_task, + ]) } #[cfg(all(feature = "wayland", target_os = "linux"))] pub fn get_layer_shell( &mut self, settings: iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings, - view: Box< - dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, + live_settings: Box Fn(&'a App) -> LiveSettings + Send + Sync>, + view: Option< + Box Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync>, >, ) -> Task> { use iced_winit::SurfaceIdWrapper; use iced_winit::platform_specific::commands::layer_surface::get_layer_surface; *self.opened_surfaces.entry(settings.id).or_insert(0) += 1; + let live_settings_task = self.apply_live_settings( + SurfaceIdWrapper::LayerSurface(settings.id), + &live_settings(&self.app), + ); self.surface_views.insert( settings.id, ( None, // TODO parent for layer shell, platform specific option maybe? SurfaceIdWrapper::LayerSurface(settings.id), + live_settings, view, ), ); - get_layer_surface(settings) + Task::batch([get_layer_surface(settings), live_settings_task]) } } diff --git a/src/surface/action.rs b/src/surface/action.rs index 300d844b..23a72e68 100644 --- a/src/surface/action.rs +++ b/src/surface/action.rs @@ -5,7 +5,9 @@ use super::Action; #[cfg(feature = "winit")] use crate::Application; -use iced::window; +use iced::{Rectangle, window}; +#[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] +use iced_runtime::platform_specific::wayland::CornerRadius; use std::any::Any; use std::sync::Arc; @@ -34,18 +36,30 @@ pub fn destroy_layer_shell(id: iced_core::window::Id) -> Action { Action::DestroyLayerShell(id) } +#[derive(Debug, Default, Copy, Clone)] +pub struct LiveSettings { + #[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] + /// Override the default corner radius value for the surface type. + pub corners: Option, + /// Override the default blur setting for the surface type. + pub blur: Option, +} + +type BoxedView = Option< + Box< + dyn Fn(&App) -> crate::Element<'_, crate::Action<::Message>> + + Send + + Sync + + 'static, + >, +>; + #[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn app_window( + live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static, settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static, - view: Option< - Box< - dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> - + Send - + Sync - + 'static, - >, - >, + view: BoxedView, ) -> (window::Id, Action) { let id = window::Id::unique(); @@ -53,11 +67,16 @@ pub fn app_window( Box::new(settings); let boxed: Box = Box::new(boxed); + let boxed_live: Box LiveSettings + Send + Sync + 'static> = + Box::new(live_settings); + let boxed_live: Box = Box::new(boxed_live); + ( id, Action::AppWindow( id, Arc::new(boxed), + Arc::new(boxed_live), view.map(|view| { let boxed: Box = Box::new(view); Arc::new(boxed) @@ -70,6 +89,7 @@ pub fn app_window( #[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn simple_window( + live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static, settings: impl Fn() -> window::Settings + Send + Sync + 'static, view: Option< impl Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, @@ -80,11 +100,15 @@ pub fn simple_window( let boxed: Box window::Settings + Send + Sync + 'static> = Box::new(settings); let boxed: Box = Box::new(boxed); + let boxed_live: Box LiveSettings + Send + Sync + 'static> = Box::new(live_settings); + let boxed_live: Box = Box::new(boxed_live); + ( id, Action::Window( id, Arc::new(boxed), + Arc::new(boxed_live), view.map(|view| { let boxed: Box< dyn Fn() -> crate::Element<'static, crate::Action> @@ -102,18 +126,12 @@ pub fn simple_window( #[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn app_popup( + live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static, settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync + 'static, - view: Option< - Box< - dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> - + Send - + Sync - + 'static, - >, - >, + view: BoxedView, ) -> Action { let boxed: Box< dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings @@ -123,8 +141,13 @@ pub fn app_popup( > = Box::new(settings); let boxed: Box = Box::new(boxed); + let boxed_live: Box LiveSettings + Send + Sync + 'static> = + Box::new(live_settings); + let boxed_live: Box = Box::new(boxed_live); + Action::AppPopup( Arc::new(boxed), + Arc::new(boxed_live), view.map(|view| { let boxed: Box = Box::new(view); Arc::new(boxed) @@ -154,6 +177,7 @@ pub fn simple_subsurface( Action::Subsurface( Arc::new(boxed), + Arc::new(Box::new(LiveSettings::default)), view.map(|view| { let boxed: Box = Box::new(view); Arc::new(boxed) @@ -165,6 +189,7 @@ pub fn simple_subsurface( #[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn simple_popup( + live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static, settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync @@ -181,8 +206,12 @@ pub fn simple_popup( > = Box::new(settings); let boxed: Box = Box::new(boxed); + let boxed_live: Box LiveSettings + Send + Sync + 'static> = Box::new(live_settings); + let boxed_live: Box = Box::new(boxed_live); + Action::Popup( Arc::new(boxed), + Arc::new(boxed_live), view.map(|view| { let boxed: Box< dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, @@ -204,14 +233,7 @@ pub fn subsurface( + Sync + 'static, // XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways. - view: Option< - Box< - dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> - + Send - + Sync - + 'static, - >, - >, + view: BoxedView, ) -> Action { let boxed: Box< dyn Fn( @@ -226,6 +248,7 @@ pub fn subsurface( Action::AppSubsurface( Arc::new(boxed), + Arc::new(Box::new(|_: &App| LiveSettings::default())), view.map(|view| { let boxed: Box = Box::new(view); Arc::new(boxed) @@ -236,6 +259,7 @@ pub fn subsurface( #[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] pub fn simple_layer_shell( + live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static, settings: impl Fn() -> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings + Send @@ -253,9 +277,11 @@ pub fn simple_layer_shell( + 'static, > = Box::new(settings); let boxed: Box = Box::new(boxed); - + let boxed_live: Box LiveSettings + Send + Sync + 'static> = Box::new(live_settings); + let boxed_live: Box = Box::new(boxed_live); Action::LayerShell( Arc::new(boxed), + Arc::new(boxed_live), view.map(|view| { let boxed: Box< dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, @@ -268,7 +294,8 @@ pub fn simple_layer_shell( #[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] #[must_use] -pub fn layer_shell( +pub fn app_layer_shell( + live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static, settings: impl Fn( &mut App, ) @@ -277,14 +304,7 @@ pub fn layer_shell( + Sync + 'static, // XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways. - view: Option< - Box< - dyn for<'a> Fn(&'a App) -> crate::Element<'a, crate::Action> - + Send - + Sync - + 'static, - >, - >, + view: BoxedView, ) -> Action { let boxed: Box< dyn Fn( @@ -297,8 +317,13 @@ pub fn layer_shell( > = Box::new(settings); let boxed: Box = Box::new(boxed); + let boxed_live: Box LiveSettings + Send + Sync + 'static> = + Box::new(live_settings); + let boxed_live: Box = Box::new(boxed_live); + Action::AppLayerShell( Arc::new(boxed), + Arc::new(boxed_live), view.map(|view| { let boxed: Box = Box::new(view); Arc::new(boxed) diff --git a/src/surface/mod.rs b/src/surface/mod.rs index 5377b421..092ef07b 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -4,34 +4,24 @@ pub mod action; use iced::{Limits, Size, Task}; -use std::future::Future; +use std::any::Any; use std::sync::Arc; +type BoxedSetting = Arc>; + /// Ignore this message in your application. It will be intercepted. #[derive(Clone)] pub enum Action { /// Create a subsurface with a view function accepting the App as a parameter - AppSubsurface( - std::sync::Arc>, - Option>>, - ), + AppSubsurface(BoxedSetting, BoxedSetting, Option), /// Create a subsurface with a view function - Subsurface( - std::sync::Arc>, - Option>>, - ), + Subsurface(BoxedSetting, BoxedSetting, Option), /// Destroy a subsurface with a view function DestroySubsurface(iced::window::Id), /// Create a popup with a view function accepting the App as a parameter - AppPopup( - std::sync::Arc>, - Option>>, - ), + AppPopup(BoxedSetting, BoxedSetting, Option), /// Create a popup - Popup( - std::sync::Arc>, - Option>>, - ), + Popup(BoxedSetting, BoxedSetting, Option), /// Destroy a subsurface with a view function DestroyPopup(iced::window::Id), /// Destroys the global tooltip popup subsurface @@ -40,29 +30,25 @@ pub enum Action { /// Create a window with a view function accepting the App as a parameter AppWindow( iced::window::Id, - std::sync::Arc>, - Option>>, + BoxedSetting, + BoxedSetting, + Option, ), /// Create a window with a view function Window( iced::window::Id, - std::sync::Arc>, - Option>>, + BoxedSetting, + BoxedSetting, + Option, ), /// Destroy a window DestroyWindow(iced::window::Id), /// Create a layer shell surface with a view function accepting the App as a parameter - AppLayerShell( - std::sync::Arc>, - Option>>, - ), + AppLayerShell(BoxedSetting, BoxedSetting, Option), /// Create a layer shell surface with a view function - LayerShell( - std::sync::Arc>, - Option>>, - ), + LayerShell(BoxedSetting, BoxedSetting, Option), /// Destroy a layer shell surface DestroyLayerShell(iced::window::Id), @@ -89,21 +75,33 @@ impl std::fmt::Debug for Action { #[cold] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::AppSubsurface(arg0, arg1) => f + Self::AppSubsurface(arg0, arg1, arg2) => f .debug_tuple("AppSubsurface") .field(arg0) .field(arg1) + .field(arg2) + .finish(), + Self::Subsurface(arg0, arg1, arg2) => f + .debug_tuple("Subsurface") + .field(arg0) + .field(arg1) + .field(arg2) .finish(), - Self::Subsurface(arg0, arg1) => { - f.debug_tuple("Subsurface").field(arg0).field(arg1).finish() - } Self::DestroySubsurface(arg0) => { f.debug_tuple("DestroySubsurface").field(arg0).finish() } - Self::AppPopup(arg0, arg1) => { - f.debug_tuple("AppPopup").field(arg0).field(arg1).finish() - } - Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(), + Self::AppPopup(arg0, arg1, arg2) => f + .debug_tuple("AppPopup") + .field(arg0) + .field(arg1) + .field(arg2) + .finish(), + Self::Popup(arg0, arg1, arg2) => f + .debug_tuple("Popup") + .field(arg0) + .field(arg1) + .field(arg2) + .finish(), Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(), Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(), Self::ResponsiveMenuBar { @@ -117,28 +115,34 @@ impl std::fmt::Debug for Action { .field("size", size) .finish(), Self::Ignore => write!(f, "Ignore"), - Self::AppWindow(id, arg0, arg1) => f + Self::AppWindow(id, arg0, arg1, arg2) => f .debug_tuple("AppWindow") .field(id) .field(arg0) .field(arg1) + .field(arg2) .finish(), - Self::Window(id, arg0, arg1) => f + Self::Window(id, arg0, arg1, arg2) => f .debug_tuple("Window") .field(id) .field(arg0) .field(arg1) + .field(arg2) .finish(), Self::DestroyWindow(arg0) => f.debug_tuple("DestroyWindow").field(arg0).finish(), Self::Task(_) => f.debug_tuple("Future").finish(), - Self::AppLayerShell(any, any1) => f + Self::AppLayerShell(arg, arg1, arg2) => f .debug_tuple("AppLayerShell") - .field(any) - .field(any1) + .field(arg) + .field(arg1) + .field(arg2) + .finish(), + Self::LayerShell(arg, arg1, arg2) => f + .debug_tuple("LayerShell") + .field(arg) + .field(arg1) + .field(arg2) .finish(), - Self::LayerShell(any, any1) => { - f.debug_tuple("LayerShell").field(any).field(any1).finish() - } Self::DestroyLayerShell(arg0) => { f.debug_tuple("DestroyLayerShell").field(arg0).finish() } diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index bd950bff..b66f95b1 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -81,7 +81,7 @@ impl ContextMenu<'_, Message> { my_state: &mut LocalState, ) { if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { - use crate::surface::action::destroy_popup; + use crate::surface::action::{LiveSettings, destroy_popup}; use crate::widget::menu::Menu; use iced_runtime::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, @@ -187,6 +187,7 @@ impl ContextMenu<'_, Message> { let parent = self.window_id; shell.publish((self.on_surface_action.as_ref().unwrap())( crate::surface::action::simple_popup( + || LiveSettings::default(), move || SctkPopupSettings { parent, id, diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 046cf84d..79f854fb 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -580,6 +580,8 @@ pub fn update< use iced_runtime::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, }; + + use crate::surface::action::LiveSettings; let bounds = layout.bounds(); let anchor_rect = Rectangle { x: bounds.x as i32, @@ -606,6 +608,7 @@ pub fn update< let on_surface_action_clone = on_surface_action.clone(); let translation = layout.virtual_offset(); let get_popup_action = surface::action::simple_popup::( + || LiveSettings::default(), move || { SctkPopupSettings { parent, diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index b5ffca42..facdfb85 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -385,7 +385,7 @@ where my_state: &mut MenuBarState, ) { if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { - use crate::surface::action::destroy_popup; + use crate::surface::action::{LiveSettings, destroy_popup}; use iced_runtime::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, }; @@ -501,6 +501,7 @@ where }; let parent = self.window_id; shell.publish((surface_action)(crate::surface::action::simple_popup( + || LiveSettings::default(), move || SctkPopupSettings { parent, id, diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index bb33be5e..b2e7fd9d 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -988,6 +988,8 @@ impl Widget Widget( > = Box::new(move || s(bounds)); let boxed: Box = Box::new(boxed); + + let boxed_live: Box< + dyn Fn() -> LiveSettings + Send + Sync + 'static, + > = Box::new(move || Default::default()); + let boxed_live: Box = + Box::new(boxed_live); crate::surface::Action::Popup( Arc::new(boxed), + Arc::new(boxed_live), Some({ let boxed: Box< dyn Fn() -> crate::Element< @@ -553,9 +562,14 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>( + 'static, > = Box::new(move || s(bounds)); let boxed: Box = Box::new(boxed); + let boxed_live: Box LiveSettings + Send + Sync + 'static> = + Box::new(move || Default::default()); + let boxed_live: Box = + Box::new(boxed_live); let sm = crate::surface::Action::Popup( Arc::new(boxed), + Arc::new(boxed_live), Some({ let boxed: Box< dyn Fn() -> crate::Element<