Add on_drop listeners

Nathan Sobo created

Change summary

crates/gpui2/src/elements/div.rs |   2 
crates/gpui2/src/elements/img.rs |   4 
crates/gpui2/src/elements/svg.rs |   4 
crates/gpui2/src/interactive.rs  | 141 ++++++++++++++++++++++-----------
crates/gpui2/src/window.rs       |  26 ++---
crates/ui2/src/components/tab.rs |   5 
6 files changed, 112 insertions(+), 70 deletions(-)

Detailed changes

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

@@ -354,7 +354,7 @@ where
     F: ElementFocus<V>,
     V: 'static + Send + Sync,
 {
-    fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<V> {
+    fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
         self.interaction.as_stateless_mut()
     }
 }

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

@@ -156,8 +156,8 @@ where
     I: ElementInteraction<V>,
     F: ElementFocus<V>,
 {
-    fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<V> {
-        self.base.stateless_interactivity()
+    fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
+        self.base.stateless_interaction()
     }
 }
 

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

@@ -130,8 +130,8 @@ where
     I: ElementInteraction<V>,
     F: ElementFocus<V>,
 {
-    fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<V> {
-        self.base.stateless_interactivity()
+    fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
+        self.base.stateless_interaction()
     }
 }
 

crates/gpui2/src/interactive.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    point, px, view, Action, AnyDrag, AppContext, BorrowWindow, Bounds, DispatchContext,
+    point, px, view, Action, AnyBox, AnyDrag, AppContext, BorrowWindow, Bounds, DispatchContext,
     DispatchPhase, Element, ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow,
     Pixels, Point, SharedString, Size, Style, StyleRefinement, ViewContext,
 };
