popup_menu: Ensure focus is returned after menu is closed (#689)

This commit is contained in:
Floyd Wang 2025-03-05 16:41:43 +08:00
parent 4c96679cf7
commit 9194d7f6bb
3 changed files with 14 additions and 33 deletions

View file

@ -483,8 +483,7 @@ impl Panel for StoryContainer {
}
fn popup_menu(&self, menu: PopupMenu, _window: &Window, _cx: &App) -> PopupMenu {
menu.track_focus(&self.focus_handle)
.menu("Info", Box::new(ShowPanelInfo))
menu.menu("Info", Box::new(ShowPanelInfo))
}
fn toolbar_buttons(&self, _window: &mut Window, _cx: &mut App) -> Option<Vec<Button>> {

View file

@ -111,6 +111,9 @@ impl PopupStory {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let form = Form::new(window, cx);
cx.focus_self(window);
Self {
form,
focus_handle: cx.focus_handle(),

View file

@ -107,7 +107,7 @@ pub struct PopupMenu {
scroll_handle: ScrollHandle,
scroll_state: Rc<Cell<ScrollbarState>>,
action_focus_handle: Option<FocusHandle>,
previous_focus_handle: Option<FocusHandle>,
_subscriptions: Vec<Subscription>,
}
@ -128,7 +128,7 @@ impl PopupMenu {
let menu = Self {
focus_handle,
action_focus_handle: None,
previous_focus_handle: window.focused(cx),
parent_menu: None,
menu_items: Vec::new(),
selected_index: None,
@ -147,12 +147,6 @@ impl PopupMenu {
})
}
/// Bind the focus handle of the menu, when clicked, it will focus back to this handle and then dispatch the action
pub fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
self.action_focus_handle = Some(focus_handle.clone());
self
}
/// Set min width of the popup menu, default is 120px
pub fn min_w(mut self, width: impl Into<Pixels>) -> Self {
self.min_width = width.into();
@ -255,27 +249,7 @@ impl PopupMenu {
}
fn wrap_handler(&self, action: Box<dyn Action>) -> Rc<dyn Fn(&mut Window, &mut App)> {
let action_focus_handle = self.action_focus_handle.clone();
Rc::new(move |window, cx| {
window.activate_window();
// Focus back to the user expected focus handle
// Then the actions listened on that focus handle can be received
//
// For example:
//
// TabPanel
// |- PopupMenu
// |- PanelContent (actions are listened here)
//
// The `PopupMenu` and `PanelContent` are at the same level in the TabPanel
// If the actions are listened on the `PanelContent`,
// it can't receive the actions from the `PopupMenu`, unless we focus on `PanelContent`.
if let Some(handle) = action_focus_handle.as_ref() {
window.focus(&handle);
}
window.dispatch_action(action.boxed_clone(), cx);
})
}
@ -431,13 +405,18 @@ impl PopupMenu {
}
}
fn dismiss(&mut self, _: &Dismiss, _window: &mut Window, cx: &mut Context<Self>) {
fn dismiss(&mut self, _: &Dismiss, window: &mut Window, cx: &mut Context<Self>) {
if self.active_submenu().is_some() {
return;
}
cx.emit(DismissEvent);
// Focus back to the previous focused handle.
if let Some(previous_focus_handle) = self.previous_focus_handle.as_ref() {
window.focus(previous_focus_handle);
}
let Some(parent_menu) = self.parent_menu.clone() else {
return;
};
@ -445,7 +424,7 @@ impl PopupMenu {
// Dismiss parent menu, when this menu is dismissed
_ = parent_menu.update(cx, |view, cx| {
view.hovered_menu_ix = None;
view.dismiss(&Dismiss, _window, cx);
view.dismiss(&Dismiss, window, cx);
});
}
@ -528,7 +507,7 @@ impl Render for PopupMenu {
v_flex()
.id("popup-menu")
.key_context("PopupMenu")
// .track_focus(&self.focus_handle)
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::confirm))