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 { fn popup_menu(&self, menu: PopupMenu, _window: &Window, _cx: &App) -> PopupMenu {
menu.track_focus(&self.focus_handle) menu.menu("Info", Box::new(ShowPanelInfo))
.menu("Info", Box::new(ShowPanelInfo))
} }
fn toolbar_buttons(&self, _window: &mut Window, _cx: &mut App) -> Option<Vec<Button>> { 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 { fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let form = Form::new(window, cx); let form = Form::new(window, cx);
cx.focus_self(window);
Self { Self {
form, form,
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),

View file

@ -107,7 +107,7 @@ pub struct PopupMenu {
scroll_handle: ScrollHandle, scroll_handle: ScrollHandle,
scroll_state: Rc<Cell<ScrollbarState>>, scroll_state: Rc<Cell<ScrollbarState>>,
action_focus_handle: Option<FocusHandle>, previous_focus_handle: Option<FocusHandle>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@ -128,7 +128,7 @@ impl PopupMenu {
let menu = Self { let menu = Self {
focus_handle, focus_handle,
action_focus_handle: None, previous_focus_handle: window.focused(cx),
parent_menu: None, parent_menu: None,
menu_items: Vec::new(), menu_items: Vec::new(),
selected_index: None, 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 /// Set min width of the popup menu, default is 120px
pub fn min_w(mut self, width: impl Into<Pixels>) -> Self { pub fn min_w(mut self, width: impl Into<Pixels>) -> Self {
self.min_width = width.into(); 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)> { 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| { 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); 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() { if self.active_submenu().is_some() {
return; return;
} }
cx.emit(DismissEvent); 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 { let Some(parent_menu) = self.parent_menu.clone() else {
return; return;
}; };
@ -445,7 +424,7 @@ impl PopupMenu {
// Dismiss parent menu, when this menu is dismissed // Dismiss parent menu, when this menu is dismissed
_ = parent_menu.update(cx, |view, cx| { _ = parent_menu.update(cx, |view, cx| {
view.hovered_menu_ix = None; 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() v_flex()
.id("popup-menu") .id("popup-menu")
.key_context("PopupMenu") .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_next))
.on_action(cx.listener(Self::select_prev)) .on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))