Prevent dragging items that aren't terminals to the terminal panel

Antonio Scandurra created

Change summary

crates/terminal_view/src/terminal_panel.rs         |  15 +
crates/workspace/src/pane.rs                       |  36 +++--
crates/workspace/src/pane/dragged_item_receiver.rs | 101 ++++++++-------
3 files changed, 88 insertions(+), 64 deletions(-)

Detailed changes

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -2,7 +2,7 @@ use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, Weak
 use project::Project;
 use settings::{Settings, WorkingDirectory};
 use util::ResultExt;
-use workspace::{dock::Panel, Pane, Workspace};
+use workspace::{dock::Panel, DraggedItem, Pane, Workspace};
 
 use crate::TerminalView;
 
@@ -17,11 +17,20 @@ impl TerminalPanel {
         Self {
             project: workspace.project().clone(),
             pane: cx.add_view(|cx| {
-                Pane::new(
+                let window_id = cx.window_id();
+                let mut pane = Pane::new(
                     workspace.weak_handle(),
                     workspace.app_state().background_actions,
                     cx,
-                )
+                );
+                pane.on_can_drop(move |drag_and_drop, cx| {
+                    drag_and_drop
+                        .currently_dragged::<DraggedItem>(window_id)
+                        .map_or(false, |(_, item)| {
+                            item.handle.act_as::<TerminalView>(cx).is_some()
+                        })
+                });
+                pane
             }),
             workspace: workspace.weak_handle(),
         }

crates/workspace/src/pane.rs 🔗

@@ -7,8 +7,8 @@ use crate::{
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
 use context_menu::{ContextMenu, ContextMenuItem};
-use drag_and_drop::Draggable;
-pub use dragged_item_receiver::{dragged_item_receiver, handle_dropped_item};
+use drag_and_drop::{DragAndDrop, Draggable};
+use dragged_item_receiver::dragged_item_receiver;
 use futures::StreamExt;
 use gpui::{
     actions,
@@ -148,6 +148,7 @@ pub struct Pane {
     _background_actions: BackgroundActions,
     workspace: WeakViewHandle<Workspace>,
     has_focus: bool,
+    can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
 }
 
 pub struct ItemNavHistory {
@@ -185,9 +186,9 @@ pub struct NavigationEntry {
     pub data: Option<Box<dyn Any>>,
 }
 
-struct DraggedItem {
-    item: Box<dyn ItemHandle>,
-    pane: WeakViewHandle<Pane>,
+pub struct DraggedItem {
+    pub handle: Box<dyn ItemHandle>,
+    pub pane: WeakViewHandle<Pane>,
 }
 
 pub enum ReorderBehavior {
@@ -253,6 +254,7 @@ impl Pane {
             _background_actions: background_actions,
             workspace,
             has_focus: false,
+            can_drop: Rc::new(|_, _| true),
         }
     }
 
@@ -273,6 +275,13 @@ impl Pane {
         self.has_focus
     }
 
+    pub fn on_can_drop<F>(&mut self, can_drop: F)
+    where
+        F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
+    {
+        self.can_drop = Rc::new(can_drop);
+    }
+
     pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
         ItemNavHistory {
             history: self.nav_history.clone(),
@@ -1293,7 +1302,7 @@ impl Pane {
             row.add_child({
                 enum TabDragReceiver {}
                 let mut receiver =
-                    dragged_item_receiver::<TabDragReceiver, _, _>(ix, ix, true, None, cx, {
+                    dragged_item_receiver::<TabDragReceiver, _, _>(self, ix, ix, true, None, cx, {
                         let item = item.clone();
                         let pane = pane.clone();
                         let detail = detail.clone();
@@ -1372,7 +1381,7 @@ impl Pane {
 
                 receiver.as_draggable(
                     DraggedItem {
-                        item,
+                        handle: item,
                         pane: pane.clone(),
                     },
                     {
@@ -1382,7 +1391,7 @@ impl Pane {
                         move |dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
                             let tab_style = &theme.workspace.tab_bar.dragged_tab;
                             Self::render_dragged_tab(
-                                &dragged_item.item,
+                                &dragged_item.handle,
                                 dragged_item.pane.clone(),
                                 false,
                                 detail,
@@ -1402,7 +1411,7 @@ impl Pane {
         let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
         enum Filler {}
         row.add_child(
-            dragged_item_receiver::<Filler, _, _>(0, filler_index, true, None, cx, |_, _| {
+            dragged_item_receiver::<Filler, _, _>(self, 0, filler_index, true, None, cx, |_, _| {
                 Empty::new()
                     .contained()
                     .with_style(filler_style.container)
@@ -1601,11 +1610,7 @@ impl Pane {
             .into_any()
     }
 
-    fn render_blank_pane(
-        &mut self,
-        theme: &Theme,
-        _cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
+    fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         let background = theme.workspace.background;
         Empty::new()
             .contained()
@@ -1668,6 +1673,7 @@ impl View for Pane {
                     .with_child({
                         enum PaneContentTabDropTarget {}
                         dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
+                            self,
                             0,
                             self.active_item_index + 1,
                             false,
@@ -1696,7 +1702,7 @@ impl View for Pane {
                 enum EmptyPane {}
                 let theme = cx.global::<Settings>().theme.clone();
 
-                dragged_item_receiver::<EmptyPane, _, _>(0, 0, false, None, cx, |_, cx| {
+                dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
                     self.render_blank_pane(&theme, cx)
                 })
                 .on_down(MouseButton::Left, |_, _, cx| {

crates/workspace/src/pane/dragged_item_receiver.rs 🔗

@@ -15,6 +15,7 @@ use crate::{Pane, SplitDirection, Workspace};
 use super::DraggedItem;
 
 pub fn dragged_item_receiver<Tag, D, F>(
+    pane: &Pane,
     region_id: usize,
     drop_index: usize,
     allow_same_pane: bool,
@@ -27,22 +28,24 @@ where
     D: Element<Pane>,
     F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> D,
 {
-    MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
+    let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
+    let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
+        drag_and_drop
+            .currently_dragged::<DraggedItem>(cx.window_id())
+            .map(|(drag_position, _)| drag_position)
+            .or_else(|| {
+                drag_and_drop
+                    .currently_dragged::<ProjectEntryId>(cx.window_id())
+                    .map(|(drag_position, _)| drag_position)
+            })
+    } else {
+        None
+    };
+
+    let mut handler = MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
         // Observing hovered will cause a render when the mouse enters regardless
         // of if mouse position was accessed before
-        let drag_position = if state.hovered() {
-            cx.global::<DragAndDrop<Workspace>>()
-                .currently_dragged::<DraggedItem>(cx.window_id())
-                .map(|(drag_position, _)| drag_position)
-                .or_else(|| {
-                    cx.global::<DragAndDrop<Workspace>>()
-                        .currently_dragged::<ProjectEntryId>(cx.window_id())
-                        .map(|(drag_position, _)| drag_position)
-                })
-        } else {
-            None
-        };
-
+        let drag_position = if state.hovered() { drag_position } else { None };
         Stack::new()
             .with_child(render_child(state, cx))
             .with_children(drag_position.map(|drag_position| {
@@ -67,38 +70,44 @@ where
                     }
                 })
             }))
-    })
-    .on_up(MouseButton::Left, {
-        move |event, pane, cx| {
-            let workspace = pane.workspace.clone();
-            let pane = cx.weak_handle();
-            handle_dropped_item(
-                event,
-                workspace,
-                &pane,
-                drop_index,
-                allow_same_pane,
-                split_margin,
-                cx,
-            );
-            cx.notify();
-        }
-    })
-    .on_move(|_, _, cx| {
-        let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
+    });
 
-        if drag_and_drop
-            .currently_dragged::<DraggedItem>(cx.window_id())
-            .is_some()
-            || drag_and_drop
-                .currently_dragged::<ProjectEntryId>(cx.window_id())
-                .is_some()
-        {
-            cx.notify();
-        } else {
-            cx.propagate_event();
-        }
-    })
+    if drag_position.is_some() {
+        handler = handler
+            .on_up(MouseButton::Left, {
+                move |event, pane, cx| {
+                    let workspace = pane.workspace.clone();
+                    let pane = cx.weak_handle();
+                    handle_dropped_item(
+                        event,
+                        workspace,
+                        &pane,
+                        drop_index,
+                        allow_same_pane,
+                        split_margin,
+                        cx,
+                    );
+                    cx.notify();
+                }
+            })
+            .on_move(|_, _, cx| {
+                let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
+
+                if drag_and_drop
+                    .currently_dragged::<DraggedItem>(cx.window_id())
+                    .is_some()
+                    || drag_and_drop
+                        .currently_dragged::<ProjectEntryId>(cx.window_id())
+                        .is_some()
+                {
+                    cx.notify();
+                } else {
+                    cx.propagate_event();
+                }
+            })
+    }
+
+    handler
 }
 
 pub fn handle_dropped_item<V: View>(
@@ -118,7 +127,7 @@ pub fn handle_dropped_item<V: View>(
     let action = if let Some((_, dragged_item)) =
         drag_and_drop.currently_dragged::<DraggedItem>(cx.window_id())
     {
-        Action::Move(dragged_item.pane.clone(), dragged_item.item.id())
+        Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
     } else if let Some((_, project_entry)) =
         drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id())
     {