refactor: popups for nav bar / segmented buttons

This commit is contained in:
Ashley Wulber 2026-06-12 14:20:37 -04:00 committed by Ashley Wulber
parent f5fc392ba4
commit cde1989e78
13 changed files with 544 additions and 57 deletions

View file

@ -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<Message>;
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<dyn std::error::Error>> {
@ -158,6 +177,70 @@ impl cosmic::Application for App {
self.update_title()
}
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
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<Self::Message> {
match message {

View file

@ -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<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
Some(menu::items(
/// The context menu list.
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
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(),
))
}

View file

@ -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<Vec<menu::Tree<crate::Action<Self::Message>>>> {
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<crate::Action<Self::Message>>>> {
None
}
@ -635,10 +645,8 @@ impl<App: Application> 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([

View file

@ -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};

View file

@ -230,7 +230,7 @@ where
path_highlight: Some(PathHighlight::MenuActive),
menu_roots,
style: <crate::Theme as StyleSheet>::Style::default(),
window_id: window::Id::NONE,
window_id: window::Id::RESERVED,
#[cfg(all(
feature = "multi-window",
feature = "wayland",

View file

@ -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);

View file

@ -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<Message = Message>,
L: Into<Cow<'static, str>> + From<&'static str> + 'static,
Message: 'static + std::clone::Clone,
>(
key_binds: &HashMap<KeyBind, A>,
children: Vec<Vec<MenuItem<A, L>>>,
) -> Vec<MenuTree<Message>> {
let menus = children
.into_iter()
.map(|m| MenuItem::<A, L>::Folder(L::from(""), m));
let root = vec![MenuItem::<A, L>::Folder(L::from(""), menus.collect())];
menu_items(key_binds, root)
}

View file

@ -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<window::Id>) -> 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<NavBar<'a, Message>>

View file

@ -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<SelectionMode: Default, Message>(
pub fn horizontal<SelectionMode: Default, Message: Clone + 'static>(
model: &Model<SelectionMode>,
) -> SegmentedButton<'_, Horizontal, SelectionMode, Message>
where
@ -30,7 +30,7 @@ where
SegmentedButton::new(model)
}
impl<SelectionMode, Message> SegmentedVariant
impl<SelectionMode, Message: Clone + 'static> SegmentedVariant
for SegmentedButton<'_, Horizontal, SelectionMode, Message>
where
Model<SelectionMode>: Selectable,

View file

@ -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<SelectionMode, Message>(
pub fn vertical<SelectionMode, Message: Clone + 'static>(
model: &Model<SelectionMode>,
) -> SegmentedButton<'_, Vertical, SelectionMode, Message>
where
@ -30,7 +30,7 @@ where
SegmentedButton::new(model)
}
impl<SelectionMode, Message> SegmentedVariant
impl<SelectionMode, Message: Clone + 'static> SegmentedVariant
for SegmentedButton<'_, Vertical, SelectionMode, Message>
where
Model<SelectionMode>: Selectable,

View file

@ -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<SelectionMode>: Selectable,
SelectionMode: Default,
@ -189,14 +190,28 @@ where
pub(super) tab_drag: Option<TabDragSource<Message>>,
#[setters(skip)]
pub(super) on_drop_hint: Option<Box<dyn Fn(Option<(Entity, bool)>) -> Message + 'static>>,
#[setters(skip)]
pub(super) on_reorder: Option<Box<dyn Fn(ReorderEvent) -> 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<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
/// Defines the implementation of this struct
variant: PhantomData<Variant>,
}
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<SelectionMode>: 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<window::Id>) -> 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::<LocalState>();
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<Message: 'static>(
live_settings: impl Fn() -> LiveSettings + Send + Sync + 'static,
settings: impl Fn()
-> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
+ Send
+ Sync
+ 'static,
view: Option<impl Fn() -> 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<dyn Any + Send + Sync + 'static> = Box::new(boxed);
let boxed_live: Box<dyn Fn() -> LiveSettings + Send + Sync + 'static> =
Box::new(live_settings);
let boxed_live: Box<dyn Any + Send + Sync + 'static> = 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<dyn Any + Send + Sync + 'static> = 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<Variant, SelectionMode, Message> Widget<Message, crate::Theme, Renderer>
@ -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::<LocalState>();
@ -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<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
#[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::<LocalState>();
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 {

View file

@ -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<SelectionMode: Default, Message>(
pub fn horizontal<SelectionMode: Default, Message: Clone + 'static>(
model: &Model<SelectionMode>,
) -> 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<SelectionMode, Message>(
pub fn vertical<SelectionMode, Message: Clone + 'static>(
model: &Model<SelectionMode>,
) -> VerticalSegmentedButton<'_, SelectionMode, Message>
where

View file

@ -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<SelectionMode: Default, Message>(
pub fn horizontal<SelectionMode: Default, Message: Clone + 'static>(
model: &Model<SelectionMode>,
) -> 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<SelectionMode, Message>(
pub fn vertical<SelectionMode, Message: Clone + 'static>(
model: &Model<SelectionMode>,
) -> VerticalSegmentedButton<'_, SelectionMode, Message>
where