Prevent drag-dropping non-terminal item into terminal pane

Julia created

Change summary

crates/gpui2/src/elements/div.rs            | 74 ++++++++++++++++------
crates/terminal_view2/src/terminal_panel.rs |  9 ++
crates/workspace2/src/pane.rs               | 17 ++++
crates/workspace2/src/workspace2.rs         |  2 
4 files changed, 79 insertions(+), 23 deletions(-)

Detailed changes

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

@@ -297,6 +297,10 @@ impl Interactivity {
         ));
     }
 
+    pub fn can_drop(&mut self, predicate: impl Fn(&dyn Any, &mut WindowContext) -> bool + 'static) {
+        self.can_drop_predicate = Some(Box::new(predicate));
+    }
+
     pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static)
     where
         Self: Sized,
@@ -569,6 +573,14 @@ pub trait InteractiveElement: Sized {
         self
     }
 
+    fn can_drop(
+        mut self,
+        predicate: impl Fn(&dyn Any, &mut WindowContext) -> bool + 'static,
+    ) -> Self {
+        self.interactivity().can_drop(predicate);
+        self
+    }
+
     fn block_mouse(mut self) -> Self {
         self.interactivity().block_mouse();
         self
@@ -699,6 +711,8 @@ pub type DragListener = Box<dyn Fn(&dyn Any, &mut WindowContext) -> AnyView + 's
 
 type DropListener = Box<dyn Fn(&dyn Any, &mut WindowContext) + 'static>;
 
+type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut WindowContext) -> bool + 'static>;
+
 pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
 
 pub type KeyDownListener = Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
