From aebf8c4259cba2fff19492f24ba6d07ea50a9466 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 22 Jul 2025 17:02:18 -0400 Subject: [PATCH] refactor(context-menu): action map for popups --- src/widget/context_menu.rs | 89 +++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index b1014cf4f7d..41013aa3645 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -5,6 +5,7 @@ #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; +use crate::surface; use crate::widget::menu::{ self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight, init_root_menu, menu_roots_diff, @@ -22,7 +23,7 @@ pub fn context_menu( content: impl Into> + 'static, // on_context: Message, context_menu: Option>>, -) -> ContextMenu<'static, Message> { +) -> ContextMenu<'static, Message, Message> { let mut this = ContextMenu { content: content.into(), context_menu: context_menu.map(|menus| { @@ -33,6 +34,7 @@ pub fn context_menu( }), window_id: window::Id::RESERVED, on_surface_action: None, + action_map: None, }; if let Some(ref mut context_menu) = this.context_menu { @@ -42,10 +44,42 @@ pub fn context_menu( this } +/// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. +pub fn context_menu_popup( + content: impl Into> + 'static, + // on_context: Message, + context_menu: Option>>, + _parent_id: window::Id, + _on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static, + _map_action: impl Fn(Message) -> AppMessage + Send + Sync + 'static, +) -> ContextMenu<'static, Message, AppMessage> { + let mut this: ContextMenu<'_, Message, AppMessage> = ContextMenu { + content: content.into(), + context_menu: context_menu.map(|menus| { + vec![menu::Tree::with_children( + crate::Element::from(crate::widget::row::<'static, Message>()), + menus, + )] + }), + window_id: window::Id::RESERVED, + on_surface_action: None, + action_map: None, + }; + + #[cfg(all(feature = "winit", feature = "wayland"))] + let mut this = this.with_popup(_parent_id, _on_surface_action, _map_action); + + if let Some(ref mut context_menu) = this.context_menu { + context_menu.iter_mut().for_each(menu::Tree::set_index); + } + + this +} + /// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. #[derive(Setters)] #[must_use] -pub struct ContextMenu<'a, Message> { +pub struct ContextMenu<'a, Message, AppMessage> { #[setters(skip)] content: crate::Element<'a, Message>, #[setters(skip)] @@ -54,9 +88,13 @@ pub struct ContextMenu<'a, Message> { #[setters(skip)] pub(crate) on_surface_action: Option Message + Send + Sync + 'static>>, + #[setters(skip)] + pub action_map: Option AppMessage + 'static + Send + Sync>>, } -impl ContextMenu<'_, Message> { +impl<'a, Message: Clone + 'static, AppMessage: Clone + 'static> + ContextMenu<'a, Message, AppMessage> +{ #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[allow(clippy::too_many_lines)] fn create_popup( @@ -68,7 +106,10 @@ impl ContextMenu<'_, Message> { viewport: &iced::Rectangle, my_state: &mut LocalState, ) { - if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { + if self.window_id != window::Id::NONE + && self.on_surface_action.is_some() + && self.action_map.is_some() + { use crate::{surface::action::destroy_popup, widget::menu::Menu}; use iced_runtime::platform_specific::wayland::popup::{ SctkPopupSettings, SctkPositioner, @@ -171,6 +212,7 @@ impl ContextMenu<'_, Message> { ..Default::default() }; let parent = self.window_id; + let action_map = self.action_map.clone().unwrap(); shell.publish((self.on_surface_action.as_ref().unwrap())( crate::surface::action::simple_popup( move || SctkPopupSettings { @@ -183,27 +225,44 @@ impl ContextMenu<'_, Message> { input_zone: None, }, Some(move || { + let action_map = action_map.clone(); crate::Element::from( crate::widget::container(popup_menu.clone()).center(Length::Fill), ) - .map(crate::action::app) + .map(move |m| crate::Action::App(action_map.clone()(m))) }), ), )); } } - pub fn on_surface_action( + #[cfg(all(feature = "winit", feature = "wayland"))] + /// Handle dropdown requests for popup creation. + /// Intended to be used with [`crate::app::message::get_popup`] + pub fn with_popup( mut self, - handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static, - ) -> Self { - self.on_surface_action = Some(Arc::new(handler)); - self + parent_id: window::Id, + on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static, + action_map: impl Fn(Message) -> NewAppMessage + Send + Sync + 'static, + ) -> ContextMenu<'a, Message, NewAppMessage> { + let Self { + content, + context_menu, + .. + } = self; + let new = ContextMenu::<'a, Message, NewAppMessage> { + content, + context_menu, + on_surface_action: Some(Arc::new(on_surface_action)), + action_map: Some(Arc::new(action_map)), + window_id: parent_id, + }; + new } } -impl Widget - for ContextMenu<'_, Message> +impl + Widget for ContextMenu<'_, Message, AppMessage> { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -539,10 +598,10 @@ impl Widget } } -impl<'a, Message: Clone + 'static> From> - for crate::Element<'static, Message> +impl<'a, Message: Clone + 'static, AppMessage: Clone + 'static> + From> for crate::Element<'static, Message> { - fn from(widget: ContextMenu<'static, Message>) -> Self { + fn from(widget: ContextMenu<'static, Message, AppMessage>) -> Self { Self::new(widget) } }