gpui: Fix nested deferred support (#47770)

Jason Lee , Claude Sonnet 4.5 , and Lukas Wirth created

Release Notes:

- N/A

To fix crash when use `deferred` in a `deferred`.

```
thread 'main' (4024343) panicked at /Users/jason/.cargo/git/checkouts/zed-a70e2ad075855582/83ca310/crates/gpui/src/window.rs:2411:9:
assertion `left == right` failed: cannot call defer_draw during deferred drawing
  left: 1
 right: 0
```

### When we need use nested deferred?

#### Case 1

- A Popover (used `deferred`) with a form.
- An Input element in the Popover form.
- Input have ContextMenu that used `deferred`.
- User right click Input to open menu.


<img width="539" height="331" alt="image"
src="https://github.com/user-attachments/assets/98c03b1e-58fd-4863-8bcd-0141252d1c51"
/>

#### Case 2

- A Popover (`deferred`) with some content.
- There have a `info` icon in the content to show some tip by open
another Popover.

### Test by `examples/popover.rs`

<img width="916" height="373" alt="image"
src="https://github.com/user-attachments/assets/7ed6a511-6d60-40fb-837b-ebab8f0cba37"
/>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Lukas Wirth <lukas@zed.dev>

Change summary

crates/gpui/examples/popover.rs |  32 +++++-----
crates/gpui/src/window.rs       | 103 ++++++++++++++++++++++------------
2 files changed, 82 insertions(+), 53 deletions(-)

Detailed changes

crates/gpui/examples/popover.rs 🔗

@@ -56,21 +56,23 @@ impl HelloWorld {
             }))
             .when(self.secondary_open, |this| {
                 this.child(
-                    // GPUI can't support deferred here yet,
-                    // it was inside another deferred element.
-                    anchored()
-                        .anchor(Corner::TopLeft)
-                        .snap_to_window_with_margin(px(8.))
-                        .child(
-                            popover()
-                                .child("This is second level Popover")
-                                .bg(gpui::white())
-                                .border_color(gpui::blue())
-                                .on_mouse_down_out(cx.listener(|this, _, _, cx| {
-                                    this.secondary_open = false;
-                                    cx.notify();
-                                })),
-                        ),
+                    // Now GPUI supports nested deferred!
+                    deferred(
+                        anchored()
+                            .anchor(Corner::TopLeft)
+                            .snap_to_window_with_margin(px(8.))
+                            .child(
+                                popover()
+                                    .child("This is second level Popover with nested deferred!")
+                                    .bg(gpui::white())
+                                    .border_color(gpui::blue())
+                                    .on_mouse_down_out(cx.listener(|this, _, _, cx| {
+                                        this.secondary_open = false;
+                                        cx.notify();
+                                    })),
+                            ),
+                    )
+                    .priority(2),
                 )
             })
     }

crates/gpui/src/window.rs 🔗

@@ -2344,10 +2344,7 @@ impl Window {
         #[cfg(any(feature = "inspector", debug_assertions))]
         let inspector_element = self.prepaint_inspector(_inspector_width, cx);
 
-        let mut sorted_deferred_draws =
-            (0..self.next_frame.deferred_draws.len()).collect::<SmallVec<[_; 8]>>();
-        sorted_deferred_draws.sort_by_key(|ix| self.next_frame.deferred_draws[*ix].priority);
-        self.prepaint_deferred_draws(&sorted_deferred_draws, cx);
+        self.prepaint_deferred_draws(cx);
 
         let mut prompt_element = None;
         let mut active_drag_element = None;
@@ -2376,7 +2373,7 @@ impl Window {
         #[cfg(any(feature = "inspector", debug_assertions))]
         self.paint_inspector(inspector_element, cx);
 
-        self.paint_deferred_draws(&sorted_deferred_draws, cx);
+        self.paint_deferred_draws(cx);
 
         if let Some(mut prompt_element) = prompt_element {
             prompt_element.paint(self, cx);
@@ -2459,25 +2456,40 @@ impl Window {
         None
     }
 
-    fn prepaint_deferred_draws(&mut self, deferred_draw_indices: &[usize], cx: &mut App) {
+    fn prepaint_deferred_draws(&mut self, cx: &mut App) {
         assert_eq!(self.element_id_stack.len(), 0);
 
-        let mut deferred_draws = mem::take(&mut self.next_frame.deferred_draws);
-        for deferred_draw_ix in deferred_draw_indices {
-            let deferred_draw = &mut deferred_draws[*deferred_draw_ix];
-            self.element_id_stack
-                .clone_from(&deferred_draw.element_id_stack);
-            self.text_style_stack
-                .clone_from(&deferred_draw.text_style_stack);
-            self.next_frame
-                .dispatch_tree
-                .set_active_node(deferred_draw.parent_node);
+        let mut completed_draws = Vec::new();
+
+        // Process deferred draws in multiple rounds to support nesting.
+        // Each round processes all current deferred draws, which may produce new ones.
+        let mut depth = 0;
+        loop {
+            // Limit maximum nesting depth to prevent infinite loops.
+            assert!(depth < 10, "Exceeded maximum (10) deferred depth");
+            depth += 1;
+            let deferred_count = self.next_frame.deferred_draws.len();
+            if deferred_count == 0 {
+                break;
+            }
 
-            let prepaint_start = self.prepaint_index();
-            let content_mask = deferred_draw.content_mask.clone();
-            if let Some(element) = deferred_draw.element.as_mut() {
-                self.with_rendered_view(deferred_draw.current_view, |window| {
-                    window.with_content_mask(content_mask, |window| {
+            // Sort by priority for this round
+            let traversal_order = self.deferred_draw_traversal_order();
+            let mut deferred_draws = mem::take(&mut self.next_frame.deferred_draws);
+
+            for deferred_draw_ix in traversal_order {
+                let deferred_draw = &mut deferred_draws[deferred_draw_ix];
+                self.element_id_stack
+                    .clone_from(&deferred_draw.element_id_stack);
+                self.text_style_stack
+                    .clone_from(&deferred_draw.text_style_stack);
+                self.next_frame
+                    .dispatch_tree
+                    .set_active_node(deferred_draw.parent_node);
+
+                let prepaint_start = self.prepaint_index();
+                if let Some(element) = deferred_draw.element.as_mut() {
+                    self.with_rendered_view(deferred_draw.current_view, |window| {
                         window.with_rem_size(Some(deferred_draw.rem_size), |window| {
                             window.with_absolute_element_offset(
                                 deferred_draw.absolute_offset,
@@ -2486,30 +2498,38 @@ impl Window {
                                 },
                             );
                         });
-                    });
-                })
-            } else {
-                self.reuse_prepaint(deferred_draw.prepaint_range.clone());
+                    })
+                } else {
+                    self.reuse_prepaint(deferred_draw.prepaint_range.clone());
+                }
+                let prepaint_end = self.prepaint_index();
+                deferred_draw.prepaint_range = prepaint_start..prepaint_end;
             }
-            let prepaint_end = self.prepaint_index();
-            deferred_draw.prepaint_range = prepaint_start..prepaint_end;
+
+            // Save completed draws and continue with newly added ones
+            completed_draws.append(&mut deferred_draws);
+
+            self.element_id_stack.clear();
+            self.text_style_stack.clear();
         }
-        assert_eq!(
-            self.next_frame.deferred_draws.len(),
-            0,
-            "cannot call defer_draw during deferred drawing"
-        );
-        self.next_frame.deferred_draws = deferred_draws;
-        self.element_id_stack.clear();
-        self.text_style_stack.clear();
+
+        // Restore all completed draws
+        self.next_frame.deferred_draws = completed_draws;
     }
 
-    fn paint_deferred_draws(&mut self, deferred_draw_indices: &[usize], cx: &mut App) {
+    fn paint_deferred_draws(&mut self, cx: &mut App) {
         assert_eq!(self.element_id_stack.len(), 0);
 
+        // Paint all deferred draws in priority order.
+        // Since prepaint has already processed nested deferreds, we just paint them all.
+        if self.next_frame.deferred_draws.len() == 0 {
+            return;
+        }
+
+        let traversal_order = self.deferred_draw_traversal_order();
         let mut deferred_draws = mem::take(&mut self.next_frame.deferred_draws);
-        for deferred_draw_ix in deferred_draw_indices {
-            let mut deferred_draw = &mut deferred_draws[*deferred_draw_ix];
+        for deferred_draw_ix in traversal_order {
+            let mut deferred_draw = &mut deferred_draws[deferred_draw_ix];
             self.element_id_stack
                 .clone_from(&deferred_draw.element_id_stack);
             self.next_frame
@@ -2536,6 +2556,13 @@ impl Window {
         self.element_id_stack.clear();
     }
 
+    fn deferred_draw_traversal_order(&mut self) -> SmallVec<[usize; 8]> {
+        let deferred_count = self.next_frame.deferred_draws.len();
+        let mut sorted_indices = (0..deferred_count).collect::<SmallVec<[_; 8]>>();
+        sorted_indices.sort_by_key(|ix| self.next_frame.deferred_draws[*ix].priority);
+        sorted_indices
+    }
+
     pub(crate) fn prepaint_index(&self) -> PrepaintStateIndex {
         PrepaintStateIndex {
             hitboxes_index: self.next_frame.hitboxes.len(),