Skip to content

Fix tab bar button flickering when opening menus#45098

Merged
as-cii merged 1 commit intomainfrom
fix-tab-bar-button-flickering
Dec 17, 2025
Merged

Fix tab bar button flickering when opening menus#45098
as-cii merged 1 commit intomainfrom
fix-tab-bar-button-flickering

Conversation

@as-cii
Copy link
Member

@as-cii as-cii commented Dec 17, 2025

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 b709996 which fixed the identical issue for the editor's MouseContextMenu.

Release Notes:

  • Fixed flickering when opening context menus.

When opening a PopoverMenu or RightClickMenu, focus was immediately
transferred to the menu. However, since menus are rendered using
deferred(), their focus handles aren't connected in the dispatch tree
until after the deferred draw callback runs.

This caused the pane's has_focus() check to return false momentarily
(because contains_focused() couldn't find the menu in the focus
hierarchy), making the tab bar buttons disappear for a frame or two.

This fix delays the focus transfer by 2 frames using nested
on_next_frame() calls, ensuring the menu is fully rendered and its
focus handle is properly linked before focus moves to it.

This follows the same pattern established in commit b709996 which
fixed the same issue for the editor's MouseContextMenu.

Closes #33018
@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Dec 17, 2025
@as-cii as-cii enabled auto-merge (squash) December 17, 2025 12:37
@as-cii as-cii merged commit 4af26f0 into main Dec 17, 2025
26 checks passed
@as-cii as-cii deleted the fix-tab-bar-button-flickering branch December 17, 2025 12:42
HactarCE pushed a commit that referenced this pull request Dec 17, 2025
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 b709996 which fixed the
identical issue for the editor's `MouseContextMenu`.
rtfeldman pushed a commit that referenced this pull request Jan 5, 2026
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 b709996 which fixed the
identical issue for the editor's `MouseContextMenu`.
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
Closes zed-industries#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 b709996 which fixed the
identical issue for the editor's `MouseContextMenu`.
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
Closes zed-industries#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 b709996 which fixed the
identical issue for the editor's `MouseContextMenu`.
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Feb 15, 2026
Closes zed-industries#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 b709996 which fixed the
identical issue for the editor's `MouseContextMenu`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clicking interface elements causes tab bar buttons to flicker

1 participant