libcosmic-yoda/src/app/cosmic.rs
2026-07-09 17:45:24 -04:00

1840 lines
77 KiB
Rust

// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use std::borrow::Borrow;
use std::collections::HashMap;
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"))]
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
use cosmic_theme::ThemeMode;
#[cfg(all(feature = "wayland", target_os = "linux"))]
use enumflags2::BitFlags;
#[cfg(not(any(feature = "multi-window", feature = "wayland", target_os = "linux")))]
use iced::Application as IcedApplication;
#[cfg(all(feature = "wayland", target_os = "linux"))]
use iced::event::wayland;
use iced::{Task, theme, window};
use iced_futures::event::listen_with;
#[cfg(feature = "winit")]
use iced_winit::SurfaceIdWrapper;
use palette::color_difference::EuclideanDistance;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum WindowingSystem {
UiKit,
AppKit,
Orbital,
OhosNdk,
Xlib,
Xcb,
Wayland,
Drm,
Gbm,
Win32,
WinRt,
Web,
WebCanvas,
WebOffscreenCanvas,
AndroidNdk,
Haiku,
}
pub(crate) static WINDOWING_SYSTEM: std::sync::OnceLock<WindowingSystem> =
std::sync::OnceLock::new();
pub fn windowing_system() -> Option<WindowingSystem> {
WINDOWING_SYSTEM.get().copied()
}
fn init_windowing_system<M>(handle: window::raw_window_handle::WindowHandle) -> crate::Action<M> {
let raw = handle.as_ref();
let system = match raw {
window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit,
window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit,
window::raw_window_handle::RawWindowHandle::Orbital(_) => WindowingSystem::Orbital,
window::raw_window_handle::RawWindowHandle::OhosNdk(_) => WindowingSystem::OhosNdk,
window::raw_window_handle::RawWindowHandle::Xlib(_) => WindowingSystem::Xlib,
window::raw_window_handle::RawWindowHandle::Xcb(_) => WindowingSystem::Xcb,
window::raw_window_handle::RawWindowHandle::Wayland(_) => WindowingSystem::Wayland,
window::raw_window_handle::RawWindowHandle::Web(_) => WindowingSystem::Web,
window::raw_window_handle::RawWindowHandle::WebCanvas(_) => WindowingSystem::WebCanvas,
window::raw_window_handle::RawWindowHandle::WebOffscreenCanvas(_) => {
WindowingSystem::WebOffscreenCanvas
}
window::raw_window_handle::RawWindowHandle::AndroidNdk(_) => WindowingSystem::AndroidNdk,
window::raw_window_handle::RawWindowHandle::Haiku(_) => WindowingSystem::Haiku,
window::raw_window_handle::RawWindowHandle::Drm(_) => WindowingSystem::Drm,
window::raw_window_handle::RawWindowHandle::Gbm(_) => WindowingSystem::Gbm,
window::raw_window_handle::RawWindowHandle::Win32(_) => WindowingSystem::Win32,
window::raw_window_handle::RawWindowHandle::WinRt(_) => WindowingSystem::WinRt,
_ => {
tracing::warn!("Unknown windowing system: {raw:?}");
return crate::Action::Cosmic(Action::WindowingSystemInitialized);
}
};
_ = WINDOWING_SYSTEM.set(system);
crate::Action::Cosmic(Action::WindowingSystemInitialized)
}
#[derive(Default)]
pub struct Cosmic<App: Application> {
pub app: App,
pub surface_views: HashMap<
window::Id,
(
Option<window::Id>,
SurfaceIdWrapper,
Box<dyn for<'a> Fn(&'a App) -> crate::surface::action::LiveSettings>,
Option<
Box<
dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>>
+ Send
+ Sync
+ 'static,
>,
>,
),
>,
pub opened_surfaces: HashMap<window::Id, u32>,
blur_enabled: bool,
}
impl<T: Application> Cosmic<T>
where
T::Message: Send + 'static,
{
pub fn init(
(mut core, flags): (Core, T::Flags),
) -> (Self, iced::Task<crate::Action<T::Message>>) {
#[cfg(all(feature = "dbus-config", target_os = "linux"))]
{
use iced_futures::futures::executor::block_on;
core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok();
}
let id = core.main_window_id().unwrap_or(window::Id::RESERVED);
let (model, command) = T::init(core, flags);
let existing_theme = THEME.lock().unwrap();
let blur = existing_theme.transparent;
drop(existing_theme);
let mut cmds = vec![
iced_runtime::window::run_with_handle(id, init_windowing_system),
command,
];
if blur {
cmds.push(crate::task::message(crate::action::cosmic(
Action::BlurEnabled,
)));
}
(Self::new(model), Task::batch(cmds))
}
#[cfg(not(feature = "multi-window"))]
pub fn title(&self) -> String {
self.app.title().to_string()
}
#[cfg(feature = "multi-window")]
pub fn title(&self, id: window::Id) -> String {
self.app.title(id).to_string()
}
#[allow(clippy::too_many_lines)]
pub fn surface_update(
&mut self,
_surface_message: crate::surface::Action,
) -> iced::Task<crate::Action<T::Message>> {
#[cfg(feature = "surface-message")]
match _surface_message {
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::AppSubsurface(settings, live_settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok()
.and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else {
tracing::error!("Invalid settings for subsurface");
return Task::none();
};
let settings = settings(&mut self.app);
let view = view.and_then(|view| {
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
+ Send
+ Sync,
>>() {
Ok(v) => Some(v),
Err(err) => {
tracing::error!("Invalid view for subsurface view: {err:?}");
None
}
}
});
self.get_subsurface(settings, view.map(|v| *v))
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::Subsurface(settings, live_settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok()
.and_then(|s| s.downcast::<Box<dyn Fn() -> 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::<Box<
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
>>() {
Ok(v) => Some(v),
Err(err) => {
tracing::error!("Invalid view for subsurface view: {err:?}");
None
}
}
}) {
self.get_subsurface(settings, Some(Box::new(move |_| view())))
} else {
self.get_subsurface(settings, None)
}
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::AppPopup(settings, live_settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok()
.and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> 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::<Box<dyn Fn(&T) -> LiveSettings + Send + Sync>>()
.ok()
})
else {
tracing::error!("Invalid live settings for popup");
return Task::none();
};
let view = view.and_then(|view| {
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
+ Send
+ Sync,
>>() {
Ok(v) => Some(v),
Err(err) => {
tracing::error!("Invalid view for subsurface view: {err:?}");
None
}
}
});
let settings = settings(&mut self.app);
self.get_popup(settings, *live_settings, view.map(|v| *v))
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::DestroyPopup(id) => {
iced_winit::commands::popup::destroy_popup(id)
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::DestroyTooltipPopup => {
#[cfg(feature = "applet")]
{
iced_winit::commands::popup::destroy_popup(*crate::applet::TOOLTIP_WINDOW_ID)
}
#[cfg(not(feature = "applet"))]
{
Task::none()
}
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::DestroySubsurface(id) => {
iced_winit::commands::subsurface::destroy_subsurface(id)
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::DestroyWindow(id) => iced::window::close(id),
crate::surface::Action::ResponsiveMenuBar {
menu_bar,
limits,
size,
} => {
let core = self.app.core_mut();
core.menu_bars.insert(menu_bar, (limits, size));
iced::Task::none()
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::Popup(settings, live_settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok()
.and_then(|s| s.downcast::<Box<dyn Fn() -> 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::<Box<dyn Fn() -> LiveSettings + Send + Sync>>()
.ok()
})
else {
tracing::error!("Invalid live settings for popup");
return Task::none();
};
let settings = settings();
let live_settings = Box::new(move |_: &T| live_settings());
if let Some(view) = view.and_then(|view| {
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
>>() {
Ok(v) => Some(v),
Err(err) => {
tracing::error!("Invalid view for subsurface view: {err:?}");
None
}
}
}) {
self.get_popup(settings, live_settings, Some(Box::new(move |_| view())))
} else {
self.get_popup(settings, live_settings, None)
}
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::AppWindow(id, settings, live_settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
s.downcast::<Box<dyn Fn(&mut T) -> iced::window::Settings + Send + Sync>>()
.ok()
}) else {
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::<Box<dyn Fn(&T) -> LiveSettings + Send + Sync>>()
.ok()
})
else {
tracing::error!("Invalid live settings for popup");
return Task::none();
};
let view = view.and_then(|view| {
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
+ Send
+ Sync,
>>() {
Ok(v) => Some(v),
Err(err) => {
tracing::error!("Invalid view for AppWindow: {err:?}");
None
}
}
});
let settings = settings(&mut self.app);
self.get_window(id, settings, *live_settings, view.map(|v| *v))
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::Window(id, settings, live_settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
s.downcast::<Box<dyn Fn() -> iced::window::Settings + Send + Sync>>()
.ok()
}) else {
tracing::error!("Invalid settings for Window");
return Task::none();
};
let Some(live_settings) =
std::sync::Arc::try_unwrap(live_settings)
.ok()
.and_then(|s| {
s.downcast::<Box<dyn Fn() -> 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::<Box<
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
>>() {
Ok(v) => Some(v),
Err(err) => {
tracing::error!("Invalid view for Window: {err:?}");
None
}
}
}) {
let settings = settings();
self.get_window(
id,
settings,
Box::new(move |_| live_settings()),
Some(Box::new(move |_| view())),
)
} else {
let settings = settings();
iced_runtime::task::oneshot(|channel| {
iced_runtime::Action::Window(iced_runtime::window::Action::Open(
id, settings, channel,
))
})
.discard()
}
}
crate::surface::Action::Ignore => iced::Task::none(),
crate::surface::Action::Task(f) => {
f().map(|sm| crate::Action::Cosmic(Action::Surface(sm)))
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::AppLayerShell(settings, live_settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok()
.and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> 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::<Box<dyn Fn(&T) -> LiveSettings + Send + Sync>>()
.ok()
})
else {
tracing::error!("Invalid live settings for popup");
return Task::none();
};
let view = view.and_then(|view| {
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
+ Send
+ Sync,
>>() {
Ok(v) => Some(v),
Err(err) => {
tracing::error!("Invalid view for layer surface: {err:?}");
None
}
}
});
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, live_settings, view) => {
let Some(settings) = std::sync::Arc::try_unwrap(settings)
.ok()
.and_then(|s| s.downcast::<Box<dyn Fn() -> 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::<Box<dyn Fn() -> 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::<Box<
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
>>() {
Ok(v) => Some(v),
Err(err) => {
tracing::error!("Invalid view for layer surface: {err:?}");
None
}
}
}) {
self.get_layer_shell(settings, live_settings, Some(Box::new(move |_| view())))
} else {
self.get_layer_shell(settings, live_settings, None)
}
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
crate::surface::Action::DestroyLayerShell(id) => {
iced_winit::commands::layer_surface::destroy_layer_surface(id)
}
crate::surface::Action::SyncLiveSettings(id) => {
if let Some((_, id, live_settings, _)) = self.surface_views.get(&id) {
let live_settings = live_settings(&self.app);
return self.apply_live_settings(*id, &live_settings);
}
Task::none()
}
_ => iced::Task::none(),
}
#[cfg(not(feature = "surface-message"))]
iced::Task::none()
}
pub fn update(
&mut self,
message: crate::Action<T::Message>,
) -> iced::Task<crate::Action<T::Message>> {
let message = match message {
crate::Action::App(message) => self.app.update(message),
crate::Action::Cosmic(message) => self.cosmic_update(message),
crate::Action::None => iced::Task::none(),
#[cfg(feature = "single-instance")]
crate::Action::DbusActivation(message) => {
let mut task = self.app.dbus_activation(message);
if let Some(id) = self.app.core().main_window_id() {
let unminimize = iced_runtime::window::minimize::<()>(id, false);
task = task.chain(unminimize.discard());
}
task
}
};
#[cfg(all(target_env = "gnu", not(target_os = "windows")))]
crate::malloc::trim(0);
message
}
#[cfg(not(feature = "multi-window"))]
pub fn scale_factor(&self) -> f64 {
f64::from(self.app.core().scale_factor())
}
#[cfg(feature = "multi-window")]
pub fn scale_factor(&self, _id: window::Id) -> f64 {
f64::from(self.app.core().scale_factor())
}
pub fn style(&self, theme: &Theme) -> theme::Style {
if let Some(style) = self.app.style() {
style
} else if self.app.core().window.is_maximized {
let theme = THEME.lock().unwrap();
crate::style::iced::application::style(theme.borrow())
} else {
let theme = THEME.lock().unwrap();
theme::Style {
background_color: iced_core::Color::TRANSPARENT,
icon_color: theme.cosmic().on_bg_color().into(),
text_color: theme.cosmic().on_bg_color().into(),
}
}
}
#[allow(clippy::too_many_lines)]
#[cold]
pub fn subscription(&self) -> Subscription<crate::Action<T::Message>> {
let window_events = listen_with(|event, _, id| {
match event {
iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => {
return Some(Action::WindowResize(id, width, height));
}
iced::Event::Window(window::Event::Opened { .. }) => {
return Some(Action::Opened(id));
}
iced::Event::Window(window::Event::Closed) => {
return Some(Action::SurfaceClosed(id));
}
iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)),
iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)),
#[cfg(all(feature = "wayland", target_os = "linux"))]
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
match event {
wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
| wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => {
return Some(Action::SurfaceClosed(id));
}
#[cfg(feature = "applet")]
wayland::Event::Window(
iced::event::wayland::WindowEvent::SuggestedBounds(b),
) => {
return Some(Action::SuggestedBounds(b));
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
wayland::Event::Window(iced::event::wayland::WindowEvent::WindowState(
s,
)) => {
return Some(Action::WindowState(id, s));
}
wayland::Event::BlurEnabled => {
return Some(Action::BlurEnabled);
}
_ => (),
}
}
_ => (),
}
None
});
let mut subscriptions = vec![
self.app.subscription().map(crate::Action::App),
self.app
.core()
.watch_config::<crate::config::CosmicTk>(crate::config::ID)
.map(|update| {
for why in update
.errors
.into_iter()
.filter(cosmic_config::Error::is_err)
{
if let cosmic_config::Error::GetKey(_, err) = &why {
if err.kind() == std::io::ErrorKind::NotFound {
// No system default config installed; don't error
continue;
}
}
tracing::error!(?why, "cosmic toolkit config update error");
}
crate::Action::Cosmic(Action::ToolkitConfig(update.config))
}),
self.app
.core()
.watch_config::<cosmic_theme::Theme>(
if if let ThemeType::System { prefer_dark, .. } =
THEME.lock().unwrap().theme_type
{
prefer_dark
} else {
None
}
.unwrap_or_else(|| self.app.core().system_theme_mode.is_dark)
{
cosmic_theme::DARK_THEME_ID
} else {
cosmic_theme::LIGHT_THEME_ID
},
)
.map(|update| {
for why in update
.errors
.into_iter()
.filter(cosmic_config::Error::is_err)
{
tracing::error!(?why, "cosmic theme config update error");
}
Action::SystemThemeChange(
update.keys,
crate::theme::Theme::system(Arc::new(update.config)),
)
})
.map(crate::Action::Cosmic),
self.app
.core()
.watch_config::<ThemeMode>(cosmic_theme::THEME_MODE_ID)
.map(|update| {
for error in update
.errors
.into_iter()
.filter(cosmic_config::Error::is_err)
{
tracing::error!(?error, "error reading system theme mode update");
}
Action::SystemThemeModeChange(update.keys, update.config)
})
.map(crate::Action::Cosmic),
window_events.map(crate::Action::Cosmic),
#[cfg(feature = "xdg-portal")]
crate::theme::portal::desktop_settings()
.map(Action::DesktopSettings)
.map(crate::Action::Cosmic),
];
if self.app.core().keyboard_nav {
subscriptions.push(
keyboard_nav::subscription()
.map(Action::KeyboardNav)
.map(crate::Action::Cosmic),
);
}
#[cfg(feature = "single-instance")]
if self.app.core().single_instance {
subscriptions.push(crate::dbus_activation::subscription::<T>());
}
Subscription::batch(subscriptions)
}
#[cfg(not(feature = "multi-window"))]
pub fn theme(&self) -> Theme {
crate::theme::active()
}
#[cfg(feature = "multi-window")]
pub fn theme(&self, _id: window::Id) -> Theme {
crate::theme::active()
}
#[cfg(feature = "multi-window")]
pub fn view(&self, id: window::Id) -> Element<'_, crate::Action<T::Message>> {
if let Some((_, _, _, Some(v))) = self.surface_views.get(&id) {
return v(&self.app);
}
if self
.app
.core()
.main_window_id()
.is_none_or(|main_id| main_id != id)
{
return self.app.view_window(id).map(crate::Action::App);
}
let view = if self.app.core().window.use_template {
self.app.view_main()
} else {
self.app.view().map(crate::Action::App)
};
#[cfg(all(target_env = "gnu", not(target_os = "windows")))]
crate::malloc::trim(0);
view
}
#[cfg(not(feature = "multi-window"))]
pub fn view(&self) -> Element<crate::Action<T::Message>> {
let view = self.app.view_main();
#[cfg(all(target_env = "gnu", not(target_os = "windows")))]
crate::malloc::trim(0);
view
}
}
impl<T: Application> Cosmic<T> {
#[allow(clippy::unused_self)]
#[cold]
pub fn close(&mut self) -> iced::Task<crate::Action<T::Message>> {
if let Some(id) = self.app.core().main_window_id() {
iced::window::close(id)
} else {
iced::Task::none()
}
}
#[allow(clippy::too_many_lines)]
fn cosmic_update(&mut self, message: Action) -> iced::Task<crate::Action<T::Message>> {
match message {
Action::WindowMaximized(id, maximized) => {
#[cfg(not(all(feature = "wayland", target_os = "linux")))]
if self
.app
.core()
.main_window_id()
.is_some_and(|main_id| main_id == id)
{
self.app.core_mut().window.sharp_corners = maximized;
}
}
Action::WindowResize(id, width, height) => {
if self
.app
.core()
.main_window_id()
.is_some_and(|main_id| main_id == id)
{
self.app.core_mut().set_window_width(width);
self.app.core_mut().set_window_height(height);
}
self.app.on_window_resize(id, width, height);
//TODO: more efficient test of maximized (winit has no event for maximize if set by the OS)
return iced::window::is_maximized(id).map(move |maximized| {
crate::Action::Cosmic(Action::WindowMaximized(id, maximized))
});
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
Action::WindowState(id, state) => {
if self
.app
.core()
.main_window_id()
.is_some_and(|main_id| main_id == id)
{
self.app.core_mut().window.sharp_corners = state.intersects(
WindowState::MAXIMIZED
| WindowState::FULLSCREEN
| WindowState::TILED
| WindowState::TILED_RIGHT
| WindowState::TILED_LEFT
| WindowState::TILED_TOP
| WindowState::TILED_BOTTOM,
);
self.app.core_mut().window.is_maximized =
state.intersects(WindowState::MAXIMIZED | WindowState::FULLSCREEN);
}
{
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();
let cur_rad = self.app.core().corners(&theme, rounded);
return Task::batch([corner_radius(id, cur_rad).discard()]);
}
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
Action::WmCapabilities(id, capabilities) => {
if self
.app
.core()
.main_window_id()
.is_some_and(|main_id| main_id == id)
{
self.app.core_mut().window.show_maximize =
capabilities.contains(WindowManagerCapabilities::MAXIMIZE);
self.app.core_mut().window.show_minimize =
capabilities.contains(WindowManagerCapabilities::MINIMIZE);
self.app.core_mut().window.show_window_menu =
capabilities.contains(WindowManagerCapabilities::WINDOW_MENU);
}
}
Action::KeyboardNav(message) => match message {
keyboard_nav::Action::FocusNext => {
return iced::widget::operation::focus_next().map(crate::Action::Cosmic);
}
keyboard_nav::Action::FocusPrevious => {
return iced::widget::operation::focus_previous().map(crate::Action::Cosmic);
}
keyboard_nav::Action::Escape => return self.app.on_escape(),
keyboard_nav::Action::Search => return self.app.on_search(),
keyboard_nav::Action::Fullscreen => return self.app.core().toggle_maximize(None),
},
Action::ContextDrawer(show) => {
self.app.core_mut().set_show_context(show);
return self.app.on_context_drawer();
}
Action::Drag => return self.app.core().drag(None),
Action::Minimize => return self.app.core().minimize(None),
Action::Maximize => return self.app.core().toggle_maximize(None),
Action::NavBar(key) => {
self.app.core_mut().nav_bar_set_toggled_condensed(false);
return self.app.on_nav_select(key);
}
Action::NavBarContext(key) => {
self.app.core_mut().nav_bar_set_context(key);
return self.app.on_nav_context(key);
}
Action::ToggleNavBar => {
self.app.core_mut().nav_bar_toggle();
}
Action::ToggleNavBarCondensed => {
self.app.core_mut().nav_bar_toggle_condensed();
}
Action::AppThemeChange(mut theme) => {
if let ThemeType::System { theme: _, .. } = theme.theme_type {
self.app.core_mut().theme_sub_counter += 1;
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)));
}
}
}
let new_blur = self.blur_enabled && {
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,
}
};
theme.transparent = new_blur;
let mut guard = THEME.lock().unwrap();
guard.set_theme(theme.theme_type.clone());
guard.transparent = new_blur;
drop(guard);
#[cfg(all(feature = "wayland", target_os = "linux"))]
{
let core = self.app.core();
let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
let blur = if new_blur {
iced::window::enable_blur
} else {
iced::window::disable_blur
};
if core.blur(&theme, None) {
cmds.push(blur(
self.app
.core()
.main_window_id()
.unwrap_or(window::Id::RESERVED),
));
}
for (id, wrapper, ..) in &self.surface_views {
let overriden = wrapper.2(&self.app);
if core.blur(&theme, Some(wrapper.1)) && overriden.blur.unwrap_or(true) {
cmds.push(blur(*id));
} else if overriden.blur.is_some_and(|b| !b) {
cmds.push(iced::window::disable_blur(*id));
}
}
return Task::batch(cmds);
}
}
Action::SystemThemeChange(keys, mut theme) => {
let cur_is_dark = self.app.core().system_theme_mode.is_dark;
// Ignore updates if the current theme mode does not match.
if cur_is_dark != theme.cosmic().is_dark {
return iced::Task::none();
}
// update transparent
let new_blur = self.blur_enabled && {
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,
}
};
theme.transparent = new_blur;
let cmd = self.app.system_theme_update(&keys, theme.cosmic());
// 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;
{
let mut cosmic_theme = THEME.lock().unwrap();
// Only apply update if the theme is set to load a system theme
if let ThemeType::System {
theme: _,
prefer_dark,
} = cosmic_theme.theme_type
{
let mut 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
};
new_theme.transparent = new_blur;
new_theme.theme_type.prefer_dark(prefer_dark);
cosmic_theme.set_theme(new_theme.theme_type);
cosmic_theme.transparent = new_blur;
#[cfg(all(feature = "wayland", target_os = "linux"))]
{
use iced_winit::platform_specific::commands::corner_radius::corner_radius;
let rounded = self.app.core().sync_window_border_radii_to_theme()
&& !self.app.core().window.sharp_corners;
let cur_rad = self.app.core().corners(&cosmic_theme, rounded);
// Update radius for the main window
let main_window_id = self
.app
.core()
.main_window_id()
.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, 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;
}
cmds.push(corner_radius(*id, cur_rad).discard());
}
let blur = if new_blur {
iced::window::enable_blur
} else {
iced::window::disable_blur
};
cmds.push(blur(
self.app
.core()
.main_window_id()
.unwrap_or(window::Id::RESERVED),
));
for (id, wrapper, ..) in &self.surface_views {
let overriden = wrapper.2(&self.app);
if self.app.core().blur(&cosmic_theme, Some(wrapper.1))
&& overriden.blur.unwrap_or(true)
{
cmds.push(blur(*id));
} else if overriden.blur.is_some_and(|b| !b) {
cmds.push(iced::window::disable_blur(*id));
}
}
return Task::batch(cmds);
}
}
}
return cmd;
}
Action::ScaleFactor(factor) => {
self.app.core_mut().set_scale_factor(factor);
}
Action::Close => {
return match self.app.on_app_exit() {
Some(message) => self.app.update(message),
None => self.close(),
};
}
Action::SystemThemeModeChange(keys, mode) => {
if match THEME.lock().unwrap().theme_type {
ThemeType::System {
theme: _,
prefer_dark,
} => prefer_dark.is_some(),
_ => false,
} {
return iced::Task::none();
}
let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)];
let core = self.app.core_mut();
core.system_theme_mode = mode;
let is_dark = core.system_is_dark();
let changed = core.system_theme_mode.is_dark != is_dark
|| core.portal_is_dark != Some(is_dark)
|| core.system_theme.cosmic().is_dark != is_dark;
if changed {
core.theme_sub_counter += 1;
let mut new_theme = if is_dark {
crate::theme::system_dark()
} else {
crate::theme::system_light()
};
cmds.push(self.app.system_theme_update(&[], new_theme.cosmic()));
let core = self.app.core_mut();
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
};
let new_blur = self.blur_enabled && {
let t = new_theme.cosmic();
match 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,
}
};
new_theme.transparent = new_blur;
core.system_theme = new_theme.clone();
{
let mut cosmic_theme = THEME.lock().unwrap();
// 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);
cosmic_theme.transparent = new_blur;
#[cfg(all(feature = "wayland", target_os = "linux"))]
{
use iced_winit::platform_specific::commands::corner_radius::corner_radius;
let rounded = self.app.core().sync_window_border_radii_to_theme()
&& !self.app.core().window.sharp_corners;
let cur_rad = self.app.core().corners(&cosmic_theme, rounded);
// Update radius for the main window
let main_window_id = self
.app
.core()
.main_window_id()
.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, 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;
}
cmds.push(corner_radius(*id, cur_rad).discard());
}
let core = self.app.core();
let blur = if cosmic_theme.transparent {
iced::window::enable_blur
} else {
iced::window::disable_blur
};
if core.blur(&cosmic_theme, None) {
cmds.push(blur(
self.app
.core()
.main_window_id()
.unwrap_or(window::Id::RESERVED),
));
}
for (id, wrapper, ..) in &self.surface_views {
let overriden = wrapper.2(&self.app);
if core.blur(&cosmic_theme, Some(wrapper.1))
&& overriden.blur.unwrap_or(true)
{
cmds.push(blur(*id));
} else if overriden.blur.is_some_and(|b| !b) {
cmds.push(iced::window::disable_blur(*id));
}
}
return Task::batch(cmds);
}
}
}
}
return Task::batch(cmds);
}
Action::Activate(_token) => {
if let Some(id) = self.app.core().main_window_id() {
// Unminimize window before requesting to activate it.
let mut task = iced_runtime::window::minimize(id, false);
#[cfg(all(feature = "wayland", target_os = "linux"))]
{
task = task.chain(
iced_winit::platform_specific::commands::activation::activate(
id,
#[allow(clippy::used_underscore_binding)]
_token,
),
);
}
#[cfg(not(all(feature = "wayland", target_os = "linux")))]
{
task = task.chain(iced_runtime::window::gain_focus(id));
}
return task;
}
}
Action::Surface(action) => return self.surface_update(action),
Action::SurfaceClosed(id) => {
if self.opened_surfaces.get_mut(&id).is_some_and(|v| {
*v = v.saturating_sub(1);
*v == 0
}) {
self.opened_surfaces.remove(&id);
self.surface_views.remove(&id);
}
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)
} else {
Task::none()
};
let core = self.app.core();
if core.exit_on_main_window_closed
&& core.main_window_id().is_some_and(|m_id| id == m_id)
{
ret = Task::batch([iced::exit::<crate::Action<T::Message>>()]);
}
return ret;
}
Action::ShowWindowMenu => {
if let Some(id) = self.app.core().main_window_id() {
return iced::window::show_system_menu(id);
}
}
#[cfg(feature = "single-instance")]
Action::DbusConnection(conn) => {
return self.app.dbus_connection(conn);
}
#[cfg(feature = "xdg-portal")]
Action::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => {
use ashpd::desktop::settings::ColorScheme;
if match THEME.lock().unwrap().theme_type {
ThemeType::System {
theme: _,
prefer_dark,
} => prefer_dark.is_some(),
_ => false,
} {
return iced::Task::none();
}
let is_dark = match s {
ColorScheme::NoPreference => None,
ColorScheme::PreferDark => Some(true),
ColorScheme::PreferLight => Some(false),
};
let core = self.app.core_mut();
core.portal_is_dark = is_dark;
let is_dark = core.system_is_dark();
let changed = core.system_theme_mode.is_dark != is_dark
|| core.portal_is_dark != Some(is_dark)
|| core.system_theme.cosmic().is_dark != is_dark;
if changed {
core.theme_sub_counter += 1;
let mut new_theme = if is_dark {
crate::theme::system_dark()
} else {
crate::theme::system_light()
};
if let ThemeType::System { .. } = new_theme.theme_type {
let new_blur = self.blur_enabled && {
let t = new_theme.cosmic();
match 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,
}
};
new_theme.transparent = new_blur;
}
core.system_theme = new_theme.clone();
let core = self.app.core();
{
let mut cosmic_theme = THEME.lock().unwrap();
// Only apply update if the theme is set to load a system theme
if let ThemeType::System { theme: _, .. } = cosmic_theme.theme_type {
let mut cmds = Vec::with_capacity(1);
#[cfg(all(feature = "wayland", target_os = "linux"))]
{
let blur = if cosmic_theme.transparent {
iced::window::enable_blur
} else {
iced::window::disable_blur
};
if core.blur(&cosmic_theme, None) {
cmds.push(blur(
core.main_window_id().unwrap_or(window::Id::RESERVED),
));
}
for (id, wrapper, ..) in &self.surface_views {
let overriden = wrapper.2(&self.app);
if core.blur(&cosmic_theme, Some(wrapper.1))
&& overriden.blur.unwrap_or(true)
{
cmds.push(blur(*id));
} else if overriden.blur.is_some_and(|b| !b) {
cmds.push(iced::window::disable_blur(*id));
}
}
}
cosmic_theme.set_theme(new_theme.theme_type);
return Task::batch(cmds);
}
}
}
}
#[cfg(feature = "xdg-portal")]
Action::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => {
use palette::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::Task::none();
}
{
let mut cosmic_theme = THEME.lock().unwrap();
// Only apply update if the theme is set to load a system theme
if let ThemeType::System {
theme: t,
prefer_dark,
} = cosmic_theme.theme_type.clone()
{
cosmic_theme.set_theme(ThemeType::System {
theme: Arc::new(t.with_accent(c)),
prefer_dark,
});
}
}
}
#[cfg(feature = "xdg-portal")]
Action::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => {
// TODO when high contrast is integrated in settings and all custom themes
}
Action::ToolkitConfig(config) => {
// Change the icon theme if not defined by the application.
if !self.app.core().icon_theme_override
&& crate::icon_theme::default() != config.icon_theme
{
crate::icon_theme::set_default(config.icon_theme.clone());
}
*crate::config::COSMIC_TK.write().unwrap() = config;
}
Action::Focus(f) => {
#[cfg(all(
feature = "wayland",
feature = "multi-window",
feature = "surface-message",
target_os = "linux"
))]
if let Some((
parent,
SurfaceIdWrapper::Subsurface(_) | SurfaceIdWrapper::Popup(_),
_,
_,
)) = self.surface_views.get(&f)
{
// If the parent is already focused, push the new focus
// to the end of the focus chain.
if parent.is_some_and(|p| self.app.core().focused_window.last() == Some(&p)) {
self.app.core_mut().focused_window.push(f);
return iced::Task::none();
} else {
// set the whole parent chain to the focus chain
let mut parent_chain = vec![f];
let mut cur = *parent;
while let Some(p) = cur {
parent_chain.push(p);
cur = self
.surface_views
.get(&p)
.and_then(|(parent, _, _, _)| *parent);
}
parent_chain.reverse();
self.app.core_mut().focused_window = parent_chain;
return iced::Task::none();
}
}
self.app.core_mut().focused_window = vec![f];
}
Action::Unfocus(id) => {
let core = self.app.core_mut();
if core.focused_window().as_ref().is_some_and(|cur| *cur == id) {
core.focused_window.pop();
}
}
#[cfg(feature = "applet")]
Action::SuggestedBounds(b) => {
tracing::info!("Suggested bounds: {b:?}");
let core = self.app.core_mut();
core.applet.suggested_bounds = b;
}
Action::Opened(id) => {
#[cfg(all(feature = "wayland", target_os = "linux"))]
{
use iced_winit::platform_specific::commands::corner_radius::corner_radius;
let mut theme = THEME.lock().unwrap();
// 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, _, _)| {
matches!(
surface_type,
SurfaceIdWrapper::Popup(_) | SurfaceIdWrapper::LayerSurface(_)
)
});
let new_blur = self.blur_enabled && {
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,
}
};
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)
&& self
.surface_views
.get(&id)
.and_then(|s| s.2(&self.app).blur)
.unwrap_or(true)
{
let blur = if new_blur {
iced::window::enable_blur
} else {
iced::window::disable_blur
};
let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
cmds.push(blur(id));
Task::batch(cmds)
} else {
Task::none()
};
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
.app
.core()
.main_window_id()
.unwrap_or(window::Id::RESERVED)
{
corner_radius(id, self.app.core().corners(&theme, rounded)).discard()
} else {
Task::none()
};
return Task::batch([
blur_cmd,
corner_task,
iced_runtime::window::run_with_handle(id, init_windowing_system),
]);
}
return iced_runtime::window::run_with_handle(id, init_windowing_system);
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
Action::BlurEnabled => {
// TODO do this after blur event confirms support instead of for all wayland windows
self.blur_enabled = true;
let mut t = THEME.lock().unwrap();
let new_blur = self.blur_enabled && {
let t = t.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,
}
};
t.transparent = new_blur;
self.app.core_mut().system_theme.transparent = new_blur;
{
let blur = if new_blur {
iced::window::enable_blur
} else {
iced::window::disable_blur
};
let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
if self.app.core().blur(&t, None) {
cmds.push(blur(
self.app
.core()
.main_window_id()
.unwrap_or(window::Id::RESERVED),
));
}
for (id, wrapper, ..) in &self.surface_views {
let overriden = wrapper.2(&self.app);
if self.app.core().blur(&t, Some(wrapper.1))
&& overriden.blur.unwrap_or(true)
{
cmds.push(blur(*id));
} else if overriden.blur.is_some_and(|b| !b) {
cmds.push(iced::window::disable_blur(*id));
}
}
return Task::batch(cmds);
}
}
_ => (),
}
iced::Task::none()
}
}
impl<App: Application> Cosmic<App> {
pub fn new(app: App) -> Self {
Self {
app,
surface_views: HashMap::new(),
opened_surfaces: HashMap::new(),
blur_enabled: false,
}
}
#[cfg(feature = "surface-message")]
/// Apply live setting overrides for a surface
pub fn apply_live_settings(
&mut self,
id_wrapper: SurfaceIdWrapper,
live_settings: &crate::surface::action::LiveSettings,
) -> Task<crate::Action<App::Message>> {
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 {
if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) {
window::enable_blur(id)
} else {
#[cfg(all(feature = "wayland", feature = "winit", target_os = "linux"))]
{
iced_winit::commands::blur::blur(
id,
Some(vec![iced::Rectangle::new(
iced::Point::ORIGIN,
iced::Size::INFINITE,
)]),
)
.discard()
}
#[cfg(not(all(feature = "wayland", feature = "winit", target_os = "linux")))]
{
iced::window::enable_blur(id)
}
}
} else if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) {
window::disable_blur(id)
} else {
#[cfg(all(feature = "wayland", feature = "winit", target_os = "linux"))]
{
iced_winit::commands::blur::blur(id, None).discard()
}
#[cfg(not(all(feature = "wayland", feature = "winit", target_os = "linux")))]
{
iced::window::disable_blur(id)
}
};
cmds.push(blur_cmd);
} else if self.app.core().blur(&t, Some(id_wrapper)) {
cmds.push(if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) {
window::enable_blur(id)
} else {
#[cfg(all(feature = "wayland", feature = "winit", target_os = "linux"))]
{
iced_winit::commands::blur::blur(
id,
Some(vec![iced::Rectangle::new(
iced::Point::ORIGIN,
iced::Size::INFINITE,
)]),
)
.discard()
}
#[cfg(not(all(feature = "wayland", feature = "winit", target_os = "linux")))]
{
iced::window::enable_blur(id)
}
});
}
#[cfg(all(feature = "wayland", feature = "winit", target_os = "linux"))]
if let Some(corners) = live_settings.corners {
cmds.push(
iced_winit::commands::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(
iced_winit::commands::corner_radius::corner_radius(id, Some(cur_rad)).discard(),
);
}
}
#[cfg(all(feature = "wayland", feature = "winit", target_os = "linux"))]
if let (SurfaceIdWrapper::LayerSurface(id), Some(padding)) =
(id_wrapper, live_settings.padding)
{
cmds.push(iced_winit::commands::layer_surface::set_padding(
id, padding,
));
}
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: Option<
Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
>,
) -> Task<crate::Action<App::Message>> {
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: None,
corners: None,
padding: None,
},
);
self.surface_views.insert(
settings.id,
(
Some(settings.parent),
SurfaceIdWrapper::Subsurface(settings.id),
Box::new(|_| LiveSettings::default()),
view,
),
);
Task::batch([live_settings_task, get_subsurface(settings)])
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
/// Create a subsurface
pub fn get_popup(
&mut self,
settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
view: Option<
Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
>,
) -> Task<crate::Action<App::Message>> {
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,
),
);
live_settings_task.chain(get_popup(settings)).discard()
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
/// Create a window surface
pub fn get_window(
&mut self,
id: iced::window::Id,
settings: iced::window::Settings,
live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
view: Option<
Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
>,
) -> Task<crate::Action<App::Message>> {
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,
),
);
Task::batch([
iced_runtime::task::oneshot(|channel| {
iced_runtime::Action::Window(iced_runtime::window::Action::Open(
id, settings, channel,
))
})
.discard(),
// We don't control window creation in the same way
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,
live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
view: Option<
Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
>,
) -> Task<crate::Action<App::Message>> {
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,
),
);
Task::batch([live_settings_task, get_layer_surface(settings)])
}
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
fn corners(
surface_type: SurfaceIdWrapper,
rounded: bool,
theme: &Theme,
auto_corner_radius: BitFlags<Auto>,
) -> Option<iced_runtime::platform_specific::wayland::CornerRadius> {
if !match surface_type {
SurfaceIdWrapper::Window(_) => auto_corner_radius.contains(Auto::Window),
SurfaceIdWrapper::LayerSurface(_) => auto_corner_radius.contains(Auto::System),
SurfaceIdWrapper::Popup(_) => auto_corner_radius.contains(Auto::Popup),
_ => false,
} {
return None;
}
let theme = theme.cosmic();
Some(if let SurfaceIdWrapper::Popup(_) = surface_type {
let radius_m = theme.radius_m();
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_m[0].round() as u32,
top_right: radius_m[1].round() as u32,
bottom_right: radius_m[2].round() as u32,
bottom_left: radius_m[3].round() as u32,
}
} else if let SurfaceIdWrapper::Window(_) = surface_type
&& !rounded
{
let radius_0 = theme.radius_0();
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_0[0].round() as u32,
top_right: radius_0[1].round() as u32,
bottom_right: radius_0[2].round() as u32,
bottom_left: radius_0[3].round() as u32,
}
} else {
let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_s[0].round() as u32,
top_right: radius_s[1].round() as u32,
bottom_right: radius_s[2].round() as u32,
bottom_left: radius_s[3].round() as u32,
}
})
}