WIP

K Simmons created

Change summary

.cargo/config.toml                        |   7 
crates/drag_and_drop/src/drag_and_drop.rs |  22 +-
crates/gpui/src/presenter.rs              |  43 +---
crates/workspace/src/pane.rs              | 224 ++++++++++++++++++------
4 files changed, 191 insertions(+), 105 deletions(-)

Detailed changes

.cargo/config.toml 🔗

@@ -1,7 +0,0 @@
-[target.'cfg(all())']
-rustflags = [
-    "-Dwarnings",
-    "-Aclippy::reversed_empty_ranges",
-    "-Aclippy::missing_safety_doc",
-    "-Aclippy::let_unit_value",
-]

crates/drag_and_drop/src/drag_and_drop.rs 🔗

@@ -1,4 +1,4 @@
-use std::{any::Any, sync::Arc};
+use std::{any::Any, rc::Rc};
 
 use gpui::{
     elements::{Container, MouseEventHandler},
@@ -10,8 +10,8 @@ use gpui::{
 struct State<V: View> {
     position: Vector2F,
     region_offset: Vector2F,
-    payload: Arc<dyn Any>,
-    render: Arc<dyn Fn(Arc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
+    payload: Rc<dyn Any + 'static>,
+    render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
 }
 
 impl<V: View> Clone for State<V> {
@@ -46,13 +46,15 @@ impl<V: View> DragAndDrop<V> {
         }
     }
 
-    pub fn currently_dragged<T: Any>(&self) -> Option<(Vector2F, &T)> {
+    pub fn currently_dragged<T: Any>(&self) -> Option<(Vector2F, Rc<T>)> {
         self.currently_dragged.as_ref().and_then(
             |State {
                  position, payload, ..
              }| {
                 payload
-                    .downcast_ref::<T>()
+                    .clone()
+                    .downcast::<T>()
+                    .ok()
                     .map(|payload| (position.clone(), payload))
             },
         )
@@ -61,9 +63,9 @@ impl<V: View> DragAndDrop<V> {
     pub fn dragging<T: Any>(
         relative_to: Option<RectF>,
         position: Vector2F,
-        payload: Arc<T>,
+        payload: Rc<T>,
         cx: &mut EventContext,
-        render: Arc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
+        render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
     ) {
         cx.update_global::<Self, _, _>(|this, cx| {
             let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() {
@@ -80,7 +82,7 @@ impl<V: View> DragAndDrop<V> {
                 region_offset,
                 position,
                 payload,
-                render: Arc::new(move |payload, cx| {
+                render: Rc::new(move |payload, cx| {
                     render(payload.downcast_ref::<T>().unwrap(), cx)
                 }),
             });
@@ -143,8 +145,8 @@ impl Draggable for MouseEventHandler {
     where
         Self: Sized,
     {
-        let payload = Arc::new(payload);
-        let render = Arc::new(render);
+        let payload = Rc::new(payload);
+        let render = Rc::new(render);
         self.on_drag(MouseButton::Left, move |e, cx| {
             let payload = payload.clone();
             let render = render.clone();

crates/gpui/src/presenter.rs 🔗

@@ -224,6 +224,8 @@ impl Presenter {
                 Event::MouseDown(e @ MouseButtonEvent { position, .. }) => {
                     for (region, _) in self.mouse_regions.iter().rev() {
                         if region.bounds.contains_point(*position) {
+                            self.clicked_region = Some(region.clone());
+                            self.prev_drag_position = Some(*position);
                             events_to_send.push((
                                 region.clone(),
                                 MouseRegionEvent::Down(DownRegionEvent {
@@ -277,19 +279,19 @@ impl Presenter {
                     }
                 }
                 Event::MouseMoved(e @ MouseMovedEvent { position, .. }) => {
-                    if let Some((clicked_region, prev_drag_position)) = self
-                        .clicked_region
-                        .as_ref()
-                        .zip(self.prev_drag_position.as_mut())
-                    {
-                        events_to_send.push((
-                            clicked_region.clone(),
-                            MouseRegionEvent::Drag(DragRegionEvent {
-                                region: clicked_region.bounds,
-                                prev_drag_position: *prev_drag_position,
-                                platform_event: e.clone(),
-                            }),
-                        ));
+                    if let Some(clicked_region) = self.clicked_region.as_ref() {
+                        if let Some(prev_drag_position) = self.prev_drag_position {
+                            events_to_send.push((
+                                clicked_region.clone(),
+                                MouseRegionEvent::Drag(DragRegionEvent {
+                                    region: clicked_region.bounds,
+                                    prev_drag_position,
+                                    platform_event: e.clone(),
+                                }),
+                            ));
+                        }
+
+                        self.prev_drag_position = Some(*position)
                     }
 
                     for (region, _) in self.mouse_regions.iter().rev() {
@@ -417,8 +419,6 @@ impl Presenter {
             view_stack: Default::default(),
             invalidated_views: Default::default(),
             notify_count: 0,
-            clicked_region: &mut self.clicked_region,
-            prev_drag_position: &mut self.prev_drag_position,
             handled: false,
             window_id: self.window_id,
             app: cx,
@@ -639,8 +639,6 @@ pub struct EventContext<'a> {
     pub window_id: usize,
     pub notify_count: usize,
     view_stack: Vec<usize>,
-    clicked_region: &'a mut Option<MouseRegion>,
-    prev_drag_position: &'a mut Option<Vector2F>,
     handled: bool,
     invalidated_views: HashSet<usize>,
 }
@@ -664,17 +662,6 @@ impl<'a> EventContext<'a> {
                     continue;
                 }
 
-                match &event {
-                    MouseRegionEvent::Down(e) => {
-                        *self.clicked_region = Some(region.clone());
-                        *self.prev_drag_position = Some(e.position);
-                    }
-                    MouseRegionEvent::Drag(e) => {
-                        *self.prev_drag_position = Some(e.position);
-                    }
-                    _ => {}
-                }
-
                 self.invalidated_views.insert(region.view_id);
             }
 

crates/workspace/src/pane.rs 🔗

@@ -3,7 +3,7 @@ use crate::{toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, WeakItemHan
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use context_menu::{ContextMenu, ContextMenuItem};
-use drag_and_drop::Draggable;
+use drag_and_drop::{DragAndDrop, Draggable};
 use futures::StreamExt;
 use gpui::{
     actions,
@@ -49,6 +49,14 @@ pub struct CloseItem {
     pub pane: WeakViewHandle<Pane>,
 }
 
+#[derive(Clone, PartialEq)]
+pub struct MoveItem {
+    pub item_id: usize,
+    pub from: WeakViewHandle<Pane>,
+    pub to: WeakViewHandle<Pane>,
+    pub destination_index: usize,
+}
+
 #[derive(Clone, Deserialize, PartialEq)]
 pub struct GoBack {
     #[serde(skip_deserializing)]
@@ -72,7 +80,7 @@ pub struct DeployNewMenu {
 }
 
 impl_actions!(pane, [GoBack, GoForward, ActivateItem]);
-impl_internal_actions!(pane, [CloseItem, DeploySplitMenu, DeployNewMenu]);
+impl_internal_actions!(pane, [CloseItem, DeploySplitMenu, DeployNewMenu, MoveItem]);
 
 const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 
@@ -99,6 +107,47 @@ pub fn init(cx: &mut MutableAppContext) {
             Ok(())
         }))
     });
+    cx.add_action(|workspace: &mut Workspace, action: &MoveItem, cx| {
+        // Get item handle to move
+        let from = if let Some(from) = action.from.upgrade(cx) {
+            from
+        } else {
+            return;
+        };
+
+        let (item_ix, item_handle) = from
+            .read(cx)
+            .items()
+            .enumerate()
+            .find(|(_, item_handle)| item_handle.id() == action.item_id)
+            .expect("Tried to move item handle which was not in from pane");
+
+        // Add item to new pane at given index
+        let to = if let Some(to) = action.to.upgrade(cx) {
+            to
+        } else {
+            return;
+        };
+
+        // This automatically removes duplicate items in the pane
+        Pane::add_item_at(
+            workspace,
+            to,
+            item_handle.clone(),
+            true,
+            true,
+            Some(action.destination_index),
+            cx,
+        );
+
+        if action.from != action.to {
+            // Close item from previous pane
+            from.update(cx, |from, cx| {
+                from.remove_item(item_ix, cx);
+                dbg!(from.items().collect::<Vec<_>>());
+            });
+        }
+    });
     cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
     cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
     cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
@@ -408,12 +457,13 @@ impl Pane {
         }
     }
 
-    pub(crate) fn add_item(
+    pub fn add_item_at(
         workspace: &mut Workspace,
         pane: ViewHandle<Pane>,
         item: Box<dyn ItemHandle>,
         activate_pane: bool,
         focus_item: bool,
+        destination_index: Option<usize>,
         cx: &mut ViewContext<Workspace>,
     ) {
         // Prevent adding the same item to the pane more than once.
@@ -428,16 +478,20 @@ impl Pane {
 
         item.added_to_pane(workspace, pane.clone(), cx);
         pane.update(cx, |pane, cx| {
-            // If there is already an active item, then insert the new item
-            // right after it. Otherwise, adjust the `active_item_index` field
-            // before activating the new item, so that in the `activate_item`
-            // method, we can detect that the active item is changing.
-            let item_ix;
-            if pane.active_item_index < pane.items.len() {
-                item_ix = pane.active_item_index + 1
+            let item_ix = if let Some(destination_index) = destination_index {
+                destination_index
             } else {
-                item_ix = pane.items.len();
-                pane.active_item_index = usize::MAX;
+                // If there is already an active item, then insert the new item
+                // right after it. Otherwise, adjust the `active_item_index` field
+                // before activating the new item, so that in the `activate_item`
+                // method, we can detect that the active item is changing.
+                if pane.active_item_index < pane.items.len() {
+                    pane.active_item_index + 1
+                } else {
+                    let ix = pane.items.len();
+                    pane.active_item_index = usize::MAX;
+                    ix
+                }
             };
 
             cx.reparent(&item);
@@ -447,6 +501,17 @@ impl Pane {
         });
     }
 
+    pub(crate) fn add_item(
+        workspace: &mut Workspace,
+        pane: ViewHandle<Pane>,
+        item: Box<dyn ItemHandle>,
+        activate_pane: bool,
+        focus_item: bool,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        Self::add_item_at(workspace, pane, item, activate_pane, focus_item, None, cx)
+    }
+
     pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> {
         self.items.iter()
     }
@@ -673,48 +738,7 @@ impl Pane {
                 // Remove the item from the pane.
                 pane.update(&mut cx, |pane, cx| {
                     if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
-                        if item_ix == pane.active_item_index {
-                            // Activate the previous item if possible.
-                            // This returns the user to the previously opened tab if they closed
-                            // a ne item they just navigated to.
-                            if item_ix > 0 {
-                                pane.activate_prev_item(cx);
-                            } else if item_ix + 1 < pane.items.len() {
-                                pane.activate_next_item(cx);
-                            }
-                        }
-
-                        let item = pane.items.remove(item_ix);
-                        cx.emit(Event::RemoveItem);
-                        if pane.items.is_empty() {
-                            item.deactivated(cx);
-                            pane.update_toolbar(cx);
-                            cx.emit(Event::Remove);
-                        }
-
-                        if item_ix < pane.active_item_index {
-                            pane.active_item_index -= 1;
-                        }
-
-                        pane.nav_history
-                            .borrow_mut()
-                            .set_mode(NavigationMode::ClosingItem);
-                        item.deactivated(cx);
-                        pane.nav_history
-                            .borrow_mut()
-                            .set_mode(NavigationMode::Normal);
-
-                        if let Some(path) = item.project_path(cx) {
-                            pane.nav_history
-                                .borrow_mut()
-                                .paths_by_item
-                                .insert(item.id(), path);
-                        } else {
-                            pane.nav_history
-                                .borrow_mut()
-                                .paths_by_item
-                                .remove(&item.id());
-                        }
+                        pane.remove_item(item_ix, cx);
                     }
                 });
             }
@@ -724,6 +748,53 @@ impl Pane {
         })
     }
 
+    fn remove_item(&mut self, item_ix: usize, cx: &mut ViewContext<Self>) {
+        if item_ix == self.active_item_index {
+            // Activate the previous item if possible.
+            // This returns the user to the previously opened tab if they closed
+            // a new item they just navigated to.
+            if item_ix > 0 {
+                self.activate_prev_item(cx);
+            } else if item_ix + 1 < self.items.len() {
+                self.activate_next_item(cx);
+            }
+        }
+
+        let item = self.items.remove(item_ix);
+        cx.emit(Event::RemoveItem);
+        if self.items.is_empty() {
+            item.deactivated(cx);
+            self.update_toolbar(cx);
+            cx.emit(Event::Remove);
+        }
+
+        if item_ix < self.active_item_index {
+            self.active_item_index -= 1;
+        }
+
+        self.nav_history
+            .borrow_mut()
+            .set_mode(NavigationMode::ClosingItem);
+        item.deactivated(cx);
+        self.nav_history
+            .borrow_mut()
+            .set_mode(NavigationMode::Normal);
+
+        if let Some(path) = item.project_path(cx) {
+            self.nav_history
+                .borrow_mut()
+                .paths_by_item
+                .insert(item.id(), path);
+        } else {
+            self.nav_history
+                .borrow_mut()
+                .paths_by_item
+                .remove(&item.id());
+        }
+
+        cx.notify();
+    }
+
     pub async fn save_item(
         project: ModelHandle<Project>,
         pane: &ViewHandle<Pane>,
@@ -880,6 +951,11 @@ impl Pane {
     fn render_tab_bar(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
         let theme = cx.global::<Settings>().theme.clone();
 
+        struct DraggedItem {
+            item: Box<dyn ItemHandle>,
+            pane: WeakViewHandle<Pane>,
+        }
+
         enum Tabs {}
         enum Tab {}
         let pane = cx.handle();
@@ -940,15 +1016,43 @@ impl Pane {
                             })
                         }
                     })
-                    .as_draggable(item, {
+                    .on_up(MouseButton::Left, {
                         let pane = pane.clone();
-                        let detail = detail.clone();
-
-                        move |item, cx: &mut RenderContext<Workspace>| {
-                            let pane = pane.clone();
-                            Pane::render_tab(item, pane, detail, false, pane_active, tab_active, cx)
+                        move |_, cx: &mut EventContext| {
+                            if let Some((_, dragged_item)) = cx
+                                .global::<DragAndDrop<Workspace>>()
+                                .currently_dragged::<DraggedItem>()
+                            {
+                                cx.dispatch_action(MoveItem {
+                                    item_id: dragged_item.item.id(),
+                                    from: dragged_item.pane.clone(),
+                                    to: pane.clone(),
+                                    destination_index: ix,
+                                })
+                            }
+                            cx.propogate_event();
                         }
                     })
+                    .as_draggable(
+                        DraggedItem {
+                            item,
+                            pane: pane.clone(),
+                        },
+                        {
+                            let detail = detail.clone();
+                            move |dragged_item, cx: &mut RenderContext<Workspace>| {
+                                Pane::render_tab(
+                                    &dragged_item.item,
+                                    dragged_item.pane.clone(),
+                                    detail,
+                                    false,
+                                    pane_active,
+                                    tab_active,
+                                    cx,
+                                )
+                            }
+                        },
+                    )
                     .boxed()
                 })
             }