@@ -19,13 +19,13 @@ use std::{
 const DRAG_THRESHOLD: f64 = 2.;
 
 pub trait StatelessInteractive: Element {
-    fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<Self::ViewState>;
+    fn stateless_interaction(&mut self) -> &mut StatelessInteraction<Self::ViewState>;
 
     fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
     where
         Self: Sized,
     {
-        self.stateless_interactivity().hover_style = f(StyleRefinement::default());
+        self.stateless_interaction().hover_style = f(StyleRefinement::default());
         self
     }
 
@@ -37,7 +37,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity().group_hover_style = Some(GroupStyle {
+        self.stateless_interaction().group_hover_style = Some(GroupStyle {
             group: group_name.into(),
             style: f(StyleRefinement::default()),
         });
@@ -55,7 +55,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity()
+        self.stateless_interaction()
             .mouse_down_listeners
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
@@ -79,7 +79,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity()
+        self.stateless_interaction()
             .mouse_up_listeners
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
@@ -103,7 +103,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity()
+        self.stateless_interaction()
             .mouse_down_listeners
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
@@ -127,7 +127,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity()
+        self.stateless_interaction()
             .mouse_up_listeners
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
@@ -150,7 +150,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity()
+        self.stateless_interaction()
             .mouse_move_listeners
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
@@ -170,7 +170,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity()
+        self.stateless_interaction()
             .scroll_wheel_listeners
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
@@ -186,7 +186,7 @@ pub trait StatelessInteractive: Element {
         C: TryInto<DispatchContext>,
         C::Error: Debug,
     {
-        self.stateless_interactivity().dispatch_context =
+        self.stateless_interaction().dispatch_context =
             context.try_into().expect("invalid dispatch context");
         self
     }
@@ -201,7 +201,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity().key_listeners.push((
+        self.stateless_interaction().key_listeners.push((
             TypeId::of::<A>(),
             Arc::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
@@ -226,7 +226,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity().key_listeners.push((
+        self.stateless_interaction().key_listeners.push((
             TypeId::of::<KeyDownEvent>(),
             Arc::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
@@ -247,7 +247,7 @@ pub trait StatelessInteractive: Element {
     where
         Self: Sized,
     {
-        self.stateless_interactivity().key_listeners.push((
+        self.stateless_interaction().key_listeners.push((
             TypeId::of::<KeyUpEvent>(),
             Arc::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
@@ -257,6 +257,53 @@ pub trait StatelessInteractive: Element {
         ));
         self
     }
+
+    fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+    where
+        Self: Sized,
+    {
+        self.stateless_interaction()
+            .drag_over_styles
+            .push((TypeId::of::<S>(), f(StyleRefinement::default())));
+        self
+    }
+
+    fn group_drag_over<S: 'static>(
+        mut self,
+        group_name: impl Into<SharedString>,
+        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.stateless_interaction().group_drag_over_styles.push((
+            TypeId::of::<S>(),
+            GroupStyle {
+                group: group_name.into(),
+                style: f(StyleRefinement::default()),
+            },
+        ));
+        self
+    }
+
+    fn on_drop<S: 'static>(
+        mut self,
+        listener: impl Fn(&mut Self::ViewState, S, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.stateless_interaction().drop_listeners.push((
+            TypeId::of::<S>(),
+            Arc::new(move |view, drag_state, cx| {
+                listener(view, *drag_state.downcast().unwrap(), cx);
+            }),
+        ));
+        self
+    }
 }
 
 pub trait StatefulInteractive: StatelessInteractive {
@@ -338,34 +385,6 @@ pub trait StatefulInteractive: StatelessInteractive {
             }));
         self
     }
-
-    fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateful_interaction()
-            .drag_over_styles
-            .push((TypeId::of::<S>(), f(StyleRefinement::default())));
-        self
-    }
-
-    fn group_drag_over<S: 'static>(
-        mut self,
-        group_name: impl Into<SharedString>,
-        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateful_interaction().group_drag_over_styles.push((
-            TypeId::of::<S>(),
-            GroupStyle {
-                group: group_name.into(),
-                style: f(StyleRefinement::default()),
-            },
-        ));
-        self
-    }
 }
 
 pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
@@ -523,6 +542,29 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
             });
         }
 
+        if cx.active_drag.is_some() {
+            let drop_listeners = stateless.drop_listeners.clone();
+            cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    if let Some(drag_state_type) =
+                        cx.active_drag.as_ref().map(|drag| drag.state_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(view, drag.state, cx);
+                                cx.notify();
+                                cx.stop_propagation();
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
         if let Some(stateful) = self.as_stateful() {
             let click_listeners = stateful.click_listeners.clone();
             let drag_listener = stateful.drag_listener.clone();
@@ -544,10 +586,11 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
                                 && (event.position - mouse_down.position).magnitude()
                                     > DRAG_THRESHOLD
                             {
-                                let cursor_offset = event.position - bounds.origin;
-                                let any_drag = drag_listener(view_state, cursor_offset, cx);
                                 *active_state.lock() = ActiveState::default();
-                                cx.start_drag(any_drag);
+                                let cursor_offset = event.position - bounds.origin;
+                                let drag = drag_listener(view_state, cursor_offset, cx);
+                                cx.active_drag = Some(drag);
+                                cx.notify();
                                 cx.stop_propagation();
                             }
                         });
@@ -564,10 +607,6 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
                                 listener(view_state, &mouse_click, cx);
                             }
                         }
-
-                        if cx.active_drag.is_some() {
-                            cx.end_drag();
-                        }
                         *pending_mouse_down.lock() = None;
                     });
                 } else {
@@ -690,6 +729,8 @@ where
     }
 }
 
+type DropListener<V> = dyn Fn(&mut V, AnyBox, &mut ViewContext<V>) + Send + Sync;
+
 pub struct StatelessInteraction<V> {
     pub dispatch_context: DispatchContext,
     pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
@@ -701,6 +742,7 @@ pub struct StatelessInteraction<V> {
     pub group_hover_style: Option<GroupStyle>,
     drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
     group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
+    drop_listeners: SmallVec<[(TypeId, Arc<DropListener<V>>); 2]>,
 }
 
 impl<V> StatelessInteraction<V>
@@ -793,6 +835,7 @@ impl<V> Default for StatelessInteraction<V> {
             group_hover_style: None,
             drag_over_styles: SmallVec::new(),
             group_drag_over_styles: SmallVec::new(),
+            drop_listeners: SmallVec::new(),
         }
     }
 }

crates/gpui2/src/window.rs 🔗

@@ -3,11 +3,11 @@ use crate::{
     BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext,
     DisplayId, Edges, Effect, Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId,
     GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher,
-    Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path,
-    Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference,
-    RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow,
-    SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
-    WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
+    Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent,
+    MouseUpEvent, Path, Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite,
+    Quad, Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
+    SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task,
+    Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use collections::HashMap;
@@ -925,6 +925,12 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                     }
                 }
 
+                if self.app.propagate_event
+                    && any_mouse_event.downcast_ref::<MouseUpEvent>().is_some()
+                {
+                    self.active_drag = None;
+                }
+
                 // Just in case any handlers added new handlers, which is weird, but possible.
                 handlers.extend(
                     self.window
@@ -1626,16 +1632,6 @@ impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
             })
         });
     }
-
-    pub(crate) fn start_drag(&mut self, drag: AnyDrag) {
-        self.app.active_drag = Some(drag);
-        self.notify();
-    }
-
-    pub(crate) fn end_drag(&mut self) {
-        self.app.active_drag = None;
-        self.notify();
-    }
 }
 
 impl<'a, 'w, V: EventEmitter + Send + Sync + 'static> ViewContext<'a, 'w, V> {

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

@@ -17,7 +17,7 @@ pub struct Tab<S: 'static + Send + Sync + Clone> {
     close_side: IconSide,
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 struct TabDragState {
     title: String,
 }
@@ -126,6 +126,9 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
                 Drag::new(drag_state.clone(), |view, cx| div().w_8().h_4().bg(red()))
             })
             .drag_over::<TabDragState>(|d| d.bg(black()))
+            .on_drop(|_view, state: TabDragState, cx| {
+                dbg!(state);
+            })
             .px_2()
             .py_0p5()
             .flex()