fix(menu): panic when right-clicking a submenu

This commit is contained in:
Alex Klepov 2026-08-20 01:18:04 +03:00 • committed by GitHub
parent a5267e623f
commit 3a8ebf9197
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 56 additions and 43 deletions

View file

@ -30,6 +30,7 @@ pub enum Message {
WindowClose, WindowClose,
Surface(cosmic::surface::Action), Surface(cosmic::surface::Action),
ToggleHideContent, ToggleHideContent,
ToggleSomeAction,
WindowNew, WindowNew,
} }
@ -82,6 +83,7 @@ impl cosmic::Application for App {
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
tracing::error!("Clicked {message:?}");
match message { match message {
Message::Clicked => { Message::Clicked => {
self.button_label = format!("Clicked {message:?}"); self.button_label = format!("Clicked {message:?}");
@ -91,9 +93,10 @@ impl cosmic::Application for App {
cosmic::app::Action::Surface(action), cosmic::app::Action::Surface(action),
)); ));
} }
Message::WindowClose => {} Message::WindowClose |
Message::ToggleHideContent => {} Message::ToggleHideContent |
Message::WindowNew => {} Message::ToggleSomeAction |
Message::WindowNew=> {}
} }
Task::none() Task::none()
@ -102,7 +105,7 @@ impl cosmic::Application for App {
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let widget = cosmic::widget::context_menu( let widget = cosmic::widget::context_menu(
cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked), cosmic::widget::button::text(self.button_label.clone()).on_press(Message::Clicked),
self.context_menu(), self.context_menu(),
) )
.on_surface_action(Message::Surface); .on_surface_action(Message::Surface);
@ -131,7 +134,14 @@ impl App {
None, None,
self.hide_content, self.hide_content,
ContextMenuAction::ToggleHideContent, ContextMenuAction::ToggleHideContent,
)], ),
menu::Item::CheckBox(
"Test content",
None,
self.hide_content,
ContextMenuAction::ToggleSomeAction,
)
],
), ),
menu::Item::Divider, menu::Item::Divider,
menu::Item::Button("Quit", None, ContextMenuAction::WindowClose), menu::Item::Button("Quit", None, ContextMenuAction::WindowClose),
@ -144,6 +154,7 @@ impl App {
pub enum ContextMenuAction { pub enum ContextMenuAction {
WindowClose, WindowClose,
ToggleHideContent, ToggleHideContent,
ToggleSomeAction,
WindowNew, WindowNew,
} }
@ -153,6 +164,7 @@ impl menu::Action for ContextMenuAction {
match self { match self {
ContextMenuAction::WindowClose => Message::WindowClose, ContextMenuAction::WindowClose => Message::WindowClose,
ContextMenuAction::ToggleHideContent => Message::ToggleHideContent, ContextMenuAction::ToggleHideContent => Message::ToggleHideContent,
ContextMenuAction::ToggleSomeAction => Message::ToggleSomeAction,
ContextMenuAction::WindowNew => Message::WindowNew, ContextMenuAction::WindowNew => Message::WindowNew,
} }
} }

View file

@ -650,7 +650,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
state.pressed = false; state.pressed = false;
// process close condition // process close condition
if state if state.open && state
.view_cursor .view_cursor
.position() .position()
.unwrap_or_default() .unwrap_or_default()
@ -1428,47 +1428,48 @@ where
state.view_cursor = view_cursor; state.view_cursor = view_cursor;
// * remove invalid menus // * remove invalid menus
if state.open {
let mut prev_bounds = std::iter::once(menu.bar_bounds)
.chain(
if menu.is_overlay {
state.menu_states[..state.menu_states.len().saturating_sub(1)].iter()
} else {
state.menu_states[..menu.depth].iter()
}
.map(|s| s.menu_bounds.children_bounds),
)
.collect::<Vec<_>>();
let mut prev_bounds = std::iter::once(menu.bar_bounds) if menu.is_overlay && menu.close_condition.leave {
.chain( for i in (0..state.menu_states.len()).rev() {
if menu.is_overlay { let mb = &state.menu_states[i].menu_bounds;
state.menu_states[..state.menu_states.len().saturating_sub(1)].iter()
} else { if mb.parent_bounds.contains(overlay_cursor)
state.menu_states[..menu.depth].iter() || menu.is_overlay && mb.children_bounds.contains(overlay_cursor)
|| mb.offset_bounds.contains(overlay_cursor)
|| (mb.check_bounds.contains(overlay_cursor)
&& prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor)))
{
break;
}
prev_bounds.pop();
state.active_root.pop();
state.menu_states.pop();
} }
.map(|s| s.menu_bounds.children_bounds), } else if menu.is_overlay {
) for i in (0..state.menu_states.len()).rev() {
.collect::<Vec<_>>(); let mb = &state.menu_states[i].menu_bounds;
if menu.is_overlay && menu.close_condition.leave { if mb.parent_bounds.contains(overlay_cursor)
for i in (0..state.menu_states.len()).rev() { || mb.children_bounds.contains(overlay_cursor)
let mb = &state.menu_states[i].menu_bounds; || prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))
{
if mb.parent_bounds.contains(overlay_cursor) break;
|| menu.is_overlay && mb.children_bounds.contains(overlay_cursor) }
|| mb.offset_bounds.contains(overlay_cursor) prev_bounds.pop();
|| (mb.check_bounds.contains(overlay_cursor) state.active_root.pop();
&& prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))) state.menu_states.pop();
{
break;
} }
prev_bounds.pop();
state.active_root.pop();
state.menu_states.pop();
}
} else if menu.is_overlay {
for i in (0..state.menu_states.len()).rev() {
let mb = &state.menu_states[i].menu_bounds;
if mb.parent_bounds.contains(overlay_cursor)
|| mb.children_bounds.contains(overlay_cursor)
|| prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))
{
break;
}
prev_bounds.pop();
state.active_root.pop();
state.menu_states.pop();
} }
} }