Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion iced
18 changes: 18 additions & 0 deletions src/widget/menu/menu_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()) =>
Expand Down
115 changes: 94 additions & 21 deletions src/widget/menu/menu_inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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,
Expand Down
17 changes: 15 additions & 2 deletions src/widget/menu/menu_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,23 @@ fn entry_tree<
action,
} = entry;
let spacing = crate::theme::spacing();
// Several bindings may map to one action
// choose based on these rules:
// - the fewest modifiers win (Delete vs Ctrl+D)
// - if equal then character key over a named one (Ctrl+C over Ctrl+Insert)
// - if equal then `Ord` to stay consistent since hashmap is arbitrary
let rank = |k: &KeyBind| {
(
k.modifiers.len(),
matches!(k.key, iced_core::keyboard::Key::Named(_)),
)
};
let key = key_binds
.iter()
.find(|(_, a)| **a == action)
.map_or_else(String::new, |(k, _)| k.to_string());
.filter(|(_, a)| **a == action)
.map(|(k, _)| k)
.min_by(|a, b| rank(a).cmp(&rank(b)).then_with(|| a.cmp(b)))
.map_or_else(String::new, ToString::to_string);

let mut items: Vec<crate::Element<'static, Message>> = Vec::with_capacity(7);

Expand Down
Loading