extract dragged item target

K Simmons created

Change summary

crates/gpui/src/elements/stack.rs                  |   3 
crates/workspace/src/dock.rs                       |   7 
crates/workspace/src/pane.rs                       | 233 ++-------------
crates/workspace/src/pane/dragged_item_receiver.rs | 133 +++++++++
4 files changed, 179 insertions(+), 197 deletions(-)

Detailed changes

crates/gpui/src/elements/stack.rs 🔗

@@ -24,12 +24,13 @@ impl Element for Stack {
 
     fn layout(
         &mut self,
-        constraint: SizeConstraint,
+        mut constraint: SizeConstraint,
         cx: &mut LayoutContext,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.min;
         for child in &mut self.children {
             size = size.max(child.layout(constraint, cx));
+            constraint.min = size;
         }
         (size, ())
     }

crates/workspace/src/dock.rs 🔗

@@ -9,7 +9,9 @@ use serde::Deserialize;
 use settings::{DockAnchor, Settings};
 use theme::Theme;
 
-use crate::{sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace};
+use crate::{
+    handle_dropped_item, sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace,
+};
 
 #[derive(PartialEq, Clone, Deserialize)]
 pub struct MoveDock(pub DockAnchor);
@@ -376,6 +378,7 @@ impl View for ToggleDockButton {
         let dock_position = workspace.read(cx).dock.position;
 
         let theme = cx.global::<Settings>().theme.clone();
+
         let button = MouseEventHandler::<Self>::new(0, cx, {
             let theme = theme.clone();
             move |state, _| {
@@ -400,7 +403,7 @@ impl View for ToggleDockButton {
         .on_up(MouseButton::Left, move |event, cx| {
             let dock_pane = workspace.read(cx.app).dock_pane();
             let drop_index = dock_pane.read(cx.app).items_len() + 1;
-            Pane::handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
+            handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
         });
 
         if dock_position.is_visible() {

crates/workspace/src/pane.rs 🔗

@@ -1,17 +1,19 @@
+mod dragged_item_receiver;
+
 use super::{ItemHandle, SplitDirection};
 use crate::{
     dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, ExpandDock, HideDock},
     toolbar::Toolbar,
-    Item, NewFile, NewSearch, NewTerminal, SplitWithItem, WeakItemHandle, Workspace,
+    Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace,
 };
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use context_menu::{ContextMenu, ContextMenuItem};
-use drag_and_drop::{DragAndDrop, Draggable};
+use drag_and_drop::Draggable;
+pub use dragged_item_receiver::{dragged_item_receiver, handle_dropped_item};
 use futures::StreamExt;
 use gpui::{
     actions,
-    color::Color,
     elements::*,
     geometry::{
         rect::RectF,
@@ -19,7 +21,6 @@ use gpui::{
     },
     impl_actions, impl_internal_actions,
     platform::{CursorStyle, NavigationDirection},
-    scene::MouseUp,
     Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
     ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
     ViewContext, ViewHandle, WeakViewHandle,
@@ -1056,11 +1057,7 @@ impl Pane {
 
     fn render_tabs(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
         let theme = cx.global::<Settings>().theme.clone();
-        let filler_index = self.items.len();
 
-        enum Tabs {}
-        enum Tab {}
-        enum Filler {}
         let pane = cx.handle();
         let autoscroll = if mem::take(&mut self.autoscroll) {
             Some(self.active_item_index)
@@ -1070,6 +1067,7 @@ impl Pane {
 
         let pane_active = self.is_active;
 
+        enum Tabs {}
         let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
         for (ix, (item, detail)) in self
             .items
@@ -1082,7 +1080,8 @@ impl Pane {
             let tab_active = ix == self.active_item_index;
 
             row.add_child({
-                MouseEventHandler::<Tab>::above(ix, cx, {
+                enum Tab {}
+                dragged_item_receiver::<Tab, _>(ix, ix, true, None, cx, {
                     let item = item.clone();
                     let pane = pane.clone();
                     let detail = detail.clone();
@@ -1092,16 +1091,7 @@ impl Pane {
                     move |mouse_state, cx| {
                         let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active);
                         let hovered = mouse_state.hovered();
-                        Self::render_tab(
-                            &item,
-                            pane,
-                            ix == 0,
-                            detail,
-                            hovered,
-                            Self::tab_overlay_color(hovered, cx),
-                            tab_style,
-                            cx,
-                        )
+                        Self::render_tab(&item, pane, ix == 0, detail, hovered, tab_style, cx)
                     }
                 })
                 .with_cursor_style(if pane_active && tab_active {
@@ -1123,10 +1113,6 @@ impl Pane {
                         })
                     }
                 })
-                .on_up(MouseButton::Left, {
-                    let pane = pane.clone();
-                    move |event, cx| Pane::handle_dropped_item(event, &pane, ix, true, None, cx)
-                })
                 .as_draggable(
                     DraggedItem {
                         item,
@@ -1144,7 +1130,6 @@ impl Pane {
                                 false,
                                 detail,
                                 false,
-                                None,
                                 &tab_style,
                                 cx,
                             )
@@ -1157,22 +1142,16 @@ impl Pane {
 
         // Use the inactive tab style along with the current pane's active status to decide how to render
         // the filler
+        let filler_index = self.items.len();
         let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
+        enum Filler {}
         row.add_child(
-            MouseEventHandler::<Filler>::new(0, cx, |mouse_state, cx| {
-                let mut filler = Empty::new()
+            dragged_item_receiver::<Filler, _>(0, filler_index, true, None, cx, |_, _| {
+                Empty::new()
                     .contained()
                     .with_style(filler_style.container)
-                    .with_border(filler_style.container.border);
-
-                if let Some(overlay) = Self::tab_overlay_color(mouse_state.hovered(), cx) {
-                    filler = filler.with_overlay_color(overlay);
-                }
-
-                filler.boxed()
-            })
-            .on_up(MouseButton::Left, move |event, cx| {
-                Pane::handle_dropped_item(event, &pane, filler_index, true, None, cx)
+                    .with_border(filler_style.container.border)
+                    .boxed()
             })
             .flex(1., true)
             .named("filler"),
@@ -1224,7 +1203,6 @@ impl Pane {
         first: bool,
         detail: Option<usize>,
         hovered: bool,
-        overlay: Option<Color>,
         tab_style: &theme::Tab,
         cx: &mut RenderContext<V>,
     ) -> ElementBox {
@@ -1234,7 +1212,7 @@ impl Pane {
             container.border.left = false;
         }
 
-        let mut tab = Flex::row()
+        Flex::row()
             .with_child(
                 Align::new({
                     let diameter = 7.0;
@@ -1312,13 +1290,10 @@ impl Pane {
                 .boxed(),
             )
             .contained()
-            .with_style(container);
-
-        if let Some(overlay) = overlay {
-            tab = tab.with_overlay_color(overlay);
-        }
-
-        tab.constrained().with_height(tab_style.height).boxed()
+            .with_style(container)
+            .constrained()
+            .with_height(tab_style.height)
+            .boxed()
     }
 
     fn render_tab_bar_buttons(
@@ -1356,79 +1331,6 @@ impl Pane {
             .flex(1., false)
             .boxed()
     }
-
-    pub fn handle_dropped_item(
-        event: MouseUp,
-        pane: &WeakViewHandle<Pane>,
-        index: usize,
-        allow_same_pane: bool,
-        split_margin: Option<f32>,
-        cx: &mut EventContext,
-    ) {
-        if let Some((_, dragged_item)) = cx
-            .global::<DragAndDrop<Workspace>>()
-            .currently_dragged::<DraggedItem>(cx.window_id)
-        {
-            if let Some(split_direction) = split_margin
-                .and_then(|margin| Self::drop_split_direction(event.position, event.region, margin))
-            {
-                cx.dispatch_action(SplitWithItem {
-                    from: dragged_item.pane.clone(),
-                    item_id_to_move: dragged_item.item.id(),
-                    pane_to_split: pane.clone(),
-                    split_direction,
-                });
-            } else if pane != &dragged_item.pane || allow_same_pane {
-                // If no split margin or not close enough to the edge, just move the item
-                cx.dispatch_action(MoveItem {
-                    item_id: dragged_item.item.id(),
-                    from: dragged_item.pane.clone(),
-                    to: pane.clone(),
-                    destination_index: index,
-                })
-            }
-        } else {
-            cx.propagate_event();
-        }
-    }
-
-    fn drop_split_direction(
-        position: Vector2F,
-        region: RectF,
-        split_margin: f32,
-    ) -> Option<SplitDirection> {
-        let mut min_direction = None;
-        let mut min_distance = split_margin;
-        for direction in SplitDirection::all() {
-            let edge_distance =
-                (direction.edge(region) - direction.axis().component(position)).abs();
-
-            if edge_distance < min_distance {
-                min_direction = Some(direction);
-                min_distance = edge_distance;
-            }
-        }
-
-        min_direction
-    }
-
-    fn tab_overlay_color(hovered: bool, cx: &mut RenderContext<Self>) -> Option<Color> {
-        if hovered
-            && cx
-                .global::<DragAndDrop<Workspace>>()
-                .currently_dragged::<DraggedItem>(cx.window_id())
-                .is_some()
-        {
-            Some(
-                cx.global::<Settings>()
-                    .theme
-                    .workspace
-                    .drop_target_overlay_color,
-            )
-        } else {
-            None
-        }
-    }
 }
 
 impl Entity for Pane {
@@ -1449,8 +1351,6 @@ impl View for Pane {
             .with_child(
                 MouseEventHandler::<MouseNavigationHandler>::new(0, cx, |_, cx| {
                     if let Some(active_item) = self.active_item() {
-                        enum PaneContentTabDropTarget {}
-
                         Flex::column()
                             .with_child({
                                 let mut tab_row = Flex::row()
@@ -1471,78 +1371,29 @@ impl View for Pane {
                                     .named("tab bar")
                             })
                             .with_child({
-                                let drop_index = self.active_item_index + 1;
-                                MouseEventHandler::<PaneContentTabDropTarget>::above(
+                                enum PaneContentTabDropTarget {}
+                                dragged_item_receiver::<PaneContentTabDropTarget, _>(
                                     0,
+                                    self.active_item_index + 1,
+                                    false,
+                                    Some(100.),
                                     cx,
-                                    |state, cx| {
-                                        let overlay_color = Self::tab_overlay_color(true, cx);
-                                        // Hovered will cause a render when the mouse enters regardless
-                                        // of if mouse position was accessed before
-                                        let hovered = state.hovered();
-                                        let drag_position = cx
-                                            .global::<DragAndDrop<Workspace>>()
-                                            .currently_dragged::<DraggedItem>(cx.window_id())
-                                            .filter(|_| hovered)
-                                            .map(|_| state.mouse_position());
-
-                                        Stack::new()
-                                            .with_child(
-                                                Flex::column()
-                                                    .with_child(
-                                                        ChildView::new(&self.toolbar, cx)
-                                                            .expanded()
-                                                            .boxed(),
-                                                    )
-                                                    .with_child(
-                                                        ChildView::new(active_item, cx)
-                                                            .flex(1., true)
-                                                            .boxed(),
-                                                    )
-                                                    .boxed(),
-                                            )
-                                            .with_children(drag_position.map(|drag_position| {
-                                                Canvas::new(move |region, _, cx| {
-                                                    if region.contains_point(drag_position) {
-                                                        let overlay_region = if let Some(
-                                                            split_direction,
-                                                        ) =
-                                                            Self::drop_split_direction(
-                                                                drag_position,
-                                                                region,
-                                                                100., /* Replace with theme value */
-                                                            ) {
-                                                            split_direction.along_edge(region, 100.)
-                                                        } else {
-                                                            region
-                                                        };
-
-                                                        cx.scene.push_quad(Quad {
-                                                            bounds: overlay_region,
-                                                            background: overlay_color,
-                                                            border: Default::default(),
-                                                            corner_radius: 0.,
-                                                        });
-                                                    }
-                                                })
+                                    {
+                                        let toolbar = self.toolbar.clone();
+                                        move |_, cx| {
+                                            Flex::column()
+                                                .with_child(
+                                                    ChildView::new(&toolbar, cx).expanded().boxed(),
+                                                )
+                                                .with_child(
+                                                    ChildView::new(active_item, cx)
+                                                        .flex(1., true)
+                                                        .boxed(),
+                                                )
                                                 .boxed()
-                                            }))
-                                            .boxed()
+                                        }
                                     },
                                 )
-                                .on_up(MouseButton::Left, {
-                                    let pane = cx.handle();
-                                    move |event, cx| {
-                                        Pane::handle_dropped_item(
-                                            event,
-                                            &pane,
-                                            drop_index,
-                                            false,
-                                            Some(100.), /* Use theme value */
-                                            cx,
-                                        )
-                                    }
-                                })
                                 .flex(1., true)
                                 .boxed()
                             })
@@ -1551,7 +1402,7 @@ impl View for Pane {
                         enum EmptyPane {}
                         let theme = cx.global::<Settings>().theme.clone();
 
-                        MouseEventHandler::<EmptyPane>::new(0, cx, |_, _| {
+                        dragged_item_receiver::<EmptyPane, _>(0, 0, false, None, cx, |_, _| {
                             Empty::new()
                                 .contained()
                                 .with_background_color(theme.workspace.background)
@@ -1560,12 +1411,6 @@ impl View for Pane {
                         .on_down(MouseButton::Left, |_, cx| {
                             cx.focus_parent_view();
                         })
-                        .on_up(MouseButton::Left, {
-                            let pane = this.clone();
-                            move |event, cx| {
-                                Pane::handle_dropped_item(event, &pane, 0, true, None, cx)
-                            }
-                        })
                         .boxed()
                     }
                 })

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

@@ -0,0 +1,133 @@
+use drag_and_drop::DragAndDrop;
+use gpui::{
+    color::Color,
+    elements::{Canvas, MouseEventHandler, ParentElement, Stack},
+    geometry::{rect::RectF, vector::Vector2F},
+    scene::MouseUp,
+    AppContext, Element, ElementBox, EventContext, MouseButton, MouseState, Quad, RenderContext,
+    WeakViewHandle,
+};
+use settings::Settings;
+
+use crate::{MoveItem, Pane, SplitDirection, SplitWithItem, Workspace};
+
+use super::DraggedItem;
+
+pub fn dragged_item_receiver<Tag, F>(
+    region_id: usize,
+    drop_index: usize,
+    allow_same_pane: bool,
+    split_margin: Option<f32>,
+    cx: &mut RenderContext<Pane>,
+    render_child: F,
+) -> MouseEventHandler<Tag>
+where
+    Tag: 'static,
+    F: FnOnce(&mut MouseState, &mut RenderContext<Pane>) -> ElementBox,
+{
+    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 hovered = state.hovered();
+        let drag_position = cx
+            .global::<DragAndDrop<Workspace>>()
+            .currently_dragged::<DraggedItem>(cx.window_id())
+            .filter(|_| hovered)
+            .map(|_| state.mouse_position());
+
+        Stack::new()
+            .with_child(render_child(state, cx))
+            .with_children(drag_position.map(|drag_position| {
+                Canvas::new(move |bounds, _, cx| {
+                    if bounds.contains_point(drag_position) {
+                        let overlay_region = split_margin
+                            .and_then(|split_margin| {
+                                drop_split_direction(drag_position, bounds, split_margin)
+                                    .map(|dir| (dir, split_margin))
+                            })
+                            .map(|(dir, margin)| dir.along_edge(bounds, margin))
+                            .unwrap_or(bounds);
+
+                        cx.paint_stacking_context(None, |cx| {
+                            cx.scene.push_quad(Quad {
+                                bounds: overlay_region,
+                                background: Some(overlay_color(cx)),
+                                border: Default::default(),
+                                corner_radius: 0.,
+                            });
+                        });
+                    }
+                })
+                .boxed()
+            }))
+            .boxed()
+    })
+    .on_up(MouseButton::Left, {
+        let pane = cx.handle();
+        move |event, cx| {
+            handle_dropped_item(event, &pane, drop_index, allow_same_pane, split_margin, cx);
+            cx.notify();
+        }
+    })
+}
+
+pub fn handle_dropped_item(
+    event: MouseUp,
+    pane: &WeakViewHandle<Pane>,
+    index: usize,
+    allow_same_pane: bool,
+    split_margin: Option<f32>,
+    cx: &mut EventContext,
+) {
+    if let Some((_, dragged_item)) = cx
+        .global::<DragAndDrop<Workspace>>()
+        .currently_dragged::<DraggedItem>(cx.window_id)
+    {
+        if let Some(split_direction) = split_margin
+            .and_then(|margin| drop_split_direction(event.position, event.region, margin))
+        {
+            cx.dispatch_action(SplitWithItem {
+                from: dragged_item.pane.clone(),
+                item_id_to_move: dragged_item.item.id(),
+                pane_to_split: pane.clone(),
+                split_direction,
+            });
+        } else if pane != &dragged_item.pane || allow_same_pane {
+            // If no split margin or not close enough to the edge, just move the item
+            cx.dispatch_action(MoveItem {
+                item_id: dragged_item.item.id(),
+                from: dragged_item.pane.clone(),
+                to: pane.clone(),
+                destination_index: index,
+            })
+        }
+    } else {
+        cx.propagate_event();
+    }
+}
+
+fn drop_split_direction(
+    position: Vector2F,
+    region: RectF,
+    split_margin: f32,
+) -> Option<SplitDirection> {
+    let mut min_direction = None;
+    let mut min_distance = split_margin;
+    for direction in SplitDirection::all() {
+        let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
+
+        if edge_distance < min_distance {
+            min_direction = Some(direction);
+            min_distance = edge_distance;
+        }
+    }
+
+    min_direction
+}
+
+fn overlay_color(cx: &AppContext) -> Color {
+    cx.global::<Settings>()
+        .theme
+        .workspace
+        .drop_target_overlay_color
+}