@@ -887,6 +901,7 @@ pub struct Interactivity {
     pub key_up_listeners: Vec<KeyUpListener>,
     pub action_listeners: Vec<(TypeId, ActionListener)>,
     pub drop_listeners: Vec<(TypeId, DropListener)>,
+    pub can_drop_predicate: Option<CanDropPredicate>,
     pub click_listeners: Vec<ClickListener>,
     pub drag_listener: Option<(Box<dyn Any>, DragListener)>,
     pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
@@ -1198,6 +1213,7 @@ impl Interactivity {
                         let mut drag_listener = mem::take(&mut self.drag_listener);
                         let drop_listeners = mem::take(&mut self.drop_listeners);
                         let click_listeners = mem::take(&mut self.click_listeners);
+                        let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
 
                         if !drop_listeners.is_empty() {
                             cx.on_mouse_event({
@@ -1215,9 +1231,17 @@ impl Interactivity {
                                                         "checked for type drag state type above",
                                                     );
 
-                                                    listener(drag.value.as_ref(), cx);
-                                                    cx.notify();
-                                                    cx.stop_propagation();
+                                                    let mut can_drop = true;
+                                                    if let Some(predicate) = &can_drop_predicate {
+                                                        can_drop =
+                                                            predicate(drag.value.as_ref(), cx);
+                                                    }
+
+                                                    if can_drop {
+                                                        listener(drag.value.as_ref(), cx);
+                                                        cx.notify();
+                                                        cx.stop_propagation();
+                                                    }
                                                 }
                                             }
                                         }
@@ -1596,27 +1620,36 @@ impl Interactivity {
                 }
 
                 if let Some(drag) = cx.active_drag.take() {
-                    for (state_type, group_drag_style) in &self.group_drag_over_styles {
-                        if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
-                            if *state_type == drag.value.as_ref().type_id()
-                                && group_bounds.contains(&mouse_position)
+                    let mut can_drop = true;
+                    if let Some(can_drop_predicate) = &self.can_drop_predicate {
+                        can_drop = can_drop_predicate(drag.value.as_ref(), cx);
+                    }
+
+                    if can_drop {
+                        for (state_type, group_drag_style) in &self.group_drag_over_styles {
+                            if let Some(group_bounds) =
+                                GroupBounds::get(&group_drag_style.group, cx)
                             {
-                                style.refine(&group_drag_style.style);
+                                if *state_type == drag.value.as_ref().type_id()
+                                    && group_bounds.contains(&mouse_position)
+                                {
+                                    style.refine(&group_drag_style.style);
+                                }
                             }
                         }
-                    }
 
-                    for (state_type, drag_over_style) in &self.drag_over_styles {
-                        if *state_type == drag.value.as_ref().type_id()
-                            && bounds
-                                .intersect(&cx.content_mask().bounds)
-                                .contains(&mouse_position)
-                            && cx.was_top_layer_under_active_drag(
-                                &mouse_position,
-                                cx.stacking_order(),
-                            )
-                        {
-                            style.refine(drag_over_style);
+                        for (state_type, drag_over_style) in &self.drag_over_styles {
+                            if *state_type == drag.value.as_ref().type_id()
+                                && bounds
+                                    .intersect(&cx.content_mask().bounds)
+                                    .contains(&mouse_position)
+                                && cx.was_top_layer_under_active_drag(
+                                    &mouse_position,
+                                    cx.stacking_order(),
+                                )
+                            {
+                                style.refine(drag_over_style);
+                            }
                         }
                     }
 
@@ -1672,6 +1705,7 @@ impl Default for Interactivity {
             key_up_listeners: Vec::new(),
             action_listeners: Vec::new(),
             drop_listeners: Vec::new(),
+            can_drop_predicate: None,
             click_listeners: Vec::new(),
             drag_listener: None,
             hover_listener: None,

crates/terminal_view2/src/terminal_panel.rs 🔗

@@ -58,6 +58,15 @@ impl TerminalPanel {
                 workspace.weak_handle(),
                 workspace.project().clone(),
                 Default::default(),
+                Some(Arc::new(|a, cx| {
+                    if let Some(tab) = a.downcast_ref::<workspace::pane::DraggedTab>() {
+                        if let Some(item) = tab.pane.read(cx).item_for_index(tab.ix) {
+                            return item.downcast::<TerminalView>().is_some();
+                        }
+                    }
+
+                    false
+                })),
                 cx,
             );
             pane.set_can_split(false, cx);

crates/workspace2/src/pane.rs 🔗

@@ -181,7 +181,7 @@ pub struct Pane {
     workspace: WeakView<Workspace>,
     project: Model<Project>,
     drag_split_direction: Option<SplitDirection>,
-    //     can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
+    can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut WindowContext) -> bool>>,
     can_split: bool,
     //     render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
     _subscriptions: Vec<Subscription>,
@@ -229,7 +229,7 @@ pub struct NavigationEntry {
 }
 
 #[derive(Clone)]
-struct DraggedTab {
+pub struct DraggedTab {
     pub pane: View<Pane>,
     pub ix: usize,
     pub item_id: EntityId,
@@ -325,6 +325,7 @@ impl Pane {
         workspace: WeakView<Workspace>,
         project: Model<Project>,
         next_timestamp: Arc<AtomicUsize>,
+        can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut WindowContext) -> bool + 'static>>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         // todo!("context menu")
@@ -371,7 +372,7 @@ impl Pane {
             // tab_context_menu: cx.build_view(|_| ContextMenu::new(pane_view_id, cx)),
             workspace,
             project,
-            // can_drop: Rc::new(|_, _| true),
+            can_drop_predicate,
             can_split: true,
             // render_tab_bar_buttons: Rc::new(move |pane, cx| {
             //     Flex::row()
@@ -746,6 +747,10 @@ impl Pane {
             .position(|i| i.item_id() == item.item_id())
     }
 
+    pub fn item_for_index(&self, ix: usize) -> Option<&dyn ItemHandle> {
+        self.items.get(ix).map(|i| i.as_ref())
+    }
+
     pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
         if self.zoomed {
             cx.emit(Event::ZoomOut);
@@ -1530,6 +1535,9 @@ impl Pane {
             )
             .drag_over::<DraggedTab>(|tab| tab.bg(cx.theme().colors().drop_target_background))
             .drag_over::<ProjectEntryId>(|tab| tab.bg(cx.theme().colors().drop_target_background))
+            .when_some(self.can_drop_predicate.clone(), |this, p| {
+                this.can_drop(move |a, cx| p(a, cx))
+            })
             .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
                 this.drag_split_direction = None;
                 this.handle_tab_drop(dragged_tab, ix, cx)
@@ -1947,6 +1955,9 @@ impl Render for Pane {
                             ))
                             .group_drag_over::<DraggedTab>("", |style| style.visible())
                             .group_drag_over::<ProjectEntryId>("", |style| style.visible())
+                            .when_some(self.can_drop_predicate.clone(), |this, p| {
+                                this.can_drop(move |a, cx| p(a, cx))
+                            })
                             .on_drop(cx.listener(move |this, dragged_tab, cx| {
                                 this.handle_tab_drop(dragged_tab, this.active_item_index(), cx)
                             }))

crates/workspace2/src/workspace2.rs 🔗

@@ -542,6 +542,7 @@ impl Workspace {
                 weak_handle.clone(),
                 project.clone(),
                 pane_history_timestamp.clone(),
+                None,
                 cx,
             )
         });
@@ -1724,6 +1725,7 @@ impl Workspace {
                 self.weak_handle(),
                 self.project.clone(),
                 self.pane_history_timestamp.clone(),
+                None,
                 cx,
             )
         });