From 5a7e766c0877979516b3884a327ff2669c4809c3 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 5 Mar 2025 16:01:23 +0100 Subject: [PATCH] refactor!: improve surface action handling and adjust libcosmic API --- examples/application/src/main.rs | 24 +- src/action.rs | 36 +++ src/app/action.rs | 75 +++++ src/app/command.rs | 94 ------- src/app/context_drawer.rs | 3 + src/app/cosmic.rs | 259 +++++++---------- src/app/mod.rs | 311 ++++----------------- src/app/multi_window.rs | 3 + src/applet/mod.rs | 21 +- src/command.rs | 43 +++ src/{app => }/core.rs | 132 ++++----- src/keyboard_nav.rs | 14 +- src/lib.rs | 16 +- src/malloc.rs | 3 + src/process.rs | 3 + src/surface/action.rs | 150 ++++++++++ src/{surface_message.rs => surface/mod.rs} | 31 +- src/task.rs | 25 ++ src/task/mod.rs | 57 ---- src/widget/mod.rs | 6 + src/widget/responsive_container.rs | 30 +- src/widget/responsive_menu_bar.rs | 78 ++++++ src/widget/wayland/tooltip/widget.rs | 140 ++++------ 23 files changed, 755 insertions(+), 799 deletions(-) create mode 100644 src/action.rs create mode 100644 src/app/action.rs delete mode 100644 src/app/command.rs create mode 100644 src/command.rs rename src/{app => }/core.rs (80%) create mode 100644 src/surface/action.rs rename src/{surface_message.rs => surface/mod.rs} (88%) create mode 100644 src/task.rs delete mode 100644 src/task/mod.rs create mode 100644 src/widget/responsive_menu_bar.rs diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index fdd14ce2..bcffc316 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -11,7 +11,6 @@ use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::widget::column; use cosmic::iced::Length; use cosmic::iced_core::Size; -use cosmic::surface_message::{MessageWrapper, SurfaceMessage}; use cosmic::widget::icon::{from_name, Handle}; use cosmic::widget::menu::KeyBind; use cosmic::widget::{button, text}; @@ -85,25 +84,10 @@ pub enum Message { Input2(String), Ignore, ToggleHide, - Surface(SurfaceMessage), + Surface(cosmic::surface::Action), Hi, } -impl From for MessageWrapper { - fn from(value: Message) -> Self { - match value { - Message::Surface(s) => MessageWrapper::Surface(s), - m => MessageWrapper::Message(m), - } - } -} - -impl From for Message { - fn from(value: SurfaceMessage) -> Self { - Message::Surface(value) - } -} - /// The [`App`] stores application-specific state. pub struct App { core: Core, @@ -185,7 +169,7 @@ impl cosmic::Application for App { self.hidden = !self.hidden; } Message::Surface(_) => { - unimplemented!() + // unimplemented!() } Message::Hi => { dbg!("hi"); @@ -295,9 +279,11 @@ impl cosmic::Application for App { } #[cfg(feature = "wayland")] { - vec![self.core.responsive_menu_bar( + vec![cosmic::widget::responsive_menu_bar( + self.core(), &self.keybinds, MENU_ID.clone(), + Message::Surface, vec![ ( "hiiiiiiiiiiiiiiiiiii 1".into(), diff --git a/src/action.rs b/src/action.rs new file mode 100644 index 00000000..23a98685 --- /dev/null +++ b/src/action.rs @@ -0,0 +1,36 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::app; + +pub const fn app(message: M) -> Action { + Action::App(message) +} + +pub const fn cosmic(message: app::Action) -> Action { + Action::Cosmic(message) +} + +pub const fn none() -> Action { + Action::None +} + +#[derive(Clone, Debug)] +#[must_use] +pub enum Action { + /// Messages from the application, for the application. + App(M), + /// Internal messages to be handled by libcosmic. + Cosmic(app::Action), + #[cfg(feature = "single-instance")] + /// Dbus activation messages + DbusActivation(app::DbusActivationMessage), + /// Do nothing + None, +} + +impl From for Action { + fn from(value: M) -> Self { + Self::App(value) + } +} diff --git a/src/app/action.rs b/src/app/action.rs new file mode 100644 index 00000000..44655ffa --- /dev/null +++ b/src/app/action.rs @@ -0,0 +1,75 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::surface; +use crate::theme::Theme; +use crate::widget::nav_bar; +use crate::{config::CosmicTk, keyboard_nav}; +#[cfg(feature = "wayland")] +use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; +use cosmic_theme::ThemeMode; +#[cfg(not(any(feature = "multi-window", feature = "wayland")))] +use iced::Application as IcedApplication; + +/// A message managed internally by COSMIC. +#[derive(Clone, Debug)] +pub enum Action { + /// Activate the application + Activate(String), + /// Application requests theme change. + AppThemeChange(Theme), + /// Requests to close the window. + Close, + /// Closes or shows the context drawer. + ContextDrawer(bool), + /// Requests to drag the window. + Drag, + /// Window focus changed + Focus(iced::window::Id), + /// Keyboard shortcuts managed by libcosmic. + KeyboardNav(keyboard_nav::Action), + /// Requests to maximize the window. + Maximize, + /// Requests to minimize the window. + Minimize, + /// Activates a navigation element from the nav bar. + NavBar(nav_bar::Id), + /// Activates a context menu for an item from the nav bar. + NavBarContext(nav_bar::Id), + /// Set scaling factor + ScaleFactor(f32), + /// Show the window menu + ShowWindowMenu, + /// Tracks updates to window suggested size. + #[cfg(feature = "applet")] + SuggestedBounds(Option), + /// Internal surface message + Surface(surface::Action), + /// Notifies that a surface was closed. + /// Any data relating to the surface should be cleaned up. + SurfaceClosed(iced::window::Id), + /// Notification of system theme changes. + SystemThemeChange(Vec<&'static str>, Theme), + /// Notification of system theme mode changes. + SystemThemeModeChange(Vec<&'static str>, ThemeMode), + /// Toggles visibility of the nav bar. + ToggleNavBar, + /// Toggles the condensed status of the nav bar. + ToggleNavBarCondensed, + /// Toolkit configuration update + ToolkitConfig(CosmicTk), + /// Window focus lost + Unfocus(iced::window::Id), + /// Updates the window maximized state + WindowMaximized(iced::window::Id, bool), + /// Updates the tracked window geometry. + WindowResize(iced::window::Id, f32, f32), + /// Tracks updates to window state. + #[cfg(feature = "wayland")] + WindowState(iced::window::Id, WindowState), + /// Capabilities the window manager supports + #[cfg(feature = "wayland")] + WmCapabilities(iced::window::Id, WindowManagerCapabilities), + #[cfg(feature = "xdg-portal")] + DesktopSettings(crate::theme::portal::Desktop), +} diff --git a/src/app/command.rs b/src/app/command.rs deleted file mode 100644 index b39118f5..00000000 --- a/src/app/command.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: MPL-2.0 - -use iced::window; - -/// Asynchronous actions for COSMIC applications. -use super::Message; - -/// Commands for COSMIC applications. -pub type Task = iced::Task>; - -/// Creates a task which yields a [`crate::app::Message`]. -pub fn message(message: Message) -> Task { - crate::task::message(message) -} - -/// Convenience methods for building message-based commands. -pub mod message { - /// Creates a task which yields an application message. - pub fn app(message: M) -> crate::app::Task { - super::message(super::Message::App(message)) - } - - /// Creates a task which yields a cosmic message. - pub fn cosmic(message: crate::app::cosmic::Message) -> crate::app::Task { - super::message(super::Message::Cosmic(message)) - } -} - -impl crate::app::Core { - pub fn drag(&self, id: Option) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::drag(id).map(Message::Cosmic) - } - - pub fn maximize( - &self, - id: Option, - maximized: bool, - ) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::maximize(id, maximized).map(Message::Cosmic) - } - - pub fn minimize(&self, id: Option) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::minimize(id).map(Message::Cosmic) - } - - pub fn set_scaling_factor(&self, factor: f32) -> iced::Task> { - message::cosmic(super::cosmic::Message::ScaleFactor(factor)) - } - - pub fn set_title( - &self, - id: Option, - title: String, - ) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::set_title(id, title).map(Message::Cosmic) - } - - pub fn set_windowed( - &self, - id: Option, - ) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::set_windowed(id).map(Message::Cosmic) - } - - pub fn toggle_maximize( - &self, - id: Option, - ) -> iced::Task> { - let Some(id) = id.or(self.main_window) else { - return iced::Task::none(); - }; - crate::task::toggle_maximize(id).map(Message::Cosmic) - } -} - -pub fn set_theme(theme: crate::Theme) -> iced::Task> { - message::cosmic(super::cosmic::Message::AppThemeChange(theme)) -} diff --git a/src/app/context_drawer.rs b/src/app/context_drawer.rs index 34aa35cb..bb681242 100644 --- a/src/app/context_drawer.rs +++ b/src/app/context_drawer.rs @@ -1,3 +1,6 @@ +// Copyright 2024 System76 +// SPDX-License-Identifier: MPL-2.0 +// use std::borrow::Cow; use crate::Element; diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 10ab6fc1..5e3e3fb5 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -5,12 +5,9 @@ use std::borrow::Borrow; use std::collections::HashMap; use std::sync::Arc; -use super::{Application, ApplicationExt, Core, Subscription}; -use crate::config::CosmicTk; -use crate::surface_message::{MessageWrapper, SurfaceMessage}; +use super::{Action, Application, ApplicationExt, Subscription}; use crate::theme::{Theme, ThemeType, THEME}; -use crate::widget::nav_bar; -use crate::{keyboard_nav, Element}; +use crate::{keyboard_nav, Core, Element}; #[cfg(feature = "wayland")] use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cosmic_theme::ThemeMode; @@ -22,83 +19,23 @@ use iced::{window, Task}; use iced_futures::event::listen_with; use palette::color_difference::EuclideanDistance; -/// A message managed internally by COSMIC. -#[derive(Clone, Debug)] -pub enum Message { - /// Application requests theme change. - AppThemeChange(Theme), - /// Requests to close the window. - Close, - /// Closes or shows the context drawer. - ContextDrawer(bool), - /// Requests to drag the window. - Drag, - /// Keyboard shortcuts managed by libcosmic. - KeyboardNav(keyboard_nav::Message), - /// Requests to maximize the window. - Maximize, - /// Requests to minimize the window. - Minimize, - /// Activates a navigation element from the nav bar. - NavBar(nav_bar::Id), - /// Activates a context menu for an item from the nav bar. - NavBarContext(nav_bar::Id), - /// Set scaling factor - ScaleFactor(f32), - /// Notification of system theme changes. - SystemThemeChange(Vec<&'static str>, Theme), - /// Notification of system theme mode changes. - SystemThemeModeChange(Vec<&'static str>, ThemeMode), - /// Toggles visibility of the nav bar. - ToggleNavBar, - /// Toggles the condensed status of the nav bar. - ToggleNavBarCondensed, - /// Toolkit configuration update - ToolkitConfig(CosmicTk), - /// Updates the window maximized state - WindowMaximized(window::Id, bool), - /// Updates the tracked window geometry. - WindowResize(window::Id, f32, f32), - /// Tracks updates to window state. - #[cfg(feature = "wayland")] - WindowState(window::Id, WindowState), - /// Capabilities the window manager supports - #[cfg(feature = "wayland")] - WmCapabilities(window::Id, WindowManagerCapabilities), - /// Notifies that a surface was closed. - /// Any data relating to the surface should be cleaned up. - SurfaceClosed(window::Id), - /// Activate the application - Activate(String), - ShowWindowMenu, - #[cfg(feature = "xdg-portal")] - DesktopSettings(crate::theme::portal::Desktop), - /// Window focus changed - Focus(window::Id), - /// Window focus lost - Unfocus(window::Id), - /// Tracks updates to window suggested size. - #[cfg(feature = "applet")] - SuggestedBounds(Option), -} - #[derive(Default)] pub struct Cosmic { pub app: App, #[cfg(feature = "wayland")] pub surface_views: HashMap< window::Id, - Box Fn(&'a App) -> Element<'a, super::Message>>, + Box Fn(&'a App) -> Element<'a, crate::Action>>, >, } impl Cosmic where - T::Message: Send + 'static + Into>, + T::Message: Send + 'static, { pub fn init( (mut core, flags): (Core, T::Flags), - ) -> (Self, iced::Task>) { + ) -> (Self, iced::Task>) { #[cfg(feature = "dbus-config")] { use iced_futures::futures::executor::block_on; @@ -122,11 +59,11 @@ where pub fn surface_update( &mut self, - _surface_message: SurfaceMessage, - ) -> iced::Task> { + _surface_message: crate::surface::Action, + ) -> iced::Task> { #[cfg(feature = "wayland")] match _surface_message { - SurfaceMessage::AppSubsurface(settings, view) => { + crate::surface::Action::AppSubsurface(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 { @@ -136,7 +73,7 @@ where if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, super::Message> + dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action> + Send + Sync, >>() { @@ -154,7 +91,7 @@ where iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app)) } } - SurfaceMessage::Subsurface(settings, view) => { + crate::surface::Action::Subsurface(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 { @@ -164,7 +101,7 @@ where if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, super::Message> + Send + Sync, + dyn Fn() -> Element<'static, crate::Action> + Send + Sync, >>() { Ok(v) => Some(v), Err(err) => { @@ -180,7 +117,7 @@ where iced_winit::commands::subsurface::get_subsurface(settings()) } } - SurfaceMessage::AppPopup(settings, view) => { + crate::surface::Action::AppPopup(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 { @@ -190,7 +127,7 @@ where if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Fn(&'a T) -> Element<'a, super::Message> + dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action> + Send + Sync, >>() { @@ -209,12 +146,14 @@ where } } #[cfg(feature = "wayland")] - SurfaceMessage::DestroyPopup(id) => iced_winit::commands::popup::destroy_popup(id), + crate::surface::Action::DestroyPopup(id) => { + iced_winit::commands::popup::destroy_popup(id) + } #[cfg(feature = "wayland")] - SurfaceMessage::DestroySubsurface(id) => { + crate::surface::Action::DestroySubsurface(id) => { iced_winit::commands::subsurface::destroy_subsurface(id) } - SurfaceMessage::ResponsiveMenuBar { + crate::surface::Action::ResponsiveMenuBar { menu_bar, limits, size, @@ -223,7 +162,7 @@ where core.menu_bars.insert(menu_bar, (limits, size)); iced::Task::none() } - SurfaceMessage::Popup(settings, view) => { + crate::surface::Action::Popup(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 { @@ -233,7 +172,7 @@ where if let Some(view) = view.and_then(|view| { match std::sync::Arc::try_unwrap(view).ok()?.downcast:: Element<'static, super::Message> + Send + Sync, + dyn Fn() -> Element<'static, crate::Action> + Send + Sync, >>() { Ok(v) => Some(v), Err(err) => { @@ -249,8 +188,10 @@ where iced_winit::commands::popup::get_popup(settings()) } } - SurfaceMessage::Ignore => iced::Task::none(), - SurfaceMessage::Task(f) => f().map(|sm| super::Message::Surface(sm)), + crate::surface::Action::Ignore => iced::Task::none(), + crate::surface::Action::Task(f) => { + f().map(|sm| crate::Action::Cosmic(Action::Surface(sm))) + } } #[cfg(not(feature = "wayland"))] @@ -259,19 +200,14 @@ where pub fn update( &mut self, - message: super::Message, - ) -> iced::Task> { + message: crate::Action, + ) -> iced::Task> { let message = match message { - super::Message::Surface(surface_message) => self.surface_update(surface_message), - super::Message::App(message) => match Into::>::into(message) - { - MessageWrapper::Surface(surface_message) => self.surface_update(surface_message), - MessageWrapper::Message(message) => self.app.update(message), - }, - super::Message::Cosmic(message) => self.cosmic_update(message), - super::Message::None => iced::Task::none(), + 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")] - super::Message::DbusActivation(message) => self.app.dbus_activation(message), + crate::Action::DbusActivation(message) => self.app.dbus_activation(message), }; #[cfg(target_env = "gnu")] @@ -307,29 +243,29 @@ where } #[allow(clippy::too_many_lines)] - pub fn subscription(&self) -> Subscription> { + pub fn subscription(&self) -> Subscription> { let window_events = listen_with(|event, _, id| { match event { iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => { - return Some(Message::WindowResize(id, width, height)); + return Some(Action::WindowResize(id, width, height)); } iced::Event::Window(window::Event::Closed) => { - return Some(Message::SurfaceClosed(id)); + return Some(Action::SurfaceClosed(id)); } - iced::Event::Window(window::Event::Focused) => return Some(Message::Focus(id)), - iced::Event::Window(window::Event::Unfocused) => return Some(Message::Unfocus(id)), + iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)), + iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)), #[cfg(feature = "wayland")] 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(Message::SurfaceClosed(id)); + return Some(Action::SurfaceClosed(id)); } #[cfg(feature = "applet")] wayland::Event::Window( iced::event::wayland::WindowEvent::SuggestedBounds(b), ) => { - return Some(Message::SuggestedBounds(b)); + return Some(Action::SuggestedBounds(b)); } _ => (), } @@ -341,7 +277,7 @@ where }); let mut subscriptions = vec![ - self.app.subscription().map(super::Message::App), + self.app.subscription().map(crate::Action::App), self.app .core() .watch_config::(crate::config::ID) @@ -354,7 +290,7 @@ where tracing::error!(?why, "cosmic toolkit config update error"); } - super::Message::Cosmic(Message::ToolkitConfig(update.config)) + crate::Action::Cosmic(Action::ToolkitConfig(update.config)) }), self.app .core() @@ -381,12 +317,12 @@ where { tracing::error!(?why, "cosmic theme config update error"); } - Message::SystemThemeChange( + Action::SystemThemeChange( update.keys, crate::theme::Theme::system(Arc::new(update.config)), ) }) - .map(super::Message::Cosmic), + .map(crate::Action::Cosmic), self.app .core() .watch_config::(cosmic_theme::THEME_MODE_ID) @@ -398,21 +334,21 @@ where { tracing::error!(?error, "error reading system theme mode update"); } - Message::SystemThemeModeChange(update.keys, update.config) + Action::SystemThemeModeChange(update.keys, update.config) }) - .map(super::Message::Cosmic), - window_events.map(super::Message::Cosmic), + .map(crate::Action::Cosmic), + window_events.map(crate::Action::Cosmic), #[cfg(feature = "xdg-portal")] crate::theme::portal::desktop_settings() - .map(Message::DesktopSettings) - .map(super::Message::Cosmic), + .map(Action::DesktopSettings) + .map(crate::Action::Cosmic), ]; if self.app.core().keyboard_nav { subscriptions.push( keyboard_nav::subscription() - .map(Message::KeyboardNav) - .map(super::Message::Cosmic), + .map(Action::KeyboardNav) + .map(crate::Action::Cosmic), ); } @@ -435,7 +371,7 @@ where } #[cfg(feature = "multi-window")] - pub fn view(&self, id: window::Id) -> Element> { + pub fn view(&self, id: window::Id) -> Element> { #[cfg(feature = "wayland")] if let Some(v) = self.surface_views.get(&id) { return v(&self.app); @@ -446,13 +382,13 @@ where .main_window_id() .is_some_and(|main_id| main_id == id) { - return self.app.view_window(id).map(super::Message::App); + 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(super::Message::App) + self.app.view().map(crate::Action::App) }; #[cfg(target_env = "gnu")] @@ -462,7 +398,7 @@ where } #[cfg(not(feature = "multi-window"))] - pub fn view(&self) -> Element> { + pub fn view(&self) -> Element> { let view = self.app.view_main(); #[cfg(target_env = "gnu")] @@ -474,7 +410,7 @@ where impl Cosmic { #[allow(clippy::unused_self)] - pub fn close(&mut self) -> iced::Task> { + pub fn close(&mut self) -> iced::Task> { if let Some(id) = self.app.core().main_window_id() { iced::window::close(id) } else { @@ -483,9 +419,9 @@ impl Cosmic { } #[allow(clippy::too_many_lines)] - fn cosmic_update(&mut self, message: Message) -> iced::Task> { + fn cosmic_update(&mut self, message: Action) -> iced::Task> { match message { - Message::WindowMaximized(id, maximized) => { + Action::WindowMaximized(id, maximized) => { if self .app .core() @@ -496,7 +432,7 @@ impl Cosmic { } } - Message::WindowResize(id, width, height) => { + Action::WindowResize(id, width, height) => { if self .app .core() @@ -511,12 +447,12 @@ impl Cosmic { //TODO: more efficient test of maximized (winit has no event for maximize if set by the OS) return iced::window::get_maximized(id).map(move |maximized| { - super::Message::Cosmic(Message::WindowMaximized(id, maximized)) + crate::Action::Cosmic(Action::WindowMaximized(id, maximized)) }); } #[cfg(feature = "wayland")] - Message::WindowState(id, state) => { + Action::WindowState(id, state) => { if self .app .core() @@ -536,7 +472,7 @@ impl Cosmic { } #[cfg(feature = "wayland")] - Message::WmCapabilities(id, capabilities) => { + Action::WmCapabilities(id, capabilities) => { if self .app .core() @@ -552,49 +488,49 @@ impl Cosmic { } } - Message::KeyboardNav(message) => match message { - keyboard_nav::Message::FocusNext => { - return iced::widget::focus_next().map(super::Message::Cosmic) + Action::KeyboardNav(message) => match message { + keyboard_nav::Action::FocusNext => { + return iced::widget::focus_next().map(crate::Action::Cosmic) } - keyboard_nav::Message::FocusPrevious => { - return iced::widget::focus_previous().map(super::Message::Cosmic) + keyboard_nav::Action::FocusPrevious => { + return iced::widget::focus_previous().map(crate::Action::Cosmic) } - keyboard_nav::Message::Escape => return self.app.on_escape(), - keyboard_nav::Message::Search => return self.app.on_search(), + keyboard_nav::Action::Escape => return self.app.on_escape(), + keyboard_nav::Action::Search => return self.app.on_search(), - keyboard_nav::Message::Fullscreen => return self.app.core().toggle_maximize(None), + keyboard_nav::Action::Fullscreen => return self.app.core().toggle_maximize(None), }, - Message::ContextDrawer(show) => { + Action::ContextDrawer(show) => { self.app.core_mut().set_show_context(show); return self.app.on_context_drawer(); } - Message::Drag => return self.app.core().drag(None), + Action::Drag => return self.app.core().drag(None), - Message::Minimize => return self.app.core().minimize(None), + Action::Minimize => return self.app.core().minimize(None), - Message::Maximize => return self.app.core().toggle_maximize(None), + Action::Maximize => return self.app.core().toggle_maximize(None), - Message::NavBar(key) => { + Action::NavBar(key) => { self.app.core_mut().nav_bar_set_toggled_condensed(false); return self.app.on_nav_select(key); } - Message::NavBarContext(key) => { + Action::NavBarContext(key) => { self.app.core_mut().nav_bar_set_context(key); return self.app.on_nav_context(key); } - Message::ToggleNavBar => { + Action::ToggleNavBar => { self.app.core_mut().nav_bar_toggle(); } - Message::ToggleNavBarCondensed => { + Action::ToggleNavBarCondensed => { self.app.core_mut().nav_bar_toggle_condensed(); } - Message::AppThemeChange(mut theme) => { + Action::AppThemeChange(mut theme) => { if let ThemeType::System { theme: _, .. } = theme.theme_type { self.app.core_mut().theme_sub_counter += 1; @@ -610,7 +546,7 @@ impl Cosmic { THEME.lock().unwrap().set_theme(theme.theme_type); } - Message::SystemThemeChange(keys, theme) => { + Action::SystemThemeChange(keys, theme) => { let cur_is_dark = THEME.lock().unwrap().theme_type.is_dark(); // Ignore updates if the current theme mode does not match. if cur_is_dark != theme.cosmic().is_dark { @@ -648,17 +584,17 @@ impl Cosmic { return cmd; } - Message::ScaleFactor(factor) => { + Action::ScaleFactor(factor) => { self.app.core_mut().set_scale_factor(factor); } - Message::Close => { + Action::Close => { return match self.app.on_app_exit() { Some(message) => self.app.update(message), None => self.close(), }; } - Message::SystemThemeModeChange(keys, mode) => { + Action::SystemThemeModeChange(keys, mode) => { if !keys.contains(&"is_dark") { return iced::Task::none(); } @@ -710,7 +646,7 @@ impl Cosmic { } return Task::batch(cmds); } - Message::Activate(_token) => + Action::Activate(_token) => { #[cfg(feature = "wayland")] if let Some(id) = self.app.core().main_window_id() { @@ -721,7 +657,10 @@ impl Cosmic { ); } } - Message::SurfaceClosed(id) => { + + Action::Surface(action) => return self.surface_update(action), + + Action::SurfaceClosed(id) => { let mut ret = if let Some(msg) = self.app.on_close_requested(id) { self.app.update(msg) } else { @@ -731,17 +670,19 @@ impl Cosmic { if core.exit_on_main_window_closed && core.main_window_id().is_some_and(|m_id| id == m_id) { - ret = Task::batch(vec![iced::exit::>()]); + ret = Task::batch(vec![iced::exit::>()]); } return ret; } - Message::ShowWindowMenu => { + + Action::ShowWindowMenu => { if let Some(id) = self.app.core().main_window_id() { return iced::window::show_system_menu(id); } } + #[cfg(feature = "xdg-portal")] - Message::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => { + Action::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => { use ashpd::desktop::settings::ColorScheme; if match THEME.lock().unwrap().theme_type { ThemeType::System { @@ -781,7 +722,7 @@ impl Cosmic { } } #[cfg(feature = "xdg-portal")] - Message::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => { + 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(); @@ -810,11 +751,11 @@ impl Cosmic { } } #[cfg(feature = "xdg-portal")] - Message::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => { + Action::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => { // TODO when high contrast is integrated in settings and all custom themes } - Message::ToolkitConfig(config) => { + 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 @@ -825,18 +766,18 @@ impl Cosmic { *crate::config::COSMIC_TK.write().unwrap() = config; } - Message::Focus(f) => { + Action::Focus(f) => { self.app.core_mut().focused_window = Some(f); } - Message::Unfocus(id) => { + Action::Unfocus(id) => { let core = self.app.core_mut(); if core.focused_window.as_ref().is_some_and(|cur| *cur == id) { core.focused_window = None; } } #[cfg(feature = "applet")] - Message::SuggestedBounds(b) => { + Action::SuggestedBounds(b) => { tracing::info!("Suggested bounds: {b:?}"); let core = self.app.core_mut(); core.applet.suggested_bounds = b; @@ -863,9 +804,9 @@ impl Cosmic { &mut self, settings: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings, view: Box< - dyn for<'a> Fn(&'a App) -> Element<'a, super::Message> + Send + Sync, + dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, >, - ) -> Task> { + ) -> Task> { use iced_winit::commands::subsurface::get_subsurface; self.surface_views.insert(settings.id, view); @@ -878,9 +819,9 @@ impl Cosmic { &mut self, settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings, view: Box< - dyn for<'a> Fn(&'a App) -> Element<'a, super::Message> + Send + Sync, + dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action> + Send + Sync, >, - ) -> Task> { + ) -> Task> { use iced_winit::commands::popup::get_popup; self.surface_views.insert(settings.id, view); diff --git a/src/app/mod.rs b/src/app/mod.rs index 126a21ad..5541bdca 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,243 +6,27 @@ //! Check out our [application](https://github.com/pop-os/libcosmic/tree/master/examples/application) //! example in our repository. -pub mod command; +mod action; +pub use action::Action; +use cosmic_config::CosmicConfigEntry; pub mod context_drawer; -mod core; pub mod cosmic; #[cfg(all(feature = "winit", feature = "multi-window"))] pub(crate) mod multi_window; pub mod settings; -pub mod message { - use crate::surface_message::SurfaceMessage; +pub type Task = iced::Task>; - #[derive(Clone, Debug)] - #[must_use] - pub enum Message { - /// Messages from the application, for the application. - App(M), - /// Internal messages to be handled by libcosmic. - Cosmic(super::cosmic::Message), - #[cfg(feature = "single-instance")] - /// Dbus activation messages - DbusActivation(super::DbusActivationMessage), - /// Do nothing - None, - /// Internal surface message - Surface(SurfaceMessage), - } - - pub const fn app(message: M) -> Message { - Message::App(message) - } - - pub const fn cosmic(message: super::cosmic::Message) -> Message { - Message::Cosmic(message) - } - - pub const fn none() -> Message { - Message::None - } - - /// Used to produce a destroy popup message from within a widget. - #[cfg(feature = "wayland")] - #[must_use] - pub fn destroy_popup(id: iced_core::window::Id) -> Message - where - Message: From + 'static, - { - Message::from(SurfaceMessage::DestroyPopup(id)) - } - - #[cfg(feature = "wayland")] - #[must_use] - pub fn destroy_subsurface>(id: iced_core::window::Id) -> M { - let surface_msg = SurfaceMessage::DestroySubsurface(id); - M::from(surface_msg) - } - - #[cfg(feature = "wayland")] - #[must_use] - pub fn app_popup( - 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, super::Message> - + Send - + Sync - + 'static, - >, - >, - ) -> App::Message - where - App::Message: - Into> + From, - { - use std::{any::Any, sync::Arc}; - - use crate::surface_message::SurfaceMessage; - let boxed: Box< - dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings - + Send - + Sync - + 'static, - > = Box::new(settings); - let boxed: Box = Box::new(boxed); - - App::Message::from(SurfaceMessage::AppPopup( - Arc::new(boxed), - view.map(|view| { - let boxed: Box = Box::new(view); - Arc::new(boxed) - }), - )) - } - - /// Used to create a subsurface message from within a widget. - #[cfg(feature = "wayland")] - #[must_use] - pub fn simple_subsurface( - settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync + 'static, - view: Option< - Box< - dyn Fn() -> crate::Element<'static, super::Message> - + Send - + Sync - + 'static, - >, - >, - ) -> SurfaceMessage - where - Message: From + 'static, - V:, - { - use std::{any::Any, sync::Arc}; - - use crate::surface_message::SurfaceMessage; - let boxed: Box< - dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings - + Send - + Sync - + 'static, - > = Box::new(settings); - let boxed: Box = Box::new(boxed); - - SurfaceMessage::Subsurface( - Arc::new(boxed), - view.map(|view| { - let boxed: Box = Box::new(view); - Arc::new(boxed) - }), - ) - } - - /// Used to create a popup message from within a widget. - #[cfg(feature = "wayland")] - #[must_use] - pub fn simple_popup( - settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings - + Send - + Sync - + 'static, - view: Option< - impl Fn() -> crate::Element<'static, crate::app::Message> + Send + Sync + 'static, - >, - ) -> SurfaceMessage - where - Message: From + 'static, - V:, - { - use std::{any::Any, sync::Arc}; - - use crate::surface_message::SurfaceMessage; - let boxed: Box< - dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings - + Send - + Sync - + 'static, - > = Box::new(settings); - let boxed: Box = Box::new(boxed); - - SurfaceMessage::Popup( - Arc::new(boxed), - view.map(|view| { - let boxed: Box< - dyn Fn() -> crate::Element<'static, super::Message> - + Send - + Sync - + 'static, - > = Box::new(view); - let boxed: Box = Box::new(boxed); - Arc::new(boxed) - }), - ) - } - - #[cfg(feature = "wayland")] - #[must_use] - pub fn subsurface( - settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + 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, super::Message> - + Send - + Sync - + 'static, - >, - >, - ) -> App::Message - where - App::Message: - Into> + From, - { - use crate::surface_message::SurfaceMessage; - use std::{any::Any, sync::Arc}; - let boxed: Box< - dyn Fn( - &mut App, - ) - -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings - + Send - + Sync - + 'static, - > = Box::new(settings); - let boxed: Box = Box::new(boxed); - - App::Message::from(SurfaceMessage::AppSubsurface( - Arc::new(boxed), - view.map(|view| { - let boxed: Box = Box::new(view); - Arc::new(boxed) - }), - )) - } - - impl From for Message { - fn from(value: M) -> Self { - Self::App(value) - } - } -} - -use std::borrow::Cow; - -pub use self::command::Task; -pub use self::core::Core; -pub use self::settings::Settings; use crate::prelude::*; -use crate::surface_message::SurfaceMessage; use crate::theme::THEME; use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover}; +pub use crate::Core; use apply::Apply; use context_drawer::ContextDrawer; use iced::window; use iced::{Length, Subscription}; -pub use message::Message; +pub use settings::Settings; +use std::borrow::Cow; use url::Url; #[cfg(feature = "single-instance")] use { @@ -319,10 +103,7 @@ pub(crate) fn iced_settings( /// # Errors /// /// Returns error on application failure. -pub fn run(settings: Settings, flags: App::Flags) -> iced::Result -where - App::Message: Into>, -{ +pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { #[cfg(target_env = "gnu")] if let Some(threshold) = settings.default_mmap_threshold { crate::malloc::limit_mmap_threshold(threshold); @@ -510,11 +291,7 @@ impl DbusActivation { pub fn run_single_instance(settings: Settings, flags: App::Flags) -> iced::Result where App::Flags: CosmicFlags, - App::Message: Clone - + std::fmt::Debug - + Send - + Into> - + 'static, + App::Message: Clone + std::fmt::Debug + Send + 'static, { let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok(); @@ -682,7 +459,7 @@ where } /// Allows overriding the default nav bar widget. - fn nav_bar(&self) -> Option>> { + fn nav_bar(&self) -> Option>> { if !self.core().nav_bar_active() { return None; } @@ -690,8 +467,8 @@ where let nav_model = self.nav_model()?; let mut nav = - crate::widget::nav_bar(nav_model, |id| Message::Cosmic(cosmic::Message::NavBar(id))) - .on_context(|id| Message::Cosmic(cosmic::Message::NavBarContext(id))) + crate::widget::nav_bar(nav_model, |id| crate::Action::Cosmic(Action::NavBar(id))) + .on_context(|id| crate::Action::Cosmic(Action::NavBarContext(id))) .context_menu(self.nav_context_menu(self.core().nav_bar_context())) .into_container() // XXX both must be shrink to avoid flex layout from ignoring it @@ -706,7 +483,10 @@ where } /// Shows a context menu for the active nav bar item. - fn nav_context_menu(&self, id: nav_bar::Id) -> Option>>> { + fn nav_context_menu( + &self, + id: nav_bar::Id, + ) -> Option>>> { None } @@ -839,7 +619,21 @@ pub trait ApplicationExt: Application { fn set_window_title(&mut self, title: String, id: window::Id) -> Task; /// View template for the main window. - fn view_main(&self) -> Element>; + fn view_main(&self) -> Element>; + + fn watch_config( + &self, + id: &'static str, + ) -> iced::Subscription> { + self.core().watch_config(id) + } + + fn watch_state( + &self, + id: &'static str, + ) -> iced::Subscription> { + self.core().watch_state(id) + } } impl ApplicationExt for App { @@ -886,7 +680,7 @@ impl ApplicationExt for App { #[allow(clippy::too_many_lines)] /// Creates the view for the main window. - fn view_main(&self) -> Element> { + fn view_main(&self) -> Element> { let core = self.core(); let is_condensed = core.is_condensed(); // TODO: More granularity might be needed for different resize border @@ -953,13 +747,13 @@ impl ApplicationExt for App { [0, 0, 0, 0] }) .apply(Element::from) - .map(Message::App), + .map(crate::Action::App), ); } else { //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content widgets.push( - container(main_content.map(Message::App)) + container(main_content.map(crate::Action::App)) .padding(main_content_padding) .into(), ); @@ -969,7 +763,7 @@ impl ApplicationExt for App { //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content widgets.push( - container(main_content.map(Message::App)) + container(main_content.map(crate::Action::App)) .padding(main_content_padding) .into(), ); @@ -985,7 +779,7 @@ impl ApplicationExt for App { context_width, ) .apply(Element::from) - .map(Message::App) + .map(crate::Action::App) .apply(container) .width(context_width) .apply(|drawer| { @@ -1015,7 +809,7 @@ impl ApplicationExt for App { .push(content_row) .push_maybe( self.footer() - .map(|footer| container(footer.map(Message::App)).padding([0, 8, 8, 8])), + .map(|footer| container(footer.map(crate::Action::App)).padding([0, 8, 8, 8])), ); let content: Element<_> = if core.window.content_container { content_col @@ -1035,45 +829,45 @@ impl ApplicationExt for App { let mut header = crate::widget::header_bar() .focused(focused) .title(&core.window.header_title) - .on_drag(Message::Cosmic(cosmic::Message::Drag)) - .on_right_click(Message::Cosmic(cosmic::Message::ShowWindowMenu)) - .on_double_click(Message::Cosmic(cosmic::Message::Maximize)); + .on_drag(crate::Action::Cosmic(Action::Drag)) + .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) + .on_double_click(crate::Action::Cosmic(Action::Maximize)); if self.nav_model().is_some() { let toggle = crate::widget::nav_bar_toggle() .active(core.nav_bar_active()) .selected(focused) .on_toggle(if is_condensed { - Message::Cosmic(cosmic::Message::ToggleNavBarCondensed) + crate::Action::Cosmic(Action::ToggleNavBarCondensed) } else { - Message::Cosmic(cosmic::Message::ToggleNavBar) + crate::Action::Cosmic(Action::ToggleNavBar) }); header = header.start(toggle); } if core.window.show_close { - header = header.on_close(Message::Cosmic(cosmic::Message::Close)); + header = header.on_close(crate::Action::Cosmic(Action::Close)); } if core.window.show_maximize && crate::config::show_maximize() { - header = header.on_maximize(Message::Cosmic(cosmic::Message::Maximize)); + header = header.on_maximize(crate::Action::Cosmic(Action::Maximize)); } if core.window.show_minimize && crate::config::show_minimize() { - header = header.on_minimize(Message::Cosmic(cosmic::Message::Minimize)); + header = header.on_minimize(crate::Action::Cosmic(Action::Minimize)); } for element in self.header_start() { - header = header.start(element.map(Message::App)); + header = header.start(element.map(crate::Action::App)); } for element in self.header_center() { - header = header.center(element.map(Message::App)); + header = header.center(element.map(crate::Action::App)); } for element in self.header_end() { - header = header.end(element.map(Message::App)); + header = header.end(element.map(crate::Action::App)); } if content_container { @@ -1133,7 +927,7 @@ impl ApplicationExt for App { .dialog() .map(|w| Element::from(id_container(w, iced_core::id::Id::new("COSMIC_dialog")))) { - popover = popover.popup(dialog.map(Message::App)); + popover = popover.popup(dialog.map(crate::Action::App)); } let view_element: Element<_> = popover.into(); @@ -1142,7 +936,8 @@ impl ApplicationExt for App { } #[cfg(feature = "single-instance")] -fn single_instance_subscription() -> Subscription> { +fn single_instance_subscription() -> Subscription> +{ use iced_futures::futures::StreamExt; iced_futures::Subscription::run_with_id( TypeId::of::(), @@ -1187,13 +982,13 @@ fn single_instance_subscription() -> Subscription +// SPDX-License-Identifier: MPL-2.0 + //! Create and run daemons that run in the background. //! Copied from iced 0.13, but adds optional initial window diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 16d47348..727ff244 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -11,7 +11,6 @@ use crate::{ window, Color, Length, Limits, Rectangle, }, iced_widget, - surface_message::SurfaceMessage, theme::{self, system_dark, system_light, Button, THEME}, widget::{ self, @@ -220,21 +219,19 @@ impl Context { ) } - pub fn applet_tooltip< - 'a, - Message: 'static + From + Into>, - >( + pub fn applet_tooltip<'a, Message: 'static>( &self, content: impl Into>, tooltip: impl Into>, has_popup: bool, - ) -> crate::widget::wayland::tooltip::widget::Tooltip<'a, Message, Message> { + on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static, + ) -> crate::widget::wayland::tooltip::widget::Tooltip<'a, Message> { let window_id = *TOOLTIP_WINDOW_ID; let subsurface_id = TOOLTIP_ID.clone(); let anchor = self.anchor; let tooltip = tooltip.into(); - crate::widget::wayland::tooltip::widget::Tooltip::<'a, Message, Message>::new( + crate::widget::wayland::tooltip::widget::Tooltip::<'a, Message>::new( content, (!has_popup).then_some(move |bounds: Rectangle| { let window_id = window_id; @@ -273,14 +270,15 @@ impl Context { } }), move || { - Element::<'static, crate::app::Message>::from(autosize::autosize( + Element::from(autosize::autosize( layer_container(crate::widget::text(tooltip.clone())) .layer(crate::cosmic_theme::Layer::Background) .padding(4.), subsurface_id.clone(), )) }, - crate::app::message::destroy_popup::(window_id), + on_surface_action(crate::surface::action::destroy_popup(window_id)), + on_surface_action, ) .delay(Duration::from_millis(100)) } @@ -443,10 +441,7 @@ impl Context { /// # Errors /// /// Returns error on application failure. -pub fn run(flags: App::Flags) -> iced::Result -where - App::Message: Into>, -{ +pub fn run(flags: App::Flags) -> iced::Result { let helper = Context::default(); let mut settings = helper.window_settings(); diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 00000000..cb68204a --- /dev/null +++ b/src/command.rs @@ -0,0 +1,43 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use iced::window; + +/// Initiates a window drag. +pub fn drag(id: window::Id) -> iced::Task> { + iced_runtime::window::drag(id) +} + +/// Maximizes the window. +pub fn maximize(id: window::Id, maximized: bool) -> iced::Task> { + iced_runtime::window::maximize(id, maximized) +} + +/// Minimizes the window. +pub fn minimize(id: window::Id) -> iced::Task> { + iced_runtime::window::minimize(id, true) +} + +/// Sets the title of a window. +#[allow(unused_variables, clippy::needless_pass_by_value)] +pub fn set_title(id: window::Id, title: String) -> iced::Task> { + iced::Task::none() +} + +pub fn set_scaling_factor(factor: f32) -> iced::Task> { + iced::Task::done(crate::app::Action::ScaleFactor(factor)).map(crate::Action::Cosmic) +} + +pub fn set_theme(theme: crate::Theme) -> iced::Task> { + iced::Task::done(crate::app::Action::AppThemeChange(theme)).map(crate::Action::Cosmic) +} + +/// Sets the window mode to windowed. +pub fn set_windowed(id: window::Id) -> iced::Task> { + iced_runtime::window::change_mode(id, window::Mode::Windowed) +} + +/// Toggles the windows' maximize state. +pub fn toggle_maximize(id: window::Id) -> iced::Task> { + iced_runtime::window::toggle_maximize(id) +} diff --git a/src/app/core.rs b/src/core.rs similarity index 80% rename from src/app/core.rs rename to src/core.rs index 0a4a97b8..611b9a0e 100644 --- a/src/app/core.rs +++ b/src/core.rs @@ -3,15 +3,11 @@ use std::collections::HashMap; -use crate::widget::{ - button, icon, - menu::{self}, - nav_bar, responsive_container, -}; +use crate::widget::nav_bar; use cosmic_config::CosmicConfigEntry; use cosmic_theme::ThemeMode; use iced::{window, Limits, Size}; -use iced_core::{window::Id, Element}; +use iced_core::window::Id; use palette::Srgba; use slotmap::Key; @@ -250,6 +246,10 @@ impl Core { } } + pub fn main_window_is(&self, id: iced::window::Id) -> bool { + self.main_window_id().is_some_and(|main_id| main_id == id) + } + /// Whether the nav panel is visible or not #[must_use] pub fn nav_bar_active(&self) -> bool { @@ -382,75 +382,57 @@ impl Core { id } - #[cfg(feature = "surface-message")] - /// # Panics - /// - /// Will panic if the menu bar collapses without tracking the size - pub fn responsive_menu_bar< - 'a, - Message: Clone - + From - + Into> - + 'static, - A: menu::Action, - >( - &self, - key_binds: &HashMap, - id: crate::widget::Id, - trees: Vec<( - std::borrow::Cow<'static, str>, - Vec>>, - )>, - ) -> crate::Element<'a, Message> { - use crate::widget::id_container; + pub fn drag(&self, id: Option) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::drag(id) + } - let menu_bar_size = self.menu_bars.get(&id); - #[allow(clippy::if_not_else)] - if !menu_bar_size.is_some_and(|(limits, size)| { - let max_size = limits.max(); - max_size.width < size.width - }) { - crate::Element::from(responsive_container::responsive_container( - id_container( - menu::bar( - trees - .into_iter() - .map(|mt| { - menu::Tree::<_>::with_children( - menu::root(mt.0), - menu::items(key_binds, mt.1), - ) - }) - .collect(), - ), - crate::widget::Id::new(format!("menu_bar_expanded_{id}")), - ), - id, - )) - } else { - crate::Element::from( - responsive_container::responsive_container( - id_container( - menu::bar(vec![menu::Tree::<_>::with_children( - Element::from( - button::icon(icon::from_name("open-menu-symbolic")) - .padding([4, 12]) - .class(crate::theme::Button::MenuRoot), - ), - menu::items( - key_binds, - trees - .into_iter() - .map(|mt| menu::Item::Folder(mt.0, mt.1)) - .collect(), - ), - )]), - crate::widget::Id::new(format!("menu_bar_collapsed_{id}")), - ), - id, - ) - .size(menu_bar_size.unwrap().1), - ) - } + pub fn maximize( + &self, + id: Option, + maximized: bool, + ) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::maximize(id, maximized) + } + + pub fn minimize(&self, id: Option) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::minimize(id) + } + + pub fn set_title( + &self, + id: Option, + title: String, + ) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::set_title(id, title) + } + + pub fn set_windowed(&self, id: Option) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + crate::command::set_windowed(id) + } + + pub fn toggle_maximize( + &self, + id: Option, + ) -> crate::app::Task { + let Some(id) = id.or(self.main_window) else { + return iced::Task::none(); + }; + + crate::command::toggle_maximize(id) } } diff --git a/src/keyboard_nav.rs b/src/keyboard_nav.rs index a45e667a..66a6198c 100644 --- a/src/keyboard_nav.rs +++ b/src/keyboard_nav.rs @@ -8,7 +8,7 @@ use iced_core::keyboard::key::Named; use iced_futures::event::listen_raw; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Message { +pub enum Action { Escape, FocusNext, FocusPrevious, @@ -16,7 +16,7 @@ pub enum Message { Search, } -pub fn subscription() -> Subscription { +pub fn subscription() -> Subscription { listen_raw(|event, status, _| { if event::Status::Ignored != status { return None; @@ -30,18 +30,18 @@ pub fn subscription() -> Subscription { }) => match key { Named::Tab => { return Some(if modifiers.shift() { - Message::FocusPrevious + Action::FocusPrevious } else { - Message::FocusNext + Action::FocusNext }); } Named::Escape => { - return Some(Message::Escape); + return Some(Action::Escape); } Named::F11 => { - return Some(Message::Fullscreen); + return Some(Action::Fullscreen); } _ => (), @@ -51,7 +51,7 @@ pub fn subscription() -> Subscription { modifiers, .. }) if c == "f" && modifiers.control() => { - return Some(Message::Search); + return Some(Action::Search); } _ => (), diff --git a/src/lib.rs b/src/lib.rs index 441f0894..397357d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,19 +9,31 @@ pub mod prelude { pub use crate::ext::*; #[cfg(feature = "winit")] pub use crate::ApplicationExt; - pub use crate::{Also, Apply, Element, Renderer, Theme}; + pub use crate::{Also, Apply, Element, Renderer, Task, Theme}; } pub use apply::{Also, Apply}; +/// Actions are managed internally by the cosmic runtime. +pub mod action; +pub use action::Action; + #[cfg(feature = "winit")] pub mod app; #[cfg(feature = "winit")] +#[doc(inline)] pub use app::{Application, ApplicationExt}; #[cfg(feature = "applet")] pub mod applet; +pub mod command; + +/// State which is managed by the cosmic runtime. +pub mod core; +#[doc(inline)] +pub use core::Core; + pub use iced::Task; pub mod task; @@ -85,7 +97,7 @@ pub mod process; #[cfg(feature = "wayland")] pub use cctk; -pub mod surface_message; +pub mod surface; pub mod theme; diff --git a/src/malloc.rs b/src/malloc.rs index e980ea6f..0d271447 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -1,3 +1,6 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: MPL-2.0 + use std::os::raw::c_int; const M_MMAP_THRESHOLD: c_int = -3; diff --git a/src/process.rs b/src/process.rs index 074bfab8..06aeae16 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,3 +1,6 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + #[cfg(all(feature = "smol", not(feature = "tokio")))] use smol::io::AsyncReadExt; use std::io; diff --git a/src/surface/action.rs b/src/surface/action.rs new file mode 100644 index 00000000..2d21ffef --- /dev/null +++ b/src/surface/action.rs @@ -0,0 +1,150 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: MPL-2.0 + +use super::Action; +use crate::Application; +use std::{any::Any, sync::Arc}; + +/// Used to produce a destroy popup message from within a widget. +#[cfg(feature = "wayland")] +#[must_use] +pub fn destroy_popup(id: iced_core::window::Id) -> Action { + Action::DestroyPopup(id) +} + +#[cfg(feature = "wayland")] +#[must_use] +pub fn destroy_subsurface(id: iced_core::window::Id) -> Action { + Action::DestroySubsurface(id) +} + +#[cfg(feature = "wayland")] +#[must_use] +pub fn app_popup( + 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, + >, + >, +) -> Action { + let boxed: Box< + dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + Action::AppPopup( + Arc::new(boxed), + view.map(|view| { + let boxed: Box = Box::new(view); + Arc::new(boxed) + }), + ) +} + +/// Used to create a subsurface message from within a widget. +#[cfg(feature = "wayland")] +#[must_use] +pub fn simple_subsurface( + settings: impl Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + + Send + + Sync + + 'static, + view: Option< + Box crate::Element<'static, crate::Action> + Send + Sync + 'static>, + >, +) -> Action { + let boxed: Box< + dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + Action::Subsurface( + Arc::new(boxed), + view.map(|view| { + let boxed: Box = Box::new(view); + Arc::new(boxed) + }), + ) +} + +/// Used to create a popup message from within a widget. +#[cfg(feature = "wayland")] +#[must_use] +pub fn simple_popup( + settings: impl Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + view: Option< + impl Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + >, +) -> Action { + let boxed: Box< + dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + Action::Popup( + Arc::new(boxed), + view.map(|view| { + let boxed: Box< + dyn Fn() -> crate::Element<'static, crate::Action> + Send + Sync + 'static, + > = Box::new(view); + let boxed: Box = Box::new(boxed); + Arc::new(boxed) + }), + ) +} + +#[cfg(feature = "wayland")] +#[must_use] +pub fn subsurface( + settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + + Send + + 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, + >, + >, +) -> Action { + let boxed: Box< + dyn Fn( + &mut App, + ) + -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + Action::AppSubsurface( + Arc::new(boxed), + view.map(|view| { + let boxed: Box = Box::new(view); + Arc::new(boxed) + }), + ) +} diff --git a/src/surface_message.rs b/src/surface/mod.rs similarity index 88% rename from src/surface_message.rs rename to src/surface/mod.rs index 86a27ceb..c08108ee 100644 --- a/src/surface_message.rs +++ b/src/surface/mod.rs @@ -1,26 +1,17 @@ +// Copyright 2025 System76 +// SPDX-License-Identifier: MPL-2.0 + +pub mod action; + +use iced::Limits; +use iced::Size; +use iced::Task; use std::future::Future; use std::sync::Arc; -use iced::Size; - -use iced::Limits; -use iced::Task; - -pub enum MessageWrapper { - Surface(SurfaceMessage), - Message(M), -} - -#[cfg(not(feature = "surface-message"))] -impl From for MessageWrapper { - fn from(value: M) -> Self { - MessageWrapper::Message(value) - } -} - /// Ignore this message in your application. It will be intercepted. #[derive(Clone)] -pub enum SurfaceMessage { +pub enum Action { /// Create a subsurface with a view function accepting the App as a parameter AppSubsurface( std::sync::Arc>, @@ -55,10 +46,10 @@ pub enum SurfaceMessage { size: Size, }, Ignore, - Task(Arc Task + Send + Sync>), + Task(Arc Task + Send + Sync>), } -impl std::fmt::Debug for SurfaceMessage { +impl std::fmt::Debug for Action { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::AppSubsurface(arg0, arg1) => f diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 00000000..a730b37e --- /dev/null +++ b/src/task.rs @@ -0,0 +1,25 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! Create asynchronous actions to be performed in the background. + +use std::future::Future; + +/// Yields a task which contains a batch of tasks. +pub fn batch, Y: Send + 'static>( + tasks: impl IntoIterator>, +) -> iced::Task { + iced::Task::batch(tasks).map(Into::into) +} + +/// Yields a task which will run the future on the runtime executor. +pub fn future, Y: 'static>( + future: impl Future + Send + 'static, +) -> iced::Task { + iced::Task::future(async move { future.await.into() }) +} + +/// Yields a task which will return a message. +pub fn message, Y: 'static>(message: X) -> iced::Task { + future(async move { message.into() }) +} diff --git a/src/task/mod.rs b/src/task/mod.rs deleted file mode 100644 index 1efc33d6..00000000 --- a/src/task/mod.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: MPL-2.0 - -//! Create asynchronous actions to be performed in the background. - -use iced::window; -use iced::Task; -use iced_core::window::Mode; -use std::future::Future; - -/// Yields a task which contains a batch of tasks. -pub fn batch, Y: Send + 'static>( - tasks: impl IntoIterator>, -) -> Task { - Task::batch(tasks).map(Into::into) -} - -/// Yields a task which will run the future on the runtime executor. -pub fn future, Y: 'static>(future: impl Future + Send + 'static) -> Task { - Task::future(async move { future.await.into() }) -} - -/// Yields a task which will return a message. -pub fn message, Y: 'static>(message: X) -> Task { - future(async move { message.into() }) -} - -/// Initiates a window drag. -pub fn drag(id: window::Id) -> Task { - iced_runtime::window::drag(id) -} - -/// Maximizes the window. -pub fn maximize(id: window::Id, maximized: bool) -> Task { - iced_runtime::window::maximize(id, maximized) -} - -/// Minimizes the window. -pub fn minimize(id: window::Id) -> Task { - iced_runtime::window::minimize(id, true) -} - -/// Sets the title of a window. -#[allow(unused_variables, clippy::needless_pass_by_value)] -pub fn set_title(id: window::Id, title: String) -> Task { - Task::none() -} - -/// Sets the window mode to windowed. -pub fn set_windowed(id: window::Id) -> Task { - iced_runtime::window::change_mode(id, Mode::Windowed) -} - -/// Toggles the windows' maximize state. -pub fn toggle_maximize(id: window::Id) -> Task { - iced_runtime::window::toggle_maximize(id) -} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 3d37498a..52d82832 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -100,6 +100,12 @@ pub mod autosize; #[cfg(feature = "winit")] pub(crate) mod responsive_container; +#[cfg(feature = "surface-message")] +mod responsive_menu_bar; +#[cfg(feature = "surface-message")] +#[doc(inline)] +pub use responsive_menu_bar::responsive_menu_bar; + pub mod button; #[doc(inline)] pub use button::{Button, IconButton, LinkButton, TextButton}; diff --git a/src/widget/responsive_container.rs b/src/widget/responsive_container.rs index ff644852..82b91824 100644 --- a/src/widget/responsive_container.rs +++ b/src/widget/responsive_container.rs @@ -9,18 +9,17 @@ use iced_core::renderer; use iced_core::widget::{tree, Id, Tree}; use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; -use crate::surface_message::SurfaceMessage; - -pub(crate) fn responsive_container<'a, Message: 'static + From, Theme, E>( +pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>( content: E, id: Id, + on_action: impl Fn(crate::surface::Action) -> Message + 'static, ) -> ResponsiveContainer<'a, Message, Theme, crate::Renderer> where E: Into>, Theme: iced_widget::container::Catalog, ::Class<'a>: From>, { - ResponsiveContainer::new(content, id) + ResponsiveContainer::new(content, id, on_action) } /// An element decorating some content. @@ -34,6 +33,7 @@ where content: Element<'a, Message, Theme, Renderer>, id: Id, size: Option, + on_action: Box Message>, } impl<'a, Message, Theme, Renderer> ResponsiveContainer<'a, Message, Theme, Renderer> @@ -41,7 +41,11 @@ where Renderer: iced_core::Renderer, { /// Creates an empty [`IdContainer`]. - pub(crate) fn new(content: T, id: Id) -> Self + pub(crate) fn new( + content: T, + id: Id, + on_action: impl Fn(crate::surface::Action) -> Message + 'static, + ) -> Self where T: Into>, { @@ -49,6 +53,7 @@ where content: content.into(), id, size: None, + on_action: Box::new(on_action), } } @@ -62,7 +67,6 @@ impl Widget for ResponsiveContainer<'_, Message, Theme, Renderer> where Renderer: iced_core::Renderer, - Message: From, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -148,11 +152,13 @@ where let state = tree.state.downcast_mut::(); if state.needs_update { - shell.publish(Message::from(SurfaceMessage::ResponsiveMenuBar { - menu_bar: self.id.clone(), - limits: state.limits, - size: state.size, - })); + shell.publish((self.on_action)( + crate::surface::Action::ResponsiveMenuBar { + menu_bar: self.id.clone(), + limits: state.limits, + size: state.size, + }, + )); state.needs_update = false; } @@ -264,7 +270,7 @@ where impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where - Message: 'a + From, + Message: 'a, Renderer: 'a + iced_core::Renderer, Theme: 'a, { diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs new file mode 100644 index 00000000..38857100 --- /dev/null +++ b/src/widget/responsive_menu_bar.rs @@ -0,0 +1,78 @@ +use std::collections::HashMap; + +use apply::Apply; + +use crate::{ + widget::{button, icon, responsive_container}, + Core, Element, +}; + +use super::menu; + +/// # Panics +/// +/// Will panic if the menu bar collapses without tracking the size +pub fn responsive_menu_bar<'a, Message: Clone + 'static, A: menu::Action>( + core: &Core, + key_binds: &HashMap, + id: crate::widget::Id, + action_message: impl Fn(crate::surface::Action) -> Message + 'static, + trees: Vec<( + std::borrow::Cow<'static, str>, + Vec>>, + )>, +) -> Element<'a, Message> { + use crate::widget::id_container; + + let menu_bar_size = core.menu_bars.get(&id); + + #[allow(clippy::if_not_else)] + if !menu_bar_size.is_some_and(|(limits, size)| { + let max_size = limits.max(); + max_size.width < size.width + }) { + responsive_container::responsive_container( + id_container( + menu::bar( + trees + .into_iter() + .map(|mt| { + menu::Tree::<_>::with_children( + menu::root(mt.0), + menu::items(key_binds, mt.1), + ) + }) + .collect(), + ), + crate::widget::Id::new(format!("menu_bar_expanded_{id}")), + ), + id, + action_message, + ) + .apply(Element::from) + } else { + responsive_container::responsive_container( + id_container( + menu::bar(vec![menu::Tree::<_>::with_children( + Element::from( + button::icon(icon::from_name("open-menu-symbolic")) + .padding([4, 12]) + .class(crate::theme::Button::MenuRoot), + ), + menu::items( + key_binds, + trees + .into_iter() + .map(|mt| menu::Item::Folder(mt.0, mt.1)) + .collect(), + ), + )]), + crate::widget::Id::new(format!("menu_bar_collapsed_{id}")), + ), + id, + action_message, + ) + .size(menu_bar_size.unwrap().1) + .apply(Element::from) + } +} diff --git a/src/widget/wayland/tooltip/widget.rs b/src/widget/wayland/tooltip/widget.rs index 00a81c45..bcaa66d9 100644 --- a/src/widget/wayland/tooltip/widget.rs +++ b/src/widget/wayland/tooltip/widget.rs @@ -14,7 +14,7 @@ use iced::Task; use iced_runtime::core::widget::Id; use iced_core::event::{self, Event}; -use iced_core::renderer::{self, Renderer}; +use iced_core::renderer; use iced_core::touch; use iced_core::widget::tree::{self, Tree}; use iced_core::widget::Operation; @@ -25,8 +25,6 @@ use iced_core::{ Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; -use crate::surface_message::SurfaceMessage; - pub use super::{Catalog, Style}; /// Internally defines different button widget variants. @@ -41,7 +39,7 @@ enum Variant { /// A generic button which emits a message when pressed. #[allow(missing_debug_implementations)] #[must_use] -pub struct Tooltip<'a, Message, AppMessage> { +pub struct Tooltip<'a, Message> { id: Id, #[cfg(feature = "a11y")] name: Option>, @@ -51,6 +49,7 @@ pub struct Tooltip<'a, Message, AppMessage> { label: Option>, content: crate::Element<'a, Message>, on_leave: Message, + on_surface_action: Box Message>, width: Length, height: Length, padding: Padding, @@ -65,15 +64,10 @@ pub struct Tooltip<'a, Message, AppMessage> { + 'static, >, >, - view: Arc< - dyn Fn() -> crate::Element<'static, crate::app::Message> - + Send - + Sync - + 'static, - >, + view: Arc crate::Element<'static, Message> + Send + Sync + 'static>, } -impl<'a, Message, AppMessage> Tooltip<'a, Message, AppMessage> { +impl<'a, Message> Tooltip<'a, Message> { /// Creates a new [`Tooltip`] with the given content. pub fn new( content: impl Into>, @@ -83,11 +77,9 @@ impl<'a, Message, AppMessage> Tooltip<'a, Message, AppMessage> { + Sync + 'static, >, - view: impl Fn() -> crate::Element<'static, crate::app::Message> - + Send - + Sync - + 'static, + view: impl Fn() -> crate::Element<'static, Message> + Send + Sync + 'static, on_leave: Message, + on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static, ) -> Self { Self { id: Id::unique(), @@ -104,6 +96,7 @@ impl<'a, Message, AppMessage> Tooltip<'a, Message, AppMessage> { selected: false, style: crate::theme::Tooltip::default(), on_leave, + on_surface_action: Box::new(on_surface_action), delay: None, settings: if let Some(s) = settings { Some(Arc::new(s)) @@ -189,11 +182,8 @@ impl<'a, Message, AppMessage> Tooltip<'a, Message, AppMessage> { } } -impl< - 'a, - Message: 'a + Clone + From, - AppMessage: Clone + 'static + From, - > Widget for Tooltip<'a, Message, AppMessage> +impl<'a, Message: 'static + Clone> Widget + for Tooltip<'a, Message> { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -273,6 +263,7 @@ impl< &self.view, self.delay, &self.on_leave, + &self.on_surface_action, || tree.state.downcast_mut::(), ); status.merge(self.content.as_widget_mut().on_event( @@ -390,13 +381,8 @@ impl< } } -impl< - 'a, - Message: From + Clone + 'a, - AppMessage: From + Clone + 'static, - > From> for crate::Element<'a, Message> -{ - fn from(button: Tooltip<'a, Message, AppMessage>) -> Self { +impl<'a, Message: Clone + 'static> From> for crate::Element<'a, Message> { + fn from(button: Tooltip<'a, Message>) -> Self { Self::new(button) } } @@ -419,11 +405,7 @@ impl State { /// Processes the given [`Event`] and updates the [`State`] of a [`Tooltip`] /// accordingly. #[allow(clippy::needless_pass_by_value)] -pub fn update< - 'a, - Message: Clone + From, - AppMessage: Clone + 'a + From + 'static, ->( +pub fn update<'a, Message: Clone + 'static>( _id: Id, event: Event, layout: Layout<'_>, @@ -437,14 +419,10 @@ pub fn update< + 'static, >, >, - view: &Arc< - dyn Fn() -> crate::Element<'static, crate::app::Message> - + Send - + Sync - + 'static, - >, + view: &Arc crate::Element<'static, Message> + Send + Sync + 'static>, delay: Option, on_leave: &Message, + on_surface_action: &dyn Fn(crate::surface::Action) -> Message, state: impl FnOnce() -> &'a mut State, ) -> event::Status { match event { @@ -491,46 +469,44 @@ pub fn update< let view = view.clone(); let bounds = layout.bounds(); - shell.publish( - SurfaceMessage::Task(Arc::new(move || { - let s = s.clone(); - let view = view.clone(); - let is_hovered = is_hovered.clone(); - Task::future(async move { - _ = tokio::time::sleep(delay).await; + let sm = crate::surface::Action::Task(Arc::new(move || { + let s = s.clone(); + let view = view.clone(); + let is_hovered = is_hovered.clone(); + Task::future(async move { + _ = tokio::time::sleep(delay).await; - let is_hovered = is_hovered.clone(); - let g = is_hovered.lock().unwrap(); - if !*g { - return SurfaceMessage::Ignore; - } - let boxed: Box< - dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings - + Send - + Sync - + 'static, - > = Box::new(move || s(bounds)); - let boxed: Box = Box::new(boxed); - SurfaceMessage::Popup( - Arc::new(boxed), - Some({ - let boxed: Box< - dyn Fn() -> crate::Element< - 'static, - crate::app::Message, - > + Send - + Sync - + 'static, - > = Box::new(move || view()); - let boxed: Box = - Box::new(boxed); - Arc::new(boxed) - }), - ) - }) - })) - .into(), - ); + let is_hovered = is_hovered.clone(); + let g = is_hovered.lock().unwrap(); + if !*g { + return crate::surface::Action::Ignore; + } + let boxed: Box< + dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + > = Box::new(move || s(bounds)); + let boxed: Box = + Box::new(boxed); + crate::surface::Action::Popup( + Arc::new(boxed), + Some({ + let boxed: Box< + dyn Fn() -> crate::Element<'static, Message> + + Send + + Sync + + 'static, + > = Box::new(move || view()); + let boxed: Box = + Box::new(boxed); + Arc::new(boxed) + }), + ) + }) + })); + + shell.publish((on_surface_action)(sm)); } else { let s = settings.clone(); let view = view.clone(); @@ -544,14 +520,12 @@ pub fn update< > = Box::new(move || s(bounds)); let boxed: Box = Box::new(boxed); - let sm = SurfaceMessage::Popup( + let sm = crate::surface::Action::Popup( Arc::new(boxed), Some({ let boxed: Box< - dyn Fn() -> crate::Element< - 'static, - crate::app::Message, - > + Send + dyn Fn() -> crate::Element<'static, Message> + + Send + Sync + 'static, > = Box::new(move || view()); @@ -560,7 +534,7 @@ pub fn update< Arc::new(boxed) }), ); - shell.publish(sm.into()); + shell.publish((on_surface_action)(sm)); } } }