diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index e60b4f7e..af4200c9 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -7,7 +7,7 @@ use cosmic::app::Settings; use cosmic::iced::{Alignment, Length, Size}; use cosmic::prelude::*; use cosmic::widget::menu::{self, KeyBind}; -use cosmic::widget::nav_bar; +use cosmic::widget::{nav_bar, RcElementWrapper}; use cosmic::{executor, iced, widget, Core}; use std::collections::HashMap; use std::sync::LazyLock; @@ -43,7 +43,7 @@ pub enum Action { impl widget::menu::Action for Action { type Message = Message; - fn message(&self) -> Message { + fn message(&self) -> Self::Message { match self { Action::Hi => Message::Hi, Action::Hi2 => Message::Hi2, @@ -52,6 +52,25 @@ impl widget::menu::Action for Action { } } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Action2 { + Hi, + Hi2, + Hi3, +} + +impl widget::menu::Action for Action2 { + type Message = cosmic::Action; + + fn message(&self) -> Self::Message { + cosmic::Action::App(match self { + Action2::Hi => Message::Hi, + Action2::Hi2 => Message::Hi2, + Action2::Hi3 => Message::Hi3, + }) + } +} + /// Runs application with these settings #[rustfmt::skip] fn main() -> Result<(), Box> { @@ -158,6 +177,70 @@ impl cosmic::Application for App { self.update_title() } + fn nav_context_menu(&self) -> Option>>> { + Some(menu::nav_context( + &HashMap::new(), + vec![ + vec![cosmic::widget::menu::Item::Button( + "Hi", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + )], + vec![ + cosmic::widget::menu::Item::Button( + "Hi 2", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + ), + cosmic::widget::menu::Item::Button( + "Hi 22", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + ), + ], + vec![ + cosmic::widget::menu::Item::Button( + "Hi 3", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + ), + cosmic::widget::menu::Item::Button( + "Hi 33", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + ), + cosmic::widget::menu::Item::Button( + "Hi 333", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + ), + ], + vec![ + cosmic::widget::menu::Item::Button( + "Hi 44", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + ), + cosmic::widget::menu::Item::Button( + "Hi 4444", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + ), + cosmic::widget::menu::Item::Button( + "Hi 444444", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + ), + cosmic::widget::menu::Item::Button( + "Hi 444444444444", + Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), + Action2::Hi, + ), + ], + ], + )) + } + /// Handle application events here. fn update(&mut self, message: Self::Message) -> cosmic::app::Task { match message { diff --git a/examples/nav-context/src/main.rs b/examples/nav-context/src/main.rs index 1992066f..875b83ff 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::Size; -use cosmic::widget::{menu, nav_bar}; +use cosmic::widget::{menu, nav_bar, space}; use cosmic::{executor, iced, ApplicationExt, Element}; #[derive(Clone, Copy)] @@ -127,18 +127,20 @@ impl cosmic::Application for App { Some(&self.nav_model) } - /// The context menu to display for the given nav bar item ID. - fn nav_context_menu( - &self, - id: nav_bar::Id, - ) -> Option>>> { - Some(menu::items( + /// The context menu list. + fn nav_context_menu(&self) -> Option>>> { + Some(menu::nav_context( &HashMap::new(), - vec![ - menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(id)), - menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(id)), - menu::Item::Button("Delete", None, NavMenuAction::Delete(id)), - ], + self.nav_model + .iter() + .map(|e| { + vec![ + menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(e)), + menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(e)), + menu::Item::Button("Delete", None, NavMenuAction::Delete(e)), + ] + }) + .collect(), )) } diff --git a/src/app/mod.rs b/src/app/mod.rs index e2d6ae1c..99ea6e53 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -389,10 +389,23 @@ where let mut nav = 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() - .width(iced::Length::Shrink) - .height(iced::Length::Fill); + .context_menu(self.nav_context_menu()); + #[cfg(all( + feature = "multi-window", + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" + ))] + { + nav = nav + .window_id_maybe(self.core().main_window_id()) + .on_surface_action(|m| crate::Action::Cosmic(crate::app::Action::Surface(m))) + } + let mut nav = nav + .into_container() + .width(iced::Length::Shrink) + .height(iced::Length::Fill); if !self.core().is_condensed() { nav = nav.max_width(280); @@ -402,10 +415,7 @@ 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) -> Option>>> { None } @@ -635,10 +645,8 @@ impl ApplicationExt for App { let mut widgets = Vec::with_capacity(3); // Insert nav bar onto the left side of the window. - let has_nav = if let Some(nav) = self - .nav_bar() - .map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"))) - { + let has_nav = if let Some(nav) = self.nav_bar() { + let nav = id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar")); widgets.push( container(nav) .padding([ diff --git a/src/widget/menu.rs b/src/widget/menu.rs index a452e2b8..17d47cdd 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -72,6 +72,7 @@ mod menu_inner; mod menu_tree; pub use menu_tree::{ MenuItem as Item, MenuTree as Tree, menu_button, menu_items as items, menu_root as root, + nav_context, }; pub use crate::style::menu_bar::{Appearance, StyleSheet}; diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index e9d25dd8..560b8d2a 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -230,7 +230,7 @@ where path_highlight: Some(PathHighlight::MenuActive), menu_roots, style: ::Style::default(), - window_id: window::Id::NONE, + window_id: window::Id::RESERVED, #[cfg(all( feature = "multi-window", feature = "wayland", diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index b7595ba5..47e8e30d 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -525,6 +525,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { ); let node_size = children_node.size(); intrinsic_size.height += node_size.height; + intrinsic_size.width = intrinsic_size.width.max(node_size.width); nodes.push(children_node); diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index c09151aa..bd4f1e6c 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -401,3 +401,19 @@ pub fn menu_items< }) .collect() } + +/// Create a menu tree from a widget and a vector of sub trees +pub fn nav_context< + A: MenuAction, + L: Into> + From<&'static str> + 'static, + Message: 'static + std::clone::Clone, +>( + key_binds: &HashMap, + children: Vec>>, +) -> Vec> { + let menus = children + .into_iter() + .map(|m| MenuItem::::Folder(L::from(""), m)); + let root = vec![MenuItem::::Folder(L::from(""), menus.collect())]; + menu_items(key_binds, root) +} diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index 75809398..d6595359 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -8,7 +8,7 @@ use apply::Apply; use iced::clipboard::dnd::DndAction; use iced::clipboard::mime::AllowedMimeTypes; -use iced::{Background, Length}; +use iced::{Background, Length, window}; use iced_core::{Border, Color, Shadow}; use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button}; @@ -55,7 +55,7 @@ where } #[must_use] -pub struct NavBar<'a, Message> { +pub struct NavBar<'a, Message: Clone + 'static> { segmented_button: segmented_button::VerticalSegmentedButton<'a, segmented_button::SingleSelect, Message>, } @@ -133,6 +133,42 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> { self.segmented_button = self.segmented_button.on_dnd_leave(handler); self } + + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + target_os = "linux" + ))] + pub fn with_positioner( + mut self, + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + ) -> Self { + self.segmented_button = self.segmented_button.with_positioner(positioner); + self + } + + #[must_use] + pub fn window_id(mut self, id: window::Id) -> Self { + self.segmented_button = self.segmented_button.window_id(id); + self + } + + #[must_use] + pub fn window_id_maybe(mut self, id: Option) -> Self { + self.segmented_button = self.segmented_button.window_id_maybe(id); + + self + } + + #[must_use] + pub fn on_surface_action( + mut self, + handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static, + ) -> Self { + self.segmented_button = self.segmented_button.on_surface_action(handler); + self + } } impl<'a, Message: Clone + 'static> From> diff --git a/src/widget/segmented_button/horizontal.rs b/src/widget/segmented_button/horizontal.rs index 5fd67649..62205264 100644 --- a/src/widget/segmented_button/horizontal.rs +++ b/src/widget/segmented_button/horizontal.rs @@ -21,7 +21,7 @@ pub struct Horizontal; /// Horizontal implementation of the [`SegmentedButton`]. /// /// For details on the model, see the [`segmented_button`](super) module for more details. -pub fn horizontal( +pub fn horizontal( model: &Model, ) -> SegmentedButton<'_, Horizontal, SelectionMode, Message> where @@ -30,7 +30,7 @@ where SegmentedButton::new(model) } -impl SegmentedVariant +impl SegmentedVariant for SegmentedButton<'_, Horizontal, SelectionMode, Message> where Model: Selectable, diff --git a/src/widget/segmented_button/vertical.rs b/src/widget/segmented_button/vertical.rs index 5458cd0a..26cc2bea 100644 --- a/src/widget/segmented_button/vertical.rs +++ b/src/widget/segmented_button/vertical.rs @@ -20,7 +20,7 @@ pub type VerticalSegmentedButton<'a, SelectionMode, Message> = /// Vertical implementation of the [`SegmentedButton`]. /// /// For details on the model, see the [`segmented_button`](super) module for more details. -pub fn vertical( +pub fn vertical( model: &Model, ) -> SegmentedButton<'_, Vertical, SelectionMode, Message> where @@ -30,7 +30,7 @@ where SegmentedButton::new(model) } -impl SegmentedVariant +impl SegmentedVariant for SegmentedButton<'_, Vertical, SelectionMode, Message> where Model: Selectable, diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index edd58eb7..773e20a1 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -9,7 +9,7 @@ use crate::widget::menu::{ self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, menu_roots_children, menu_roots_diff, }; -use crate::widget::{Icon, icon}; +use crate::widget::{Icon, context_menu, icon}; use crate::{Element, Renderer}; use derive_setters::Setters; use iced::clipboard::dnd::{ @@ -38,6 +38,7 @@ use std::collections::HashSet; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; +use std::sync::Arc; use std::time::{Duration, Instant}; thread_local! { @@ -107,7 +108,7 @@ pub trait SegmentedVariant { /// A conjoined group of items that function together as a button. #[derive(Setters)] #[must_use] -pub struct SegmentedButton<'a, Variant, SelectionMode, Message> +pub struct SegmentedButton<'a, Variant, SelectionMode, Message: Clone + 'static> where Model: Selectable, SelectionMode: Default, @@ -189,14 +190,28 @@ where pub(super) tab_drag: Option>, #[setters(skip)] pub(super) on_drop_hint: Option) -> Message + 'static>>, + #[setters(skip)] pub(super) on_reorder: Option Message + 'static>>, #[setters(skip)] + window_id: window::Id, + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + target_os = "linux" + ))] + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + #[setters(skip)] + pub(crate) on_surface_action: + Option Message + Send + Sync + 'static>>, + /// Defines the implementation of this struct variant: PhantomData, } -impl<'a, Variant, SelectionMode, Message> SegmentedButton<'a, Variant, SelectionMode, Message> +impl<'a, Variant, SelectionMode, Message: Clone + 'static> + SegmentedButton<'a, Variant, SelectionMode, Message> where Self: SegmentedVariant, Model: Selectable, @@ -243,6 +258,15 @@ where tab_drag: None, on_drop_hint: None, on_reorder: None, + window_id: window::Id::RESERVED, + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + target_os = "linux" + ))] + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), + on_surface_action: None, } } @@ -306,12 +330,7 @@ where where Message: Clone + 'static, { - self.context_menu = context_menu.map(|menus| { - vec![menu::Tree::with_children( - crate::Element::from(crate::widget::Row::new()), - menus, - )] - }); + self.context_menu = context_menu; if let Some(ref mut context_menu) = self.context_menu { context_menu.iter_mut().for_each(menu::Tree::set_index); @@ -863,6 +882,253 @@ where |id| id.0, ) } + + #[cfg(all( + feature = "multi-window", + feature = "wayland", + feature = "winit", + target_os = "linux" + ))] + pub fn with_positioner( + mut self, + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + ) -> Self { + self.positioner = positioner; + self + } + + #[must_use] + pub fn window_id(mut self, id: window::Id) -> Self { + self.window_id = id; + self + } + + #[must_use] + pub fn window_id_maybe(mut self, id: Option) -> Self { + if let Some(id) = id { + self.window_id = id; + } + self + } + + #[must_use] + pub fn on_surface_action( + mut self, + handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static, + ) -> Self { + self.on_surface_action = Some(Arc::new(handler)); + self + } + + #[cfg(all( + feature = "multi-window", + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" + ))] + #[allow(clippy::too_many_lines)] + fn create_popup<'b>( + &mut self, + layout: Layout<'b>, + view_cursor: mouse::Cursor, + renderer: &'b Renderer, + shell: &'b mut Shell<'_, Message>, + viewport: &'b Rectangle, + tree: &'b mut Tree, + ) { + let state = tree.state.downcast_mut::(); + let my_state = state.menu_state.clone(); + + if self.window_id != window::Id::NONE { + use crate::surface::action::{LiveSettings, destroy_popup}; + use iced_runtime::platform_specific::wayland::popup::{ + SctkPopupSettings, SctkPositioner, + }; + + let Some(surface_action) = self.on_surface_action.as_ref() else { + return; + }; + + let id = my_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.get(&self.window_id).copied() { + // close existing popups + state.menu_states.clear(); + state.active_root.clear(); + shell.publish(surface_action(destroy_popup(id))); + state.view_cursor = view_cursor; + id + } else { + window::Id::unique() + } + }); + let Some(entity) = state.show_context else { + return; + }; + + let Some((mut bounds, i)) = self + .variant_bounds(state, layout.bounds()) + .enumerate() + .find_map(|(i, item)| match item { + ItemBounds::Button(e, bounds) if e == entity => Some((bounds, i)), + _ => None, + }) + else { + return; + }; + + assert!( + self.context_menu + .as_ref() + .is_none_or(|m| m[0].children.len() == self.model.len()), + "model length must match the number of context menus" + ); + let menu = self + .context_menu + .as_mut() + .map(|m| m[0].children[i].clone()) + .unwrap(); + + bounds.x = state.context_cursor.x; + bounds.y = state.context_cursor.y; + + let mut popup_menu: menu::Menu<'static, _> = menu::Menu { + tree: my_state.clone(), + menu_roots: std::borrow::Cow::Owned(vec![menu]), + bounds_expand: 0, + menu_overlays_parent: false, + close_condition: CloseCondition { + leave: false, + click_outside: true, + click_inside: true, + }, + item_width: ItemWidth::Uniform(240), + item_height: ItemHeight::Dynamic(40), + bar_bounds: bounds, + main_offset: 0, + cross_offset: 0, + root_bounds_list: vec![bounds], + path_highlight: Some(PathHighlight::MenuActive), + style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), + position: Point::new(0., 0.), + is_overlay: false, + window_id: id, + depth: 0, + on_surface_action: self.on_surface_action.clone(), + }; + + menu::init_root_menu( + &mut popup_menu, + renderer, + shell, + view_cursor.position().unwrap(), + viewport.size(), + Vector::new(0., 0.), + bounds, + 0., // TODO offset? + ); + let (anchor_rect, gravity) = my_state.inner.with_data_mut(|state| { + state.popup_id.insert(self.window_id, id); + (state + .menu_states + .iter() + .find(|s| s.index.is_none()) + .map(|s| s.menu_bounds.parent_bounds) + .map_or_else( + || { + let bounds = layout.bounds(); + Rectangle { + x: bounds.x as i32, + y: bounds.y as i32, + width: 1, + height: 1, + } + }, + |r| Rectangle { + x: r.x as i32, + y: r.y as i32, + width: 1, + height: 1, + }, + ), match (state.horizontal_direction, state.vertical_direction) { + (menu::Direction::Positive, menu::Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, + (menu::Direction::Positive, menu::Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight, + (menu::Direction::Negative, menu::Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft, + (menu::Direction::Negative, menu::Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft, + }) + }); + + let menu_node = + popup_menu.layout(renderer, layout::Limits::NONE.min_width(1.).min_height(1.)); + let popup_size = menu_node.size(); + let positioner = SctkPositioner { + size: Some(( + popup_size.width.ceil() as u32 + 2, + popup_size.height.ceil() as u32 + 2, + )), + anchor_rect, + anchor: + cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft, + gravity, + reactive: true, + ..Default::default() + }; + let parent = self.window_id; + /// Used to create a popup message from within a widget. + #[cfg(all(feature = "wayland", target_os = "linux", feature = "winit"))] + #[must_use] + pub fn simple_popup( + live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static, + settings: impl Fn() + -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + view: Option crate::Element<'static, Message> + Send + Sync + 'static>, + ) -> crate::surface::Action { + use std::any::Any; + + let boxed: Box< + dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + + Send + + Sync + + 'static, + > = Box::new(settings); + let boxed: Box = Box::new(boxed); + + let boxed_live: Box LiveSettings + Send + Sync + 'static> = + Box::new(live_settings); + let boxed_live: Box = Box::new(boxed_live); + + crate::surface::Action::Popup( + Arc::new(boxed), + Arc::new(boxed_live), + view.map(|view| { + let boxed: Box< + dyn Fn() -> crate::Element<'static, Message> + Send + Sync + 'static, + > = Box::new(view); + let boxed: Box = Box::new(boxed); + Arc::new(boxed) + }), + ) + } + shell.publish((surface_action)(simple_popup( + LiveSettings::default, + move || SctkPopupSettings { + parent, + id, + positioner: positioner.clone(), + parent_size: None, + grab: true, + close_with_children: false, + input_zone: None, + }, + Some(move || { + Element::from(crate::widget::container(popup_menu.clone()).center(Length::Fill)) + }), + ))); + } + } } impl Widget @@ -974,10 +1240,10 @@ where mut event: &Event, layout: Layout<'_>, cursor_position: mouse::Cursor, - _renderer: &Renderer, + renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - _viewport: &iced::Rectangle, + viewport: &iced::Rectangle, ) { let my_bounds = layout.bounds(); let state = tree.state.downcast_mut::(); @@ -1396,6 +1662,7 @@ where && (right_button_released(&event) || (touch_lifted(&event) && fingers_pressed == 2)) { + // TODO create popup state.show_context = Some(key); state.context_cursor = cursor_position.position().unwrap_or_default(); @@ -1408,9 +1675,29 @@ where shell.publish(on_context(key)); shell.capture_event(); + + #[cfg(all( + feature = "multi-window", + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" + ))] + if matches!( + crate::app::cosmic::WINDOWING_SYSTEM.get(), + Some(crate::app::cosmic::WindowingSystem::Wayland) + ) { + self.create_popup( + layout, + cursor_position, + renderer, + shell, + viewport, + tree, + ); + } return; } - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = event { @@ -1528,6 +1815,34 @@ where } } + if (matches!(event, Event::Mouse(mouse::Event::ButtonReleased(_))) || (touch_lifted(event))) + && let Some(_id) = state + .menu_state + .inner + .with_data_mut(|ms| ms.popup_id.remove(&self.window_id)) + { + #[cfg(all( + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" + ))] + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + shell.capture_event(); + + shell.publish(surface_action(crate::surface::action::destroy_popup(_id))); + } + state.show_context = None; + + state.menu_state.inner.with_data_mut(|data| { + // Clear stale MenuBounds from any previous context menu before opening a new one. + data.reset(); + data.open = false; + data.view_cursor = cursor_position; + }); + } + if matches!( event, Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -2146,19 +2461,44 @@ where _viewport: &iced_core::Rectangle, translation: Vector, ) -> Option> { + #[cfg(all( + feature = "multi-window", + feature = "wayland", + target_os = "linux", + feature = "winit", + feature = "surface-message" + ))] + if matches!( + crate::app::cosmic::WINDOWING_SYSTEM.get(), + Some(crate::app::cosmic::WindowingSystem::Wayland) + ) && self.on_surface_action.is_some() + && self.window_id != window::Id::NONE + { + return None; + } + let state = tree.state.downcast_mut::(); let menu_state = state.menu_state.clone(); let entity = state.show_context?; - let mut bounds = - self.variant_bounds(state, layout.bounds()) - .find_map(|item| match item { - ItemBounds::Button(e, bounds) if e == entity => Some(bounds), - _ => None, - })?; + let (mut bounds, i) = self + .variant_bounds(state, layout.bounds()) + .enumerate() + .find_map(|(i, item)| match item { + ItemBounds::Button(e, bounds) if e == entity => Some((bounds, i)), + _ => None, + })?; - let context_menu = self.context_menu.as_mut()?; + assert!( + self.context_menu + .as_ref() + .is_none_or(|m| m[0].children.len() == self.model.len()) + ); + let menu = self + .context_menu + .as_mut() + .map(|m| m[0].children[i].clone())?; if !menu_state.inner.with_data(|data| data.open) { // If the menu is not open, we don't need to show it. @@ -2176,7 +2516,7 @@ where Some( crate::widget::menu::Menu { tree: menu_state, - menu_roots: std::borrow::Cow::Owned(context_menu.clone()), + menu_roots: std::borrow::Cow::Owned(vec![menu]), bounds_expand: 16, menu_overlays_parent: true, close_condition: CloseCondition { diff --git a/src/widget/segmented_control.rs b/src/widget/segmented_control.rs index 046956c7..cecc3eb1 100644 --- a/src/widget/segmented_control.rs +++ b/src/widget/segmented_control.rs @@ -14,7 +14,7 @@ use super::segmented_button::{ /// The data for the widget comes from a model that is maintained the application. /// /// For details on the model, see the [`segmented_button`] module for more details. -pub fn horizontal( +pub fn horizontal( model: &Model, ) -> HorizontalSegmentedButton<'_, SelectionMode, Message> where @@ -37,7 +37,7 @@ where /// The data for the widget comes from a model that is maintained the application. /// /// For details on the model, see the [`segmented_button`] module for more details. -pub fn vertical( +pub fn vertical( model: &Model, ) -> VerticalSegmentedButton<'_, SelectionMode, Message> where diff --git a/src/widget/tab_bar.rs b/src/widget/tab_bar.rs index a08128b4..358f1d4b 100644 --- a/src/widget/tab_bar.rs +++ b/src/widget/tab_bar.rs @@ -14,7 +14,7 @@ use super::segmented_button::{ /// The data for the widget comes from a model supplied by the application. /// /// For details on the model, see the [`segmented_button`] module for more details. -pub fn horizontal( +pub fn horizontal( model: &Model, ) -> HorizontalSegmentedButton<'_, SelectionMode, Message> where @@ -35,7 +35,7 @@ where /// /// The data for the widget comes from a model that is maintained the application. /// For details on the model, see the [`segmented_button`] module for more details. -pub fn vertical( +pub fn vertical( model: &Model, ) -> VerticalSegmentedButton<'_, SelectionMode, Message> where