Fix detection of topmost region under the dragged item

Max Brunsfeld and Mikayla created

Co-authored-by: Mikayla <mikayla@zed.dev>

Change summary

crates/gpui2/src/elements/div.rs | 47 +++++++++++++++++++--------------
crates/gpui2/src/window.rs       | 21 ++++++++++++++
2 files changed, 47 insertions(+), 21 deletions(-)

Detailed changes

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

@@ -763,6 +763,11 @@ impl InteractiveBounds {
     pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
         self.bounds.contains(point) && cx.was_top_layer(&point, &self.stacking_order)
     }
+
+    pub fn drag_target_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
+        self.bounds.contains(point)
+            && cx.was_top_layer_under_active_drag(&point, &self.stacking_order)
+    }
 }
 
 impl Interactivity {
@@ -888,30 +893,32 @@ impl Interactivity {
         if cx.active_drag.is_some() {
             let drop_listeners = mem::take(&mut self.drop_listeners);
             let interactive_bounds = interactive_bounds.clone();
-            cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
-                if phase == DispatchPhase::Bubble
-                    && interactive_bounds.visibly_contains(&event.position, &cx)
-                {
-                    if let Some(drag_state_type) =
-                        cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
+            if !drop_listeners.is_empty() {
+                cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
+                    if phase == DispatchPhase::Bubble
+                        && interactive_bounds.drag_target_contains(&event.position, cx)
                     {
-                        for (drop_state_type, listener) in &drop_listeners {
-                            if *drop_state_type == drag_state_type {
-                                let drag = cx
-                                    .active_drag
-                                    .take()
-                                    .expect("checked for type drag state type above");
-
-                                listener(drag.view.clone(), cx);
-                                cx.notify();
-                                cx.stop_propagation();
+                        if let Some(drag_state_type) =
+                            cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
+                        {
+                            for (drop_state_type, listener) in &drop_listeners {
+                                if *drop_state_type == drag_state_type {
+                                    let drag = cx
+                                        .active_drag
+                                        .take()
+                                        .expect("checked for type drag state type above");
+
+                                    listener(drag.view.clone(), cx);
+                                    cx.notify();
+                                    cx.stop_propagation();
+                                }
                             }
+                        } else {
+                            cx.active_drag = None;
                         }
-                    } else {
-                        cx.active_drag = None;
                     }
-                }
-            });
+                });
+            }
         }
 
         let click_listeners = mem::take(&mut self.click_listeners);

crates/gpui2/src/window.rs 🔗

@@ -38,6 +38,8 @@ use std::{
 };
 use util::ResultExt;
 
+const ACTIVE_DRAG_Z_INDEX: u32 = 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)]
@@ -907,6 +909,23 @@ impl<'a> WindowContext<'a> {
         false
     }
 
+    pub fn was_top_layer_under_active_drag(
+        &self,
+        point: &Point<Pixels>,
+        level: &StackingOrder,
+    ) -> bool {
+        for (stack, bounds) in self.window.rendered_frame.depth_map.iter() {
+            if stack.starts_with(&[ACTIVE_DRAG_Z_INDEX]) {
+                continue;
+            }
+            if bounds.contains(point) {
+                return level.starts_with(stack) || stack.starts_with(level);
+            }
+        }
+
+        false
+    }
+
     /// Called during painting to get the current stacking order.
     pub fn stacking_order(&self) -> &StackingOrder {
         &self.window.next_frame.z_index_stack
@@ -1238,7 +1257,7 @@ impl<'a> WindowContext<'a> {
         });
 
         if let Some(active_drag) = self.app.active_drag.take() {
-            self.with_z_index(1, |cx| {
+            self.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| {
                 let offset = cx.mouse_position() - active_drag.cursor_offset;
                 let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
                 active_drag.view.draw(offset, available_space, cx);