Fix tab bar button flickering when opening menus (#45098)
Antonio Scandurra
created
Closes #33018
### Problem
When opening a `PopoverMenu` or `RightClickMenu`, the pane's tab bar
buttons would flicker (disappear for a couple frames then reappear).
This happened because:
1. The menu is created and `window.focus()` was called immediately
2. However, menus are rendered using `deferred()`, so their focus
handles aren't connected in the dispatch tree until after the deferred
draw callback runs
3. When the pane checks `has_focus()`, it calls `contains_focused()`
which walks up the focus hierarchy — but the menu's focus handle isn't
linked yet
4. `has_focus()` returns false → tab bar buttons disappear
5. Next frame, the menu is rendered and linked → `has_focus()` returns
true → buttons reappear
### Solution
Delay the focus transfer by 2 frames using nested `on_next_frame()`
calls before focusing the menu.
**Why 2 frames instead of 1?**
The frame lifecycle in GPUI runs `next_frame_callbacks` BEFORE `draw()`:
```
on_request_frame:
1. Run next_frame_callbacks
2. window.draw() ← menu rendered here via deferred()
3. Present
```
So:
- **Frame 1**: First `on_next_frame` callback runs, queues second
callback. Then `draw()` renders the menu and connects its focus handle
to the dispatch tree.
- **Frame 2**: Second `on_next_frame` callback runs and focuses the
menu. Now the focus handle is connected (from Frame 1's draw), so
`contains_focused()` returns true.
With only 1 frame, the focus would happen BEFORE `draw()`, when the
menu's focus handle isn't connected yet.
This follows the same pattern established in b709996ec6 which fixed the
identical issue for the editor's `MouseContextMenu`.
@@ -287,7 +287,19 @@ fn show_menu<M: ManagedView>(
window.refresh();
})
.detach();
- window.focus(&new_menu.focus_handle(cx));
+
+ // Since menus are rendered in a deferred fashion, their focus handles are
+ // not linked in the dispatch tree until after the deferred draw callback
+ // runs. We need to wait for that to happen before focusing it, so that
+ // calling `contains_focused` on the parent's focus handle returns `true`
+ // when the menu is focused. This prevents the pane's tab bar buttons from
+ // flickering when opening popover menus.
+ let focus_handle = new_menu.focus_handle(cx);
+ window.on_next_frame(move |window, _cx| {
+ window.on_next_frame(move |window, _cx| {
+ window.focus(&focus_handle);
+ });
+ });
*menu.borrow_mut() = Some(new_menu);
window.refresh();
@@ -259,7 +259,19 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
window.refresh();
})
.detach();
- window.focus(&new_menu.focus_handle(cx));
+
+ // Since menus are rendered in a deferred fashion, their focus handles are
+ // not linked in the dispatch tree until after the deferred draw callback
+ // runs. We need to wait for that to happen before focusing it, so that
+ // calling `contains_focused` on the parent's focus handle returns `true`
+ // when the menu is focused. This prevents the pane's tab bar buttons from
+ // flickering when opening menus.
+ let focus_handle = new_menu.focus_handle(cx);
+ window.on_next_frame(move |window, _cx| {
+ window.on_next_frame(move |window, _cx| {
+ window.focus(&focus_handle);
+ });
+ });
*menu.borrow_mut() = Some(new_menu);
*position.borrow_mut() = if let Some(child_bounds) = child_bounds {
if let Some(attach) = attach {