fix: close inner most menu on esc

This commit is contained in:
Hojjat 2026-09-22 11:17:42 -06:00 • committed by Ashley Wulber
parent 03c8f93b29
commit f0eed6e0c3
2 changed files with 112 additions and 21 deletions

View file

@ -6,6 +6,7 @@ use std::sync::Arc;
use super::menu_inner::{
CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
close_innermost,
};
use super::menu_tree::MenuTree;
use crate::Renderer;
@ -626,6 +627,23 @@ where
});
match event {
event::Event::Keyboard(iced_widget::core::keyboard::Event::KeyPressed {
key:
iced_widget::core::keyboard::Key::Named(
iced_widget::core::keyboard::key::Named::Escape,
),
..
}) if open => {
my_state.inner.with_data_mut(|state| {
let handler = self.on_surface_action.as_ref();
close_innermost(state, &mut |id| {
if let Some(handler) = handler {
shell.publish((handler)(crate::surface::Action::DestroyPopup(id)));
}
});
});
shell.capture_event();
}
Mouse(mouse::Event::ButtonPressed(Left))
| Touch(touch::Event::FingerPressed { .. })
if view_cursor.is_over(layout.bounds()) =>

View file

@ -546,7 +546,48 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
})
}
/// Close every menu and, on Wayland, destroy the root popup this menu belongs to.
/// Used when clicking outside a menu.
#[allow(clippy::too_many_lines)]
fn close_all(
&self,
state: &mut super::menu_bar::MenuBarStateInner,
shell: &mut Shell<'_, Message>,
) {
#[cfg(wayland_platform)]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
&& let Some(handler) = self.on_surface_action.as_ref()
{
let mut root = self.window_id;
let mut depth = self.depth;
while let Some(parent) = state.popup_id.iter().find(|(_, v)| **v == root) {
if depth == 0 {
break;
}
root = *parent.0;
depth = depth.saturating_sub(1);
}
shell.publish((handler)(crate::surface::Action::DestroyPopup(root)));
}
state.reset();
}
/// Close the innermost open menu, see [`close_innermost`].
/// Used when pressing `esc`.
fn close(
&self,
state: &mut super::menu_bar::MenuBarStateInner,
shell: &mut Shell<'_, Message>,
) {
let handler = self.on_surface_action.as_ref();
close_innermost(state, &mut |id| {
if let Some(handler) = handler {
shell.publish((handler)(crate::surface::Action::DestroyPopup(id)));
}
});
}
fn update(
&mut self,
event: &event::Event,
@ -601,6 +642,19 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
);
match event {
event::Event::Keyboard(iced_widget::core::keyboard::Event::KeyPressed {
key:
iced_widget::core::keyboard::Key::Named(
iced_widget::core::keyboard::key::Named::Escape,
),
..
}) if !self.is_overlay => {
self.tree
.inner
.with_data_mut(|state| self.close(state, shell));
shell.capture_event();
}
Mouse(WheelScrolled { delta }) => process_scroll_events(
self,
shell,
@ -676,27 +730,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
needs_reset |= self.close_condition.click_outside && !is_inside;
if needs_reset {
#[cfg(wayland_platform)]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
&& let Some(handler) = self.on_surface_action.as_ref()
{
let mut root = self.window_id;
let mut depth = self.depth;
while let Some(parent) =
state.popup_id.iter().find(|(_, v)| **v == root)
{
// parent of root popup is the window, so we stop.
if depth == 0 {
break;
}
root = *parent.0;
depth = depth.saturating_sub(1);
}
shell
.publish((handler)(crate::surface::Action::DestroyPopup(root)));
}
state.reset();
self.close_all(state, shell);
}
}
@ -1782,6 +1816,45 @@ fn get_children_layout<Message>(
(Size::new(width, height), child_positions, child_sizes)
}
/// Close the innermost open menu level
pub(super) fn close_innermost(
state: &mut super::menu_bar::MenuBarStateInner,
destroy: &mut dyn FnMut(window::Id),
) {
let outermost = state
.popup_id
.iter()
.find(|(parent, _)| !state.popup_id.values().any(|id| id == *parent))
.map(|(_, id)| *id);
let innermost = state
.popup_id
.values()
.find(|id| !state.popup_id.contains_key(id))
.copied();
let levels = state.menu_states.len();
if levels <= 1 {
if let Some(id) = outermost {
destroy(id);
}
state.popup_id.clear();
state.reset();
return;
}
state.active_root.truncate(levels - 1);
state.menu_states.truncate(levels - 1);
// Forget the parent's hovered item, so hovering it again reopens the submenu.
if let Some(parent) = state.menu_states.last_mut() {
parent.index = None;
}
if let Some(id) = innermost {
state.popup_id.retain(|_, popup| *popup != id);
destroy(id);
}
}
fn search_bound(
default: usize,
default_left: usize,