menu: Use defer to avoid race conditions with click listeners (#1651)
When using `.context_menu` on a parent element, the `window.on_mouse_event` would fire before, for example, the tables `on_mouse_down` so it'd be like this: 1. First right click = no context menu, right_clicked_row is set 2. Second right click = context menu, but with the row from the first click Before: (Look at `Selected row`) https://github.com/user-attachments/assets/5eb89a00-f1ce-4423-b8d7-3ec61b848b83 After: https://github.com/user-attachments/assets/10033ca4-4c2c-47b5-ace5-6ba745fcfee5 --------- Co-authored-by: Jason Lee <huacnlee@gmail.com>
This commit is contained in:
parent
c8652f9010
commit
b6bf2d8c5e
1 changed files with 35 additions and 18 deletions
|
|
@ -29,7 +29,7 @@ impl<E: ParentElement + Styled> ContextMenuExt for E {}
|
|||
pub struct ContextMenu<E: ParentElement + Styled + Sized> {
|
||||
id: ElementId,
|
||||
element: Option<E>,
|
||||
menu: Option<Box<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu>>,
|
||||
menu: Option<Rc<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu>>,
|
||||
// This is not in use, just for style refinement forwarding.
|
||||
_ignore_style: StyleRefinement,
|
||||
anchor: Corner,
|
||||
|
|
@ -53,7 +53,7 @@ impl<E: ParentElement + Styled> ContextMenu<E> {
|
|||
where
|
||||
F: Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||
{
|
||||
self.menu = Some(Box::new(builder));
|
||||
self.menu = Some(Rc::new(builder));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -235,9 +235,8 @@ impl<E: ParentElement + Styled + IntoElement + 'static> Element for ContextMenu<
|
|||
element.paint(window, cx);
|
||||
}
|
||||
|
||||
let Some(builder) = self.menu.take() else {
|
||||
return;
|
||||
};
|
||||
// Take the builder before setting up element state to avoid borrow issues
|
||||
let builder = self.menu.clone();
|
||||
|
||||
self.with_element_state(
|
||||
id.unwrap(),
|
||||
|
|
@ -254,26 +253,44 @@ impl<E: ParentElement + Styled + IntoElement + 'static> Element for ContextMenu<
|
|||
{
|
||||
{
|
||||
let mut shared_state = shared_state.borrow_mut();
|
||||
// Clear any existing menu view to allow immediate replacement
|
||||
// Set the new position and open the menu
|
||||
shared_state.menu_view = None;
|
||||
shared_state._subscription = None;
|
||||
shared_state.position = event.position;
|
||||
shared_state.open = true;
|
||||
}
|
||||
|
||||
let menu = PopupMenu::build(window, cx, |menu, window, cx| {
|
||||
(builder)(menu, window, cx)
|
||||
})
|
||||
.into_element();
|
||||
|
||||
let _subscription = window.subscribe(&menu, cx, {
|
||||
// Use defer to build the menu in the next frame, avoiding race conditions
|
||||
window.defer(cx, {
|
||||
let shared_state = shared_state.clone();
|
||||
move |_, _: &DismissEvent, window, _| {
|
||||
shared_state.borrow_mut().open = false;
|
||||
window.refresh();
|
||||
let builder = builder.clone();
|
||||
move |window, cx| {
|
||||
let menu = PopupMenu::build(window, cx, move |menu, window, cx| {
|
||||
let Some(build) = &builder else {
|
||||
return menu;
|
||||
};
|
||||
build(menu, window, cx)
|
||||
});
|
||||
|
||||
// Set up the subscription for dismiss handling
|
||||
let _subscription = window.subscribe(&menu, cx, {
|
||||
let shared_state = shared_state.clone();
|
||||
move |_, _: &DismissEvent, window, _cx| {
|
||||
shared_state.borrow_mut().open = false;
|
||||
window.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
// Update the shared state with the built menu and subscription
|
||||
{
|
||||
let mut state = shared_state.borrow_mut();
|
||||
state.menu_view = Some(menu.clone());
|
||||
state._subscription = Some(_subscription);
|
||||
window.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
shared_state.borrow_mut().menu_view = Some(menu.clone());
|
||||
shared_state.borrow_mut()._subscription = Some(_subscription);
|
||||
window.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue