Make more performance improvements to GPUI 2 (#3664)

Mikayla Maki created

In the 3 charts below, "window draw" has 3 major subroutines. Request
layout, where we walk over the tree and have everything talk to the
layout engine initially. Compute layout, where we have the layout engine
actually do the layout, and then paint, where we use the computed bounds
to populate the scene.


![image_720](https://github.com/zed-industries/zed/assets/1789/d2225389-865f-4c8a-9452-9f611da64dcf)

Things are moving quickly so before/after comparisons are tough. In the
graph above, green bars are from a commit actually pre-dates a merge of
master which increased the complexity of layout. The red bars represent
the state of the world after this PR. Note how we improve the
performance of `paint`.

Improvements:

- Not moving `self` in `Element::paint`. This was moving from the heap
to the stack and imposing a big cost. This is the biggest win in this
PR.
- We got some minor wins by making the stacking order a bigger smallvec
of u8 instead of u32.
- A big win that doesn't show up in this chart is avoiding a double
render of the editor when autoscrolling by never pushing notification
effects or marking the window dirty when notifying during a window draw.

Release Notes:

- N/A

Change summary

crates/collab_ui2/src/face_pile.rs              |   2 
crates/editor2/src/element.rs                   |   9 
crates/gpui2/src/element.rs                     |  24 
crates/gpui2/src/elements/canvas.rs             |  10 
crates/gpui2/src/elements/div.rs                | 575 ++++++++++++------
crates/gpui2/src/elements/img.rs                |   5 
crates/gpui2/src/elements/list.rs               |   4 
crates/gpui2/src/elements/overlay.rs            |   4 
crates/gpui2/src/elements/svg.rs                |   8 
crates/gpui2/src/elements/text.rs               |  15 
crates/gpui2/src/elements/uniform_list.rs       |   6 
crates/gpui2/src/platform/mac/metal_renderer.rs |   6 
crates/gpui2/src/style.rs                       |   2 
crates/gpui2/src/styled.rs                      |   2 
crates/gpui2/src/view.rs                        |  27 
crates/gpui2/src/window.rs                      |  67 +
crates/search2/src/project_search.rs            |  22 
crates/storybook2/src/stories/z_index.rs        |   4 
crates/terminal_view2/src/terminal_element.rs   | 297 +++++----
crates/ui2/src/components/popover_menu.rs       |   6 
crates/ui2/src/components/right_click_menu.rs   |   8 
crates/ui2/src/styles/elevation.rs              |  12 
crates/workspace2/src/modal_layer.rs            |   2 
crates/workspace2/src/pane_group.rs             |   4 
debug.plist                                     |   8 
25 files changed, 671 insertions(+), 458 deletions(-)

Detailed changes

crates/collab_ui2/src/face_pile.rs 🔗

@@ -17,7 +17,7 @@ impl RenderOnce for FacePile {
             let isnt_last = ix < player_count - 1;
 
             div()
-                .z_index((player_count - ix) as u32)
+                .z_index((player_count - ix) as u8)
                 .when(isnt_last, |div| div.neg_mr_1())
                 .child(player)
         });

crates/editor2/src/element.rs 🔗

@@ -933,7 +933,7 @@ impl EditorElement {
                                         cx.stop_propagation();
                                     },
                                 ))
-                                .draw(
+                                .draw_and_update_state(
                                     fold_bounds.origin,
                                     fold_bounds.size,
                                     cx,
@@ -1199,11 +1199,10 @@ impl EditorElement {
                 .child(mouse_context_menu.context_menu.clone())
                 .anchor(AnchorCorner::TopLeft)
                 .snap_to_window();
-            element.draw(
+            element.into_any().draw(
                 gpui::Point::default(),
                 size(AvailableSpace::MinContent, AvailableSpace::MinContent),
                 cx,
-                |_, _| {},
             );
         }
     }
@@ -1496,7 +1495,7 @@ impl EditorElement {
         let scroll_left = scroll_position.x * layout.position_map.em_width;
         let scroll_top = scroll_position.y * layout.position_map.line_height;
 
-        for block in layout.blocks.drain(..) {
+        for mut block in layout.blocks.drain(..) {
             let mut origin = bounds.origin
                 + point(
                     Pixels::ZERO,
@@ -2781,7 +2780,7 @@ impl Element for EditorElement {
     }
 
     fn paint(
-        mut self,
+        &mut self,
         bounds: Bounds<gpui::Pixels>,
         element_state: &mut Self::State,
         cx: &mut gpui::WindowContext,

crates/gpui2/src/element.rs 🔗

@@ -23,7 +23,7 @@ pub trait IntoElement: Sized {
         self.into_element().into_any()
     }
 
-    fn draw<T, R>(
+    fn draw_and_update_state<T, R>(
         self,
         origin: Point<Pixels>,
         available_space: Size<T>,
@@ -92,7 +92,7 @@ pub trait Element: 'static + IntoElement {
         cx: &mut WindowContext,
     ) -> (LayoutId, Self::State);
 
-    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext);
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext);
 
     fn into_any(self) -> AnyElement {
         AnyElement::new(self)
@@ -150,8 +150,8 @@ impl<C: RenderOnce> Element for Component<C> {
         }
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
-        let element = state.rendered_element.take().unwrap();
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        let mut element = state.rendered_element.take().unwrap();
         if let Some(element_id) = element.element_id() {
             cx.with_element_state(element_id, |element_state, cx| {
                 let mut element_state = element_state.unwrap();
@@ -420,7 +420,7 @@ impl AnyElement {
         self.0.layout(cx)
     }
 
-    pub fn paint(mut self, cx: &mut WindowContext) {
+    pub fn paint(&mut self, cx: &mut WindowContext) {
         self.0.paint(cx)
     }
 
@@ -435,7 +435,7 @@ impl AnyElement {
 
     /// Initializes this element and performs layout in the available space, then paints it at the given origin.
     pub fn draw(
-        mut self,
+        &mut self,
         origin: Point<Pixels>,
         available_space: Size<AvailableSpace>,
         cx: &mut WindowContext,
@@ -465,8 +465,8 @@ impl Element for AnyElement {
         (layout_id, ())
     }
 
-    fn paint(self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
-        self.paint(cx);
+    fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
+        self.paint(cx)
     }
 }
 
@@ -508,5 +508,11 @@ impl Element for () {
         (cx.request_layout(&crate::Style::default(), None), ())
     }
 
-    fn paint(self, _bounds: Bounds<Pixels>, _state: &mut Self::State, _cx: &mut WindowContext) {}
+    fn paint(
+        &mut self,
+        _bounds: Bounds<Pixels>,
+        _state: &mut Self::State,
+        _cx: &mut WindowContext,
+    ) {
+    }
 }

crates/gpui2/src/elements/canvas.rs 🔗

@@ -4,13 +4,13 @@ use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled
 
 pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut WindowContext)) -> Canvas {
     Canvas {
-        paint_callback: Box::new(callback),
+        paint_callback: Some(Box::new(callback)),
         style: StyleRefinement::default(),
     }
 }
 
 pub struct Canvas {
-    paint_callback: Box<dyn FnOnce(&Bounds<Pixels>, &mut WindowContext)>,
+    paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut WindowContext)>>,
     style: StyleRefinement,
 }
 
@@ -40,8 +40,10 @@ impl Element for Canvas {
         (layout_id, style)
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut WindowContext) {
-        style.paint(bounds, cx, |cx| (self.paint_callback)(&bounds, cx));
+    fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut WindowContext) {
+        style.paint(bounds, cx, |cx| {
+            (self.paint_callback.take().unwrap())(&bounds, cx)
+        });
     }
 }
 

crates/gpui2/src/elements/div.rs 🔗

@@ -46,6 +46,286 @@ impl<T: 'static> DragMoveEvent<T> {
     }
 }
 
+impl Interactivity {
+    pub fn on_mouse_down(
+        &mut self,
+        button: MouseButton,
+        listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
+    ) {
+        self.mouse_down_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble
+                    && event.button == button
+                    && bounds.visibly_contains(&event.position, cx)
+                {
+                    (listener)(event, cx)
+                }
+            }));
+    }
+
+    pub fn on_any_mouse_down(
+        &mut self,
+        listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
+    ) {
+        self.mouse_down_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
+                    (listener)(event, cx)
+                }
+            }));
+    }
+
+    pub fn on_mouse_up(
+        &mut self,
+        button: MouseButton,
+        listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
+    ) {
+        self.mouse_up_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble
+                    && event.button == button
+                    && bounds.visibly_contains(&event.position, cx)
+                {
+                    (listener)(event, cx)
+                }
+            }));
+    }
+
+    pub fn on_any_mouse_up(
+        &mut self,
+        listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
+    ) {
+        self.mouse_up_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
+                    (listener)(event, cx)
+                }
+            }));
+    }
+
+    pub fn on_mouse_down_out(
+        &mut self,
+        listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
+    ) {
+        self.mouse_down_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
+                if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
+                {
+                    (listener)(event, cx)
+                }
+            }));
+    }
+
+    pub fn on_mouse_up_out(
+        &mut self,
+        button: MouseButton,
+        listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
+    ) {
+        self.mouse_up_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
+                if phase == DispatchPhase::Capture
+                    && event.button == button
+                    && !bounds.visibly_contains(&event.position, cx)
+                {
+                    (listener)(event, cx);
+                }
+            }));
+    }
+
+    pub fn on_mouse_move(
+        &mut self,
+        listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
+    ) {
+        self.mouse_move_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
+                    (listener)(event, cx);
+                }
+            }));
+    }
+
+    pub fn on_drag_move<T>(
+        &mut self,
+        listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
+    ) where
+        T: 'static,
+    {
+        self.mouse_move_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
+                if phase == DispatchPhase::Capture
+                    && bounds.drag_target_contains(&event.position, cx)
+                {
+                    if cx
+                        .active_drag
+                        .as_ref()
+                        .is_some_and(|drag| drag.value.as_ref().type_id() == TypeId::of::<T>())
+                    {
+                        (listener)(
+                            &DragMoveEvent {
+                                event: event.clone(),
+                                bounds: bounds.bounds,
+                                drag: PhantomData,
+                            },
+                            cx,
+                        );
+                    }
+                }
+            }));
+    }
+
+    pub fn on_scroll_wheel(
+        &mut self,
+        listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
+    ) {
+        self.scroll_wheel_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
+                    (listener)(event, cx);
+                }
+            }));
+    }
+
+    pub fn capture_action<A: Action>(
+        &mut self,
+        listener: impl Fn(&A, &mut WindowContext) + 'static,
+    ) {
+        self.action_listeners.push((
+            TypeId::of::<A>(),
+            Box::new(move |action, phase, cx| {
+                let action = action.downcast_ref().unwrap();
+                if phase == DispatchPhase::Capture {
+                    (listener)(action, cx)
+                }
+            }),
+        ));
+    }
+
+    pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) {
+        self.action_listeners.push((
+            TypeId::of::<A>(),
+            Box::new(move |action, phase, cx| {
+                let action = action.downcast_ref().unwrap();
+                if phase == DispatchPhase::Bubble {
+                    (listener)(action, cx)
+                }
+            }),
+        ));
+    }
+
+    pub fn on_boxed_action(
+        &mut self,
+        action: &Box<dyn Action>,
+        listener: impl Fn(&Box<dyn Action>, &mut WindowContext) + 'static,
+    ) {
+        let action = action.boxed_clone();
+        self.action_listeners.push((
+            (*action).type_id(),
+            Box::new(move |_, phase, cx| {
+                if phase == DispatchPhase::Bubble {
+                    (listener)(&action, cx)
+                }
+            }),
+        ));
+    }
+
+    pub fn on_key_down(&mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static) {
+        self.key_down_listeners
+            .push(Box::new(move |event, phase, cx| {
+                if phase == DispatchPhase::Bubble {
+                    (listener)(event, cx)
+                }
+            }));
+    }
+
+    pub fn capture_key_down(
+        &mut self,
+        listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
+    ) {
+        self.key_down_listeners
+            .push(Box::new(move |event, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    listener(event, cx)
+                }
+            }));
+    }
+
+    pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) {
+        self.key_up_listeners
+            .push(Box::new(move |event, phase, cx| {
+                if phase == DispatchPhase::Bubble {
+                    listener(event, cx)
+                }
+            }));
+    }
+
+    pub fn capture_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) {
+        self.key_up_listeners
+            .push(Box::new(move |event, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    listener(event, cx)
+                }
+            }));
+    }
+
+    pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) {
+        self.drop_listeners.push((
+            TypeId::of::<T>(),
+            Box::new(move |dragged_value, cx| {
+                listener(dragged_value.downcast_ref().unwrap(), cx);
+            }),
+        ));
+    }
+
+    pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static)
+    where
+        Self: Sized,
+    {
+        self.click_listeners
+            .push(Box::new(move |event, cx| listener(event, cx)));
+    }
+
+    pub fn on_drag<T, W>(
+        &mut self,
+        value: T,
+        constructor: impl Fn(&T, &mut WindowContext) -> View<W> + 'static,
+    ) where
+        Self: Sized,
+        T: 'static,
+        W: 'static + Render,
+    {
+        debug_assert!(
+            self.drag_listener.is_none(),
+            "calling on_drag more than once on the same element is not supported"
+        );
+        self.drag_listener = Some((
+            Box::new(value),
+            Box::new(move |value, cx| constructor(value.downcast_ref().unwrap(), cx).into()),
+        ));
+    }
+
+    pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static)
+    where
+        Self: Sized,
+    {
+        debug_assert!(
+            self.hover_listener.is_none(),
+            "calling on_hover more than once on the same element is not supported"
+        );
+        self.hover_listener = Some(Box::new(listener));
+    }
+
+    pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static)
+    where
+        Self: Sized,
+    {
+        debug_assert!(
+            self.tooltip_builder.is_none(),
+            "calling tooltip more than once on the same element is not supported"
+        );
+        self.tooltip_builder = Some(Rc::new(build_tooltip));
+    }
+}
+
 pub trait InteractiveElement: Sized {
     fn interactivity(&mut self) -> &mut Interactivity;
 
@@ -103,16 +383,7 @@ pub trait InteractiveElement: Sized {
         button: MouseButton,
         listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity().mouse_down_listeners.push(Box::new(
-            move |event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble
-                    && event.button == button
-                    && bounds.visibly_contains(&event.position, cx)
-                {
-                    (listener)(event, cx)
-                }
-            },
-        ));
+        self.interactivity().on_mouse_down(button, listener);
         self
     }
 
@@ -120,13 +391,7 @@ pub trait InteractiveElement: Sized {
         mut self,
         listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity().mouse_down_listeners.push(Box::new(
-            move |event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
-                    (listener)(event, cx)
-                }
-            },
-        ));
+        self.interactivity().on_any_mouse_down(listener);
         self
     }
 
@@ -135,30 +400,7 @@ pub trait InteractiveElement: Sized {
         button: MouseButton,
         listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity()
-            .mouse_up_listeners
-            .push(Box::new(move |event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble
-                    && event.button == button
-                    && bounds.visibly_contains(&event.position, cx)
-                {
-                    (listener)(event, cx)
-                }
-            }));
-        self
-    }
-
-    fn on_any_mouse_up(
-        mut self,
-        listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
-    ) -> Self {
-        self.interactivity()
-            .mouse_up_listeners
-            .push(Box::new(move |event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
-                    (listener)(event, cx)
-                }
-            }));
+        self.interactivity().on_mouse_up(button, listener);
         self
     }
 
@@ -166,14 +408,7 @@ pub trait InteractiveElement: Sized {
         mut self,
         listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity().mouse_down_listeners.push(Box::new(
-            move |event, bounds, phase, cx| {
-                if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
-                {
-                    (listener)(event, cx)
-                }
-            },
-        ));
+        self.interactivity().on_mouse_down_out(listener);
         self
     }
 
@@ -182,16 +417,7 @@ pub trait InteractiveElement: Sized {
         button: MouseButton,
         listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity()
-            .mouse_up_listeners
-            .push(Box::new(move |event, bounds, phase, cx| {
-                if phase == DispatchPhase::Capture
-                    && event.button == button
-                    && !bounds.visibly_contains(&event.position, cx)
-                {
-                    (listener)(event, cx);
-                }
-            }));
+        self.interactivity().on_mouse_up_out(button, listener);
         self
     }
 
@@ -199,13 +425,7 @@ pub trait InteractiveElement: Sized {
         mut self,
         listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity().mouse_move_listeners.push(Box::new(
-            move |event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
-                    (listener)(event, cx);
-                }
-            },
-        ));
+        self.interactivity().on_mouse_move(listener);
         self
     }
 
@@ -216,28 +436,7 @@ pub trait InteractiveElement: Sized {
     where
         T: 'static,
     {
-        self.interactivity().mouse_move_listeners.push(Box::new(
-            move |event, bounds, phase, cx| {
-                if phase == DispatchPhase::Capture
-                    && bounds.drag_target_contains(&event.position, cx)
-                {
-                    if cx
-                        .active_drag
-                        .as_ref()
-                        .is_some_and(|drag| drag.value.as_ref().type_id() == TypeId::of::<T>())
-                    {
-                        (listener)(
-                            &DragMoveEvent {
-                                event: event.clone(),
-                                bounds: bounds.bounds,
-                                drag: PhantomData,
-                            },
-                            cx,
-                        );
-                    }
-                }
-            },
-        ));
+        self.interactivity().on_drag_move(listener);
         self
     }
 
@@ -245,13 +444,7 @@ pub trait InteractiveElement: Sized {
         mut self,
         listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity().scroll_wheel_listeners.push(Box::new(
-            move |event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
-                    (listener)(event, cx);
-                }
-            },
-        ));
+        self.interactivity().on_scroll_wheel(listener);
         self
     }
 
@@ -260,29 +453,13 @@ pub trait InteractiveElement: Sized {
         mut self,
         listener: impl Fn(&A, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity().action_listeners.push((
-            TypeId::of::<A>(),
-            Box::new(move |action, phase, cx| {
-                let action = action.downcast_ref().unwrap();
-                if phase == DispatchPhase::Capture {
-                    (listener)(action, cx)
-                }
-            }),
-        ));
+        self.interactivity().capture_action(listener);
         self
     }
 
     /// Add a listener for the given action, fires during the bubble event phase
     fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
-        self.interactivity().action_listeners.push((
-            TypeId::of::<A>(),
-            Box::new(move |action, phase, cx| {
-                let action = action.downcast_ref().unwrap();
-                if phase == DispatchPhase::Bubble {
-                    (listener)(action, cx)
-                }
-            }),
-        ));
+        self.interactivity().on_action(listener);
         self
     }
 
@@ -291,15 +468,7 @@ pub trait InteractiveElement: Sized {
         action: &Box<dyn Action>,
         listener: impl Fn(&Box<dyn Action>, &mut WindowContext) + 'static,
     ) -> Self {
-        let action = action.boxed_clone();
-        self.interactivity().action_listeners.push((
-            (*action).type_id(),
-            Box::new(move |_, phase, cx| {
-                if phase == DispatchPhase::Bubble {
-                    (listener)(&action, cx)
-                }
-            }),
-        ));
+        self.interactivity().on_boxed_action(action, listener);
         self
     }
 
@@ -307,13 +476,7 @@ pub trait InteractiveElement: Sized {
         mut self,
         listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity()
-            .key_down_listeners
-            .push(Box::new(move |event, phase, cx| {
-                if phase == DispatchPhase::Bubble {
-                    (listener)(event, cx)
-                }
-            }));
+        self.interactivity().on_key_down(listener);
         self
     }
 
@@ -321,24 +484,12 @@ pub trait InteractiveElement: Sized {
         mut self,
         listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity()
-            .key_down_listeners
-            .push(Box::new(move |event, phase, cx| {
-                if phase == DispatchPhase::Capture {
-                    listener(event, cx)
-                }
-            }));
+        self.interactivity().capture_key_down(listener);
         self
     }
 
     fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self {
-        self.interactivity()
-            .key_up_listeners
-            .push(Box::new(move |event, phase, cx| {
-                if phase == DispatchPhase::Bubble {
-                    listener(event, cx)
-                }
-            }));
+        self.interactivity().on_key_up(listener);
         self
     }
 
@@ -346,13 +497,7 @@ pub trait InteractiveElement: Sized {
         mut self,
         listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity()
-            .key_up_listeners
-            .push(Box::new(move |event, phase, cx| {
-                if phase == DispatchPhase::Capture {
-                    listener(event, cx)
-                }
-            }));
+        self.interactivity().capture_key_up(listener);
         self
     }
 
@@ -379,12 +524,7 @@ pub trait InteractiveElement: Sized {
     }
 
     fn on_drop<T: 'static>(mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) -> Self {
-        self.interactivity().drop_listeners.push((
-            TypeId::of::<T>(),
-            Box::new(move |dragged_value, cx| {
-                listener(dragged_value.downcast_ref().unwrap(), cx);
-            }),
-        ));
+        self.interactivity().on_drop(listener);
         self
     }
 }
@@ -443,9 +583,7 @@ pub trait StatefulInteractiveElement: InteractiveElement {
     where
         Self: Sized,
     {
-        self.interactivity()
-            .click_listeners
-            .push(Box::new(move |event, cx| listener(event, cx)));
+        self.interactivity().on_click(listener);
         self
     }
 
@@ -459,14 +597,7 @@ pub trait StatefulInteractiveElement: InteractiveElement {
         T: 'static,
         W: 'static + Render,
     {
-        debug_assert!(
-            self.interactivity().drag_listener.is_none(),
-            "calling on_drag more than once on the same element is not supported"
-        );
-        self.interactivity().drag_listener = Some((
-            Box::new(value),
-            Box::new(move |value, cx| constructor(value.downcast_ref().unwrap(), cx).into()),
-        ));
+        self.interactivity().on_drag(value, constructor);
         self
     }
 
@@ -474,11 +605,7 @@ pub trait StatefulInteractiveElement: InteractiveElement {
     where
         Self: Sized,
     {
-        debug_assert!(
-            self.interactivity().hover_listener.is_none(),
-            "calling on_hover more than once on the same element is not supported"
-        );
-        self.interactivity().hover_listener = Some(Box::new(listener));
+        self.interactivity().on_hover(listener);
         self
     }
 
@@ -486,11 +613,7 @@ pub trait StatefulInteractiveElement: InteractiveElement {
     where
         Self: Sized,
     {
-        debug_assert!(
-            self.interactivity().tooltip_builder.is_none(),
-            "calling tooltip more than once on the same element is not supported"
-        );
-        self.interactivity().tooltip_builder = Some(Rc::new(build_tooltip));
+        self.interactivity().tooltip(build_tooltip);
         self
     }
 }
@@ -618,7 +741,7 @@ impl Element for Div {
     }
 
     fn paint(
-        self,
+        &mut self,
         bounds: Bounds<Pixels>,
         element_state: &mut Self::State,
         cx: &mut WindowContext,
@@ -669,7 +792,7 @@ impl Element for Div {
                         cx.with_text_style(style.text_style().cloned(), |cx| {
                             cx.with_content_mask(style.overflow_mask(bounds), |cx| {
                                 cx.with_element_offset(scroll_offset, |cx| {
-                                    for child in self.children {
+                                    for child in &mut self.children {
                                         child.paint(cx);
                                     }
                                 })
@@ -701,7 +824,10 @@ pub struct DivState {
 
 impl DivState {
     pub fn is_active(&self) -> bool {
-        self.interactive_state.pending_mouse_down.borrow().is_some()
+        self.interactive_state
+            .pending_mouse_down
+            .as_ref()
+            .map_or(false, |pending| pending.borrow().is_some())
     }
 }
 
@@ -786,7 +912,7 @@ impl Interactivity {
     }
 
     pub fn paint(
-        mut self,
+        &mut self,
         bounds: Bounds<Pixels>,
         content_size: Size<Pixels>,
         element_state: &mut InteractiveElementState,
@@ -805,7 +931,7 @@ impl Interactivity {
             && bounds.contains(&cx.mouse_position())
         {
             const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
-            let element_id = format!("{:?}", self.element_id.unwrap());
+            let element_id = format!("{:?}", self.element_id.as_ref().unwrap());
             let str_len = element_id.len();
 
             let render_debug_text = |cx: &mut WindowContext| {
@@ -918,10 +1044,10 @@ impl Interactivity {
             cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds))
         }
 
-        let interactive_bounds = Rc::new(InteractiveBounds {
+        let interactive_bounds = InteractiveBounds {
             bounds: bounds.intersect(&cx.content_mask().bounds),
             stacking_order: cx.stacking_order().clone(),
-        });
+        };
 
         if let Some(mouse_cursor) = style.mouse_cursor {
             let mouse_position = &cx.mouse_position();
@@ -954,28 +1080,28 @@ impl Interactivity {
         for listener in self.mouse_down_listeners.drain(..) {
             let interactive_bounds = interactive_bounds.clone();
             cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
-                listener(event, &*interactive_bounds, phase, cx);
+                listener(event, &interactive_bounds, phase, cx);
             })
         }
 
         for listener in self.mouse_up_listeners.drain(..) {
             let interactive_bounds = interactive_bounds.clone();
             cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
-                listener(event, &*interactive_bounds, phase, cx);
+                listener(event, &interactive_bounds, phase, cx);
             })
         }
 
         for listener in self.mouse_move_listeners.drain(..) {
             let interactive_bounds = interactive_bounds.clone();
             cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
-                listener(event, &*interactive_bounds, phase, cx);
+                listener(event, &interactive_bounds, phase, cx);
             })
         }
 
         for listener in self.scroll_wheel_listeners.drain(..) {
             let interactive_bounds = interactive_bounds.clone();
             cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
-                listener(event, &*interactive_bounds, phase, cx);
+                listener(event, &interactive_bounds, phase, cx);
             })
         }
 
@@ -1047,11 +1173,17 @@ impl Interactivity {
         let mut drag_listener = mem::take(&mut self.drag_listener);
 
         if !click_listeners.is_empty() || drag_listener.is_some() {
-            let pending_mouse_down = element_state.pending_mouse_down.clone();
+            let pending_mouse_down = element_state
+                .pending_mouse_down
+                .get_or_insert_with(Default::default)
+                .clone();
             let mouse_down = pending_mouse_down.borrow().clone();
             if let Some(mouse_down) = mouse_down {
                 if drag_listener.is_some() {
-                    let active_state = element_state.clicked_state.clone();
+                    let active_state = element_state
+                        .clicked_state
+                        .get_or_insert_with(Default::default)
+                        .clone();
                     let interactive_bounds = interactive_bounds.clone();
 
                     cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
@@ -1112,8 +1244,14 @@ impl Interactivity {
         }
 
         if let Some(hover_listener) = self.hover_listener.take() {
-            let was_hovered = element_state.hover_state.clone();
-            let has_mouse_down = element_state.pending_mouse_down.clone();
+            let was_hovered = element_state
+                .hover_state
+                .get_or_insert_with(Default::default)
+                .clone();
+            let has_mouse_down = element_state
+                .pending_mouse_down
+                .get_or_insert_with(Default::default)
+                .clone();
             let interactive_bounds = interactive_bounds.clone();
 
             cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
@@ -1134,8 +1272,14 @@ impl Interactivity {
         }
 
         if let Some(tooltip_builder) = self.tooltip_builder.take() {
-            let active_tooltip = element_state.active_tooltip.clone();
-            let pending_mouse_down = element_state.pending_mouse_down.clone();
+            let active_tooltip = element_state
+                .active_tooltip
+                .get_or_insert_with(Default::default)
+                .clone();
+            let pending_mouse_down = element_state
+                .pending_mouse_down
+                .get_or_insert_with(Default::default)
+                .clone();
             let interactive_bounds = interactive_bounds.clone();
 
             cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
@@ -1177,19 +1321,30 @@ impl Interactivity {
                 }
             });
 
-            let active_tooltip = element_state.active_tooltip.clone();
+            let active_tooltip = element_state
+                .active_tooltip
+                .get_or_insert_with(Default::default)
+                .clone();
             cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
                 active_tooltip.borrow_mut().take();
             });
 
-            if let Some(active_tooltip) = element_state.active_tooltip.borrow().as_ref() {
+            if let Some(active_tooltip) = element_state
+                .active_tooltip
+                .get_or_insert_with(Default::default)
+                .borrow()
+                .as_ref()
+            {
                 if active_tooltip.tooltip.is_some() {
                     cx.active_tooltip = active_tooltip.tooltip.clone()
                 }
             }
         }
 
-        let active_state = element_state.clicked_state.clone();
+        let active_state = element_state
+            .clicked_state
+            .get_or_insert_with(Default::default)
+            .clone();
         if active_state.borrow().is_clicked() {
             cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
                 if phase == DispatchPhase::Capture {
@@ -1265,23 +1420,26 @@ impl Interactivity {
             .as_ref()
             .map(|scroll_offset| *scroll_offset.borrow());
 
+        let key_down_listeners = mem::take(&mut self.key_down_listeners);
+        let key_up_listeners = mem::take(&mut self.key_up_listeners);
+        let action_listeners = mem::take(&mut self.action_listeners);
         cx.with_key_dispatch(
             self.key_context.clone(),
             element_state.focus_handle.clone(),
             |_, cx| {
-                for listener in self.key_down_listeners.drain(..) {
+                for listener in key_down_listeners {
                     cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
                         listener(event, phase, cx);
                     })
                 }
 
-                for listener in self.key_up_listeners.drain(..) {
+                for listener in key_up_listeners {
                     cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
                         listener(event, phase, cx);
                     })
                 }
 
-                for (action_type, listener) in self.action_listeners {
+                for (action_type, listener) in action_listeners {
                     cx.on_action(action_type, listener)
                 }
 
@@ -1368,7 +1526,10 @@ impl Interactivity {
                 }
             }
 
-            let clicked_state = element_state.clicked_state.borrow();
+            let clicked_state = element_state
+                .clicked_state
+                .get_or_insert_with(Default::default)
+                .borrow();
             if clicked_state.group {
                 if let Some(group) = self.group_active_style.as_ref() {
                     style.refine(&group.style)
@@ -1427,11 +1588,11 @@ impl Default for Interactivity {
 #[derive(Default)]
 pub struct InteractiveElementState {
     pub focus_handle: Option<FocusHandle>,
-    pub clicked_state: Rc<RefCell<ElementClickedState>>,
-    pub hover_state: Rc<RefCell<bool>>,
-    pub pending_mouse_down: Rc<RefCell<Option<MouseDownEvent>>>,
+    pub clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
+    pub hover_state: Option<Rc<RefCell<bool>>>,
+    pub pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
     pub scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
-    pub active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
+    pub active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
 }
 
 pub struct ActiveTooltip {
@@ -1517,7 +1678,7 @@ where
         self.element.layout(state, cx)
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
         self.element.paint(bounds, state, cx)
     }
 }
@@ -1591,7 +1752,7 @@ where
         self.element.layout(state, cx)
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
         self.element.paint(bounds, state, cx)
     }
 }

crates/gpui2/src/elements/img.rs 🔗

@@ -81,11 +81,12 @@ impl Element for Img {
     }
 
     fn paint(
-        self,
+        &mut self,
         bounds: Bounds<Pixels>,
         element_state: &mut Self::State,
         cx: &mut WindowContext,
     ) {
+        let source = self.source.clone();
         self.interactivity.paint(
             bounds,
             bounds.size,
@@ -94,7 +95,7 @@ impl Element for Img {
             |style, _scroll_offset, cx| {
                 let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
                 cx.with_z_index(1, |cx| {
-                    match self.source {
+                    match source {
                         ImageSource::Uri(uri) => {
                             let image_future = cx.image_cache.get(uri.clone());
                             if let Some(data) = image_future

crates/gpui2/src/elements/list.rs 🔗

@@ -257,7 +257,7 @@ impl Element for List {
     }
 
     fn paint(
-        self,
+        &mut self,
         bounds: crate::Bounds<crate::Pixels>,
         _state: &mut Self::State,
         cx: &mut crate::WindowContext,
@@ -385,7 +385,7 @@ impl Element for List {
         // Paint the visible items
         let mut item_origin = bounds.origin;
         item_origin.y -= scroll_top.offset_in_item;
-        for mut item_element in item_elements {
+        for item_element in &mut item_elements {
             let item_height = item_element.measure(available_item_space, cx).height;
             item_element.draw(item_origin, available_item_space, cx);
             item_origin.y += item_height;

crates/gpui2/src/elements/overlay.rs 🔗

@@ -81,7 +81,7 @@ impl Element for Overlay {
     }
 
     fn paint(
-        self,
+        &mut self,
         bounds: crate::Bounds<crate::Pixels>,
         element_state: &mut Self::State,
         cx: &mut WindowContext,
@@ -149,7 +149,7 @@ impl Element for Overlay {
 
         cx.with_element_offset(desired.origin - bounds.origin, |cx| {
             cx.break_content_mask(|cx| {
-                for child in self.children {
+                for child in &mut self.children {
                     child.paint(cx);
                 }
             })

crates/gpui2/src/elements/svg.rs 🔗

@@ -36,8 +36,12 @@ impl Element for Svg {
         })
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, element_state: &mut Self::State, cx: &mut WindowContext)
-    where
+    fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        element_state: &mut Self::State,
+        cx: &mut WindowContext,
+    ) where
         Self: Sized,
     {
         self.interactivity

crates/gpui2/src/elements/text.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
 use anyhow::anyhow;
 use parking_lot::{Mutex, MutexGuard};
 use smallvec::SmallVec;
-use std::{cell::Cell, ops::Range, rc::Rc, sync::Arc};
+use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc};
 use util::ResultExt;
 
 impl Element for &'static str {
@@ -22,7 +22,7 @@ impl Element for &'static str {
         (layout_id, state)
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
         state.paint(bounds, self, cx)
     }
 }
@@ -52,7 +52,7 @@ impl Element for SharedString {
         (layout_id, state)
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
         let text_str: &str = self.as_ref();
         state.paint(bounds, text_str, cx)
     }
@@ -128,7 +128,7 @@ impl Element for StyledText {
         (layout_id, state)
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
         state.paint(bounds, &self.text, cx)
     }
 }
@@ -356,8 +356,8 @@ impl Element for InteractiveText {
         }
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
-        if let Some(click_listener) = self.click_listener {
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        if let Some(click_listener) = self.click_listener.take() {
             if let Some(ix) = state
                 .text_state
                 .index_for_position(bounds, cx.mouse_position())
@@ -374,13 +374,14 @@ impl Element for InteractiveText {
             let text_state = state.text_state.clone();
             let mouse_down = state.mouse_down_index.clone();
             if let Some(mouse_down_index) = mouse_down.get() {
+                let clickable_ranges = mem::take(&mut self.clickable_ranges);
                 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
                     if phase == DispatchPhase::Bubble {
                         if let Some(mouse_up_index) =
                             text_state.index_for_position(bounds, event.position)
                         {
                             click_listener(
-                                &self.clickable_ranges,
+                                &clickable_ranges,
                                 InteractiveTextClickEvent {
                                     mouse_down_index,
                                     mouse_up_index,

crates/gpui2/src/elements/uniform_list.rs 🔗

@@ -155,7 +155,7 @@ impl Element for UniformList {
     }
 
     fn paint(
-        self,
+        &mut self,
         bounds: Bounds<crate::Pixels>,
         element_state: &mut Self::State,
         cx: &mut WindowContext,
@@ -220,11 +220,11 @@ impl Element for UniformList {
                             let visible_range = first_visible_element_ix
                                 ..cmp::min(last_visible_element_ix, self.item_count);
 
-                            let items = (self.render_items)(visible_range.clone(), cx);
+                            let mut items = (self.render_items)(visible_range.clone(), cx);
                             cx.with_z_index(1, |cx| {
                                 let content_mask = ContentMask { bounds };
                                 cx.with_content_mask(Some(content_mask), |cx| {
-                                    for (item, ix) in items.into_iter().zip(visible_range) {
+                                    for (item, ix) in items.iter_mut().zip(visible_range) {
                                         let item_origin = padded_bounds.origin
                                             + point(px(0.), item_height * ix + scroll_offset.y);
                                         let available_space = size(

crates/gpui2/src/platform/mac/metal_renderer.rs 🔗

@@ -187,6 +187,8 @@ impl MetalRenderer {
     }
 
     pub fn draw(&mut self, scene: &Scene) {
+        let start = std::time::Instant::now();
+
         let layer = self.layer.clone();
         let viewport_size = layer.drawable_size();
         let viewport_size: Size<DevicePixels> = size(
@@ -303,6 +305,10 @@ impl MetalRenderer {
 
         command_buffer.commit();
         self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
+
+        let duration_since_start = start.elapsed();
+        println!("renderer draw: {:?}", duration_since_start);
+
         command_buffer.wait_until_completed();
         drawable.present();
     }

crates/gpui2/src/style.rs 🔗

@@ -110,7 +110,7 @@ pub struct Style {
     /// The mouse cursor style shown when the mouse pointer is over an element.
     pub mouse_cursor: Option<CursorStyle>,
 
-    pub z_index: Option<u32>,
+    pub z_index: Option<u8>,
 
     #[cfg(debug_assertions)]
     pub debug: bool,

crates/gpui2/src/styled.rs 🔗

@@ -12,7 +12,7 @@ pub trait Styled: Sized {
 
     gpui2_macros::style_helpers!();
 
-    fn z_index(mut self, z_index: u32) -> Self {
+    fn z_index(mut self, z_index: u8) -> Self {
         self.style().z_index = Some(z_index);
         self
     }

crates/gpui2/src/view.rs 🔗

@@ -91,7 +91,7 @@ impl<V: Render> Element for View<V> {
         (layout_id, Some(element))
     }
 
-    fn paint(self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
+    fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
         element.take().unwrap().paint(cx);
     }
 }
@@ -171,7 +171,7 @@ impl<V> Eq for WeakView<V> {}
 pub struct AnyView {
     model: AnyModel,
     layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
-    paint: fn(&AnyView, AnyElement, &mut WindowContext),
+    paint: fn(&AnyView, &mut AnyElement, &mut WindowContext),
 }
 
 impl AnyView {
@@ -209,9 +209,20 @@ impl AnyView {
         cx: &mut WindowContext,
     ) {
         cx.with_absolute_element_offset(origin, |cx| {
-            let (layout_id, rendered_element) = (self.layout)(self, cx);
+            let start_time = std::time::Instant::now();
+            let (layout_id, mut rendered_element) = (self.layout)(self, cx);
+            let duration = start_time.elapsed();
+            println!("request layout: {:?}", duration);
+
+            let start_time = std::time::Instant::now();
             cx.compute_layout(layout_id, available_space);
-            (self.paint)(self, rendered_element, cx);
+            let duration = start_time.elapsed();
+            println!("compute layout: {:?}", duration);
+
+            let start_time = std::time::Instant::now();
+            (self.paint)(self, &mut rendered_element, cx);
+            let duration = start_time.elapsed();
+            println!("paint: {:?}", duration);
         })
     }
 }
@@ -238,12 +249,12 @@ impl Element for AnyView {
         (layout_id, Some(state))
     }
 
-    fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+    fn paint(&mut self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
         debug_assert!(
             state.is_some(),
             "state is None. Did you include an AnyView twice in the tree?"
         );
-        (self.paint)(&self, state.take().unwrap(), cx)
+        (self.paint)(&self, state.as_mut().unwrap(), cx)
     }
 }
 
@@ -274,7 +285,7 @@ impl IntoElement for AnyView {
 pub struct AnyWeakView {
     model: AnyWeakModel,
     layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
-    paint: fn(&AnyView, AnyElement, &mut WindowContext),
+    paint: fn(&AnyView, &mut AnyElement, &mut WindowContext),
 }
 
 impl AnyWeakView {
@@ -339,7 +350,7 @@ mod any_view {
 
     pub(crate) fn paint<V: 'static + Render>(
         _view: &AnyView,
-        element: AnyElement,
+        element: &mut AnyElement,
         cx: &mut WindowContext,
     ) {
         element.paint(cx);

crates/gpui2/src/window.rs 🔗

@@ -12,7 +12,7 @@ use crate::{
     UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Context as _, Result};
-use collections::HashMap;
+use collections::FxHashMap;
 use derive_more::{Deref, DerefMut};
 use futures::{
     channel::{mpsc, oneshot},
@@ -38,12 +38,24 @@ use std::{
 };
 use util::ResultExt;
 
-const ACTIVE_DRAG_Z_INDEX: u32 = 1;
+const ACTIVE_DRAG_Z_INDEX: u8 = 1;
 
 /// A global stacking order, which is created by stacking successive z-index values.
 /// Each z-index will always be interpreted in the context of its parent z-index.
-#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)]
-pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
+#[derive(Deref, DerefMut, Clone, Debug, Ord, PartialOrd, PartialEq, Eq)]
+pub struct StackingOrder {
+    #[deref]
+    #[deref_mut]
+    z_indices: SmallVec<[u8; 64]>,
+}
+
+impl Default for StackingOrder {
+    fn default() -> Self {
+        StackingOrder {
+            z_indices: SmallVec::new(),
+        }
+    }
+}
 
 /// Represents the two different phases when dispatching events.
 #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
@@ -238,6 +250,7 @@ pub struct Window {
     bounds_observers: SubscriberSet<(), AnyObserver>,
     active: bool,
     pub(crate) dirty: bool,
+    pub(crate) drawing: bool,
     activation_observers: SubscriberSet<(), AnyObserver>,
     pub(crate) last_blur: Option<Option<FocusId>>,
     pub(crate) focus: Option<FocusId>,
@@ -251,8 +264,8 @@ pub(crate) struct ElementStateBox {
 
 // #[derive(Default)]
 pub(crate) struct Frame {
-    pub(crate) element_states: HashMap<GlobalElementId, ElementStateBox>,
-    mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
+    pub(crate) element_states: FxHashMap<GlobalElementId, ElementStateBox>,
+    mouse_listeners: FxHashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
     pub(crate) dispatch_tree: DispatchTree,
     pub(crate) focus_listeners: Vec<AnyFocusListener>,
     pub(crate) scene_builder: SceneBuilder,
@@ -265,8 +278,8 @@ pub(crate) struct Frame {
 impl Frame {
     fn new(dispatch_tree: DispatchTree) -> Self {
         Frame {
-            element_states: HashMap::default(),
-            mouse_listeners: HashMap::default(),
+            element_states: FxHashMap::default(),
+            mouse_listeners: FxHashMap::default(),
             dispatch_tree,
             focus_listeners: Vec::new(),
             scene_builder: SceneBuilder::default(),
@@ -374,6 +387,7 @@ impl Window {
             bounds_observers: SubscriberSet::new(),
             active: false,
             dirty: false,
+            drawing: false,
             activation_observers: SubscriberSet::new(),
             last_blur: None,
             focus: None,
@@ -425,7 +439,9 @@ impl<'a> WindowContext<'a> {
 
     /// Mark the window as dirty, scheduling it to be redrawn on the next frame.
     pub fn notify(&mut self) {
-        self.window.dirty = true;
+        if !self.window.drawing {
+            self.window.dirty = true;
+        }
     }
 
     /// Close this window.
@@ -891,7 +907,7 @@ impl<'a> WindowContext<'a> {
 
     /// Called during painting to invoke the given closure in a new stacking context. The given
     /// z-index is interpreted relative to the previous call to `stack`.
-    pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+    pub fn with_z_index<R>(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R {
         self.window.next_frame.z_index_stack.push(z_index);
         let result = f(self);
         self.window.next_frame.z_index_stack.pop();
@@ -1238,6 +1254,10 @@ impl<'a> WindowContext<'a> {
 
     /// Draw pixels to the display for this window based on the contents of its scene.
     pub(crate) fn draw(&mut self) -> Scene {
+        let t0 = std::time::Instant::now();
+        self.window.dirty = false;
+        self.window.drawing = true;
+
         let window_was_focused = self
             .window
             .focus
@@ -1327,7 +1347,8 @@ impl<'a> WindowContext<'a> {
             self.platform.set_cursor_style(cursor_style);
         }
 
-        self.window.dirty = false;
+        self.window.drawing = false;
+        eprintln!("window draw: {:?}", t0.elapsed());
 
         scene
     }
@@ -2227,7 +2248,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         &mut self.window_cx
     }
 
-    pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+    pub fn with_z_index<R>(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R {
         self.window.next_frame.z_index_stack.push(z_index);
         let result = f(self);
         self.window.next_frame.z_index_stack.pop();
@@ -2364,10 +2385,12 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     }
 
     pub fn notify(&mut self) {
-        self.window_cx.notify();
-        self.window_cx.app.push_effect(Effect::Notify {
-            emitter: self.view.model.entity_id,
-        });
+        if !self.window.drawing {
+            self.window_cx.notify();
+            self.window_cx.app.push_effect(Effect::Notify {
+                emitter: self.view.model.entity_id,
+            });
+        }
     }
 
     pub fn observe_window_bounds(
@@ -2902,12 +2925,12 @@ impl AnyWindowHandle {
     }
 }
 
-#[cfg(any(test, feature = "test-support"))]
-impl From<SmallVec<[u32; 16]>> for StackingOrder {
-    fn from(small_vec: SmallVec<[u32; 16]>) -> Self {
-        StackingOrder(small_vec)
-    }
-}
+// #[cfg(any(test, feature = "test-support"))]
+// impl From<SmallVec<[u32; 16]>> for StackingOrder {
+//     fn from(small_vec: SmallVec<[u32; 16]>) -> Self {
+//         StackingOrder(small_vec)
+//     }
+// }
 
 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
 pub enum ElementId {

crates/search2/src/project_search.rs 🔗

@@ -1524,33 +1524,17 @@ impl Render for ProjectSearchBar {
                                         cx,
                                     )
                                 })
-                                .selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx))
+                                .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx))
                                 .on_click(cx.listener(
                                     |this, _, cx| {
-                                        this.toggle_search_option(
-                                            SearchOptions::CASE_SENSITIVE,
-                                            cx,
-                                        );
+                                        this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
                                     },
                                 )),
                             )
-                            .child(
-                                IconButton::new("project-search-whole-word", Icon::WholeWord)
-                                    .tooltip(|cx| {
-                                        Tooltip::for_action(
-                                            "Toggle whole word",
-                                            &ToggleWholeWord,
-                                            cx,
-                                        )
-                                    })
-                                    .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx))
-                                    .on_click(cx.listener(|this, _, cx| {
-                                        this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
-                                    })),
-                            )
                         }),
                 ),
         );
+
         let mode_column = v_stack().items_start().justify_start().child(
             h_stack()
                 .child(

crates/storybook2/src/stories/z_index.rs 🔗

@@ -78,7 +78,7 @@ impl Styles for Div {}
 
 #[derive(IntoElement)]
 struct ZIndexExample {
-    z_index: u32,
+    z_index: u8,
 }
 
 impl RenderOnce for ZIndexExample {
@@ -170,7 +170,7 @@ impl RenderOnce for ZIndexExample {
 }
 
 impl ZIndexExample {
-    pub fn new(z_index: u32) -> Self {
+    pub fn new(z_index: u8) -> Self {
         Self { z_index }
     }
 }

crates/terminal_view2/src/terminal_element.rs 🔗

@@ -2,8 +2,8 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
 use gpui::{
     black, div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace,
     Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font, FontStyle,
-    FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement,
-    LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
+    FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, Interactivity,
+    IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
     PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled,
     TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext,
 };
@@ -145,11 +145,11 @@ pub struct TerminalElement {
     focused: bool,
     cursor_visible: bool,
     can_navigate_to_selected_word: bool,
-    interactivity: gpui::Interactivity,
+    interactivity: Interactivity,
 }
 
 impl InteractiveElement for TerminalElement {
-    fn interactivity(&mut self) -> &mut gpui::Interactivity {
+    fn interactivity(&mut self) -> &mut Interactivity {
         &mut self.interactivity
     }
 }
@@ -605,141 +605,156 @@ impl TerminalElement {
     }
 
     fn register_mouse_listeners(
-        self,
+        &mut self,
         origin: Point<Pixels>,
         mode: TermMode,
         bounds: Bounds<Pixels>,
         cx: &mut WindowContext,
-    ) -> Self {
+    ) {
         let focus = self.focus.clone();
-        let connection = self.terminal.clone();
-
-        let mut this = self
-            .on_mouse_down(MouseButton::Left, {
-                let connection = connection.clone();
-                let focus = focus.clone();
-                move |e, cx| {
-                    cx.focus(&focus);
-                    //todo!(context menu)
-                    // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel());
-                    connection.update(cx, |terminal, cx| {
-                        terminal.mouse_down(&e, origin);
+        let terminal = self.terminal.clone();
+
+        self.interactivity.on_mouse_down(MouseButton::Left, {
+            let terminal = terminal.clone();
+            let focus = focus.clone();
+            move |e, cx| {
+                cx.focus(&focus);
+                //todo!(context menu)
+                // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel());
+                terminal.update(cx, |terminal, cx| {
+                    terminal.mouse_down(&e, origin);
 
+                    cx.notify();
+                })
+            }
+        });
+        self.interactivity.on_mouse_move({
+            let terminal = terminal.clone();
+            let focus = focus.clone();
+            move |e, cx| {
+                if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() {
+                    terminal.update(cx, |terminal, cx| {
+                        terminal.mouse_drag(e, origin, bounds);
                         cx.notify();
                     })
                 }
-            })
-            .on_mouse_move({
-                let connection = connection.clone();
-                let focus = focus.clone();
-                move |e, cx| {
-                    if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() {
-                        connection.update(cx, |terminal, cx| {
-                            terminal.mouse_drag(e, origin, bounds);
-                            cx.notify();
-                        })
-                    }
-                }
-            })
-            .on_mouse_up(
-                MouseButton::Left,
-                TerminalElement::generic_button_handler(
-                    connection.clone(),
-                    origin,
-                    focus.clone(),
-                    move |terminal, origin, e, cx| {
-                        terminal.mouse_up(&e, origin, cx);
-                    },
-                ),
-            )
-            .on_click({
-                let connection = connection.clone();
-                move |e, cx| {
-                    if e.down.button == MouseButton::Right {
-                        let mouse_mode = connection.update(cx, |terminal, _cx| {
-                            terminal.mouse_mode(e.down.modifiers.shift)
-                        });
-
-                        if !mouse_mode {
-                            //todo!(context menu)
-                            // view.deploy_context_menu(e.position, cx);
-                        }
-                    }
-                }
-            })
-            .on_mouse_move({
-                let connection = connection.clone();
-                let focus = focus.clone();
-                move |e, cx| {
-                    if focus.is_focused(cx) {
-                        connection.update(cx, |terminal, cx| {
-                            terminal.mouse_move(&e, origin);
-                            cx.notify();
-                        })
+            }
+        });
+        self.interactivity.on_mouse_up(
+            MouseButton::Left,
+            TerminalElement::generic_button_handler(
+                terminal.clone(),
+                origin,
+                focus.clone(),
+                move |terminal, origin, e, cx| {
+                    terminal.mouse_up(&e, origin, cx);
+                },
+            ),
+        );
+        self.interactivity.on_click({
+            let terminal = terminal.clone();
+            move |e, cx| {
+                if e.down.button == MouseButton::Right {
+                    let mouse_mode = terminal.update(cx, |terminal, _cx| {
+                        terminal.mouse_mode(e.down.modifiers.shift)
+                    });
+
+                    if !mouse_mode {
+                        //todo!(context menu)
+                        // view.deploy_context_menu(e.position, cx);
                     }
                 }
-            })
-            .on_scroll_wheel({
-                let connection = connection.clone();
-                move |e, cx| {
-                    connection.update(cx, |terminal, cx| {
-                        terminal.scroll_wheel(e, origin);
+            }
+        });
+
+        self.interactivity.on_mouse_move({
+            let terminal = terminal.clone();
+            let focus = focus.clone();
+            move |e, cx| {
+                if focus.is_focused(cx) {
+                    terminal.update(cx, |terminal, cx| {
+                        terminal.mouse_move(&e, origin);
                         cx.notify();
                     })
                 }
-            });
+            }
+        });
+        self.interactivity.on_scroll_wheel({
+            let terminal = terminal.clone();
+            move |e, cx| {
+                terminal.update(cx, |terminal, cx| {
+                    terminal.scroll_wheel(e, origin);
+                    cx.notify();
+                })
+            }
+        });
+
+        self.interactivity.on_drop::<ExternalPaths>({
+            let focus = focus.clone();
+            let terminal = terminal.clone();
+            move |external_paths, cx| {
+                cx.focus(&focus);
+                let mut new_text = external_paths
+                    .paths()
+                    .iter()
+                    .map(|path| format!(" {path:?}"))
+                    .join("");
+                new_text.push(' ');
+                terminal.update(cx, |terminal, _| {
+                    // todo!() long paths are not displayed properly albeit the text is there
+                    terminal.paste(&new_text);
+                });
+            }
+        });
 
         // Mouse mode handlers:
         // All mouse modes need the extra click handlers
         if mode.intersects(TermMode::MOUSE_MODE) {
-            this = this
-                .on_mouse_down(
-                    MouseButton::Right,
-                    TerminalElement::generic_button_handler(
-                        connection.clone(),
-                        origin,
-                        focus.clone(),
-                        move |terminal, origin, e, _cx| {
-                            terminal.mouse_down(&e, origin);
-                        },
-                    ),
-                )
-                .on_mouse_down(
-                    MouseButton::Middle,
-                    TerminalElement::generic_button_handler(
-                        connection.clone(),
-                        origin,
-                        focus.clone(),
-                        move |terminal, origin, e, _cx| {
-                            terminal.mouse_down(&e, origin);
-                        },
-                    ),
-                )
-                .on_mouse_up(
-                    MouseButton::Right,
-                    TerminalElement::generic_button_handler(
-                        connection.clone(),
-                        origin,
-                        focus.clone(),
-                        move |terminal, origin, e, cx| {
-                            terminal.mouse_up(&e, origin, cx);
-                        },
-                    ),
-                )
-                .on_mouse_up(
-                    MouseButton::Middle,
-                    TerminalElement::generic_button_handler(
-                        connection,
-                        origin,
-                        focus,
-                        move |terminal, origin, e, cx| {
-                            terminal.mouse_up(&e, origin, cx);
-                        },
-                    ),
-                )
+            self.interactivity.on_mouse_down(
+                MouseButton::Right,
+                TerminalElement::generic_button_handler(
+                    terminal.clone(),
+                    origin,
+                    focus.clone(),
+                    move |terminal, origin, e, _cx| {
+                        terminal.mouse_down(&e, origin);
+                    },
+                ),
+            );
+            self.interactivity.on_mouse_down(
+                MouseButton::Middle,
+                TerminalElement::generic_button_handler(
+                    terminal.clone(),
+                    origin,
+                    focus.clone(),
+                    move |terminal, origin, e, _cx| {
+                        terminal.mouse_down(&e, origin);
+                    },
+                ),
+            );
+            self.interactivity.on_mouse_up(
+                MouseButton::Right,
+                TerminalElement::generic_button_handler(
+                    terminal.clone(),
+                    origin,
+                    focus.clone(),
+                    move |terminal, origin, e, cx| {
+                        terminal.mouse_up(&e, origin, cx);
+                    },
+                ),
+            );
+            self.interactivity.on_mouse_up(
+                MouseButton::Middle,
+                TerminalElement::generic_button_handler(
+                    terminal,
+                    origin,
+                    focus,
+                    move |terminal, origin, e, cx| {
+                        terminal.mouse_up(&e, origin, cx);
+                    },
+                ),
+            );
         }
-
-        this
     }
 }
 
@@ -764,7 +779,12 @@ impl Element for TerminalElement {
         (layout_id, interactive_state)
     }
 
-    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext<'_>) {
+    fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        state: &mut Self::State,
+        cx: &mut WindowContext<'_>,
+    ) {
         let mut layout = self.compute_layout(bounds, cx);
 
         let theme = cx.theme();
@@ -783,32 +803,19 @@ impl Element for TerminalElement {
 
         let terminal_focus_handle = self.focus.clone();
         let terminal_handle = self.terminal.clone();
-        let mut this: TerminalElement = self
-            .register_mouse_listeners(origin, layout.mode, bounds, cx)
-            .drag_over::<ExternalPaths>(|style| {
-                // todo!() why does not it work? z-index of elements?
-                style.bg(cx.theme().colors().ghost_element_hover)
-            })
-            .on_drop::<ExternalPaths>(move |external_paths, cx| {
-                cx.focus(&terminal_focus_handle);
-                let mut new_text = external_paths
-                    .paths()
-                    .iter()
-                    .map(|path| format!(" {path:?}"))
-                    .join("");
-                new_text.push(' ');
-                terminal_handle.update(cx, |terminal, _| {
-                    // todo!() long paths are not displayed properly albeit the text is there
-                    terminal.paste(&new_text);
-                });
-            });
+        self.register_mouse_listeners(origin, layout.mode, bounds, cx);
 
-        let interactivity = mem::take(&mut this.interactivity);
+        // todo!(change this to work in terms of on_drag_move or some such)
+        // .drag_over::<ExternalPaths>(|style| {
+        //     // todo!() why does not it work? z-index of elements?
+        //     style.bg(cx.theme().colors().ghost_element_hover)
+        // })
 
+        let mut interactivity = mem::take(&mut self.interactivity);
         interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| {
-            cx.handle_input(&this.focus, terminal_input_handler);
+            cx.handle_input(&self.focus, terminal_input_handler);
 
-            this.register_key_listeners(cx);
+            self.register_key_listeners(cx);
 
             for rect in &layout.rects {
                 rect.paint(origin, &layout, cx);
@@ -839,7 +846,7 @@ impl Element for TerminalElement {
                 }
             });
 
-            if this.cursor_visible {
+            if self.cursor_visible {
                 cx.with_z_index(3, |cx| {
                     if let Some(cursor) = &layout.cursor {
                         cursor.paint(origin, cx);
@@ -847,7 +854,7 @@ impl Element for TerminalElement {
                 });
             }
 
-            if let Some(element) = layout.hyperlink_tooltip.take() {
+            if let Some(mut element) = layout.hyperlink_tooltip.take() {
                 let width: AvailableSpace = bounds.size.width.into();
                 let height: AvailableSpace = bounds.size.height.into();
                 element.draw(origin, Size { width, height }, cx)

crates/ui2/src/components/popover_menu.rs 🔗

@@ -182,12 +182,12 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
     }
 
     fn paint(
-        self,
+        &mut self,
         _: Bounds<gpui::Pixels>,
         element_state: &mut Self::State,
         cx: &mut WindowContext,
     ) {
-        if let Some(child) = element_state.child_element.take() {
+        if let Some(mut child) = element_state.child_element.take() {
             child.paint(cx);
         }
 
@@ -195,7 +195,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
             element_state.child_bounds = Some(cx.layout_bounds(child_layout_id));
         }
 
-        if let Some(menu) = element_state.menu_element.take() {
+        if let Some(mut menu) = element_state.menu_element.take() {
             menu.paint(cx);
 
             if let Some(child_bounds) = element_state.child_bounds {

crates/ui2/src/components/right_click_menu.rs 🔗

@@ -112,21 +112,21 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
     }
 
     fn paint(
-        self,
+        &mut self,
         bounds: Bounds<gpui::Pixels>,
         element_state: &mut Self::State,
         cx: &mut WindowContext,
     ) {
-        if let Some(child) = element_state.child_element.take() {
+        if let Some(mut child) = element_state.child_element.take() {
             child.paint(cx);
         }
 
-        if let Some(menu) = element_state.menu_element.take() {
+        if let Some(mut menu) = element_state.menu_element.take() {
             menu.paint(cx);
             return;
         }
 
-        let Some(builder) = self.menu_builder else {
+        let Some(builder) = self.menu_builder.take() else {
             return;
         };
         let menu = element_state.menu.clone();

crates/ui2/src/styles/elevation.rs 🔗

@@ -20,14 +20,14 @@ pub enum ElevationIndex {
 }
 
 impl ElevationIndex {
-    pub fn z_index(self) -> u32 {
+    pub fn z_index(self) -> u8 {
         match self {
             ElevationIndex::Background => 0,
-            ElevationIndex::Surface => 100,
-            ElevationIndex::ElevatedSurface => 200,
-            ElevationIndex::Wash => 250,
-            ElevationIndex::ModalSurface => 300,
-            ElevationIndex::DraggedElement => 900,
+            ElevationIndex::Surface => 42,
+            ElevationIndex::ElevatedSurface => 84,
+            ElevationIndex::Wash => 126,
+            ElevationIndex::ModalSurface => 168,
+            ElevationIndex::DraggedElement => 210,
         }
     }
 

crates/workspace2/src/pane_group.rs 🔗

@@ -890,7 +890,7 @@ mod element {
         }
 
         fn paint(
-            self,
+            &mut self,
             bounds: gpui::Bounds<ui::prelude::Pixels>,
             state: &mut Self::State,
             cx: &mut ui::prelude::WindowContext,
@@ -906,7 +906,7 @@ mod element {
             let mut bounding_boxes = self.bounding_boxes.lock();
             bounding_boxes.clear();
 
-            for (ix, child) in self.children.into_iter().enumerate() {
+            for (ix, child) in self.children.iter_mut().enumerate() {
                 //todo!(active_pane_magnification)
                 // If usign active pane magnification, need to switch to using
                 // 1 for all non-active panes, and then the magnification for the

debug.plist 🔗

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+    <dict>
+        <key>com.apple.security.get-task-allow</key>
+        <true/>
+    </dict>
+</plist>