dragged_item_receiver.rs

  1use super::DraggedItem;
  2use crate::{Pane, SplitDirection, Workspace};
  3use drag_and_drop::DragAndDrop;
  4use gpui::{
  5    color::Color,
  6    elements::{Canvas, MouseEventHandler, ParentElement, Stack},
  7    geometry::{rect::RectF, vector::Vector2F},
  8    platform::MouseButton,
  9    scene::MouseUp,
 10    AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
 11};
 12use project::ProjectEntryId;
 13
 14pub fn dragged_item_receiver<Tag, D, F>(
 15    pane: &Pane,
 16    region_id: usize,
 17    drop_index: usize,
 18    allow_same_pane: bool,
 19    split_margin: Option<f32>,
 20    cx: &mut ViewContext<Pane>,
 21    render_child: F,
 22) -> MouseEventHandler<Pane>
 23where
 24    Tag: 'static,
 25    D: Element<Pane>,
 26    F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> D,
 27{
 28    let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
 29    let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
 30        drag_and_drop
 31            .currently_dragged::<DraggedItem>(cx.window())
 32            .map(|(drag_position, _)| drag_position)
 33            .or_else(|| {
 34                drag_and_drop
 35                    .currently_dragged::<ProjectEntryId>(cx.window())
 36                    .map(|(drag_position, _)| drag_position)
 37            })
 38    } else {
 39        None
 40    };
 41
 42    let mut handler = MouseEventHandler::above::<Tag, _>(region_id, cx, |state, cx| {
 43        // Observing hovered will cause a render when the mouse enters regardless
 44        // of if mouse position was accessed before
 45        let drag_position = if state.dragging() {
 46            drag_position
 47        } else {
 48            None
 49        };
 50        Stack::new()
 51            .with_child(render_child(state, cx))
 52            .with_children(drag_position.map(|drag_position| {
 53                Canvas::new(move |bounds, _, _, cx| {
 54                    if bounds.contains_point(drag_position) {
 55                        let overlay_region = split_margin
 56                            .and_then(|split_margin| {
 57                                drop_split_direction(drag_position, bounds, split_margin)
 58                                    .map(|dir| (dir, split_margin))
 59                            })
 60                            .map(|(dir, margin)| dir.along_edge(bounds, margin))
 61                            .unwrap_or(bounds);
 62
 63                        cx.scene().push_stacking_context(None, None);
 64                        let background = overlay_color(cx);
 65                        cx.scene().push_quad(Quad {
 66                            bounds: overlay_region,
 67                            background: Some(background),
 68                            border: Default::default(),
 69                            corner_radii: Default::default(),
 70                        });
 71                        cx.scene().pop_stacking_context();
 72                    }
 73                })
 74            }))
 75    });
 76
 77    if drag_position.is_some() {
 78        handler = handler
 79            .on_up(MouseButton::Left, {
 80                move |event, pane, cx| {
 81                    let workspace = pane.workspace.clone();
 82                    let pane = cx.weak_handle();
 83                    handle_dropped_item(
 84                        event,
 85                        workspace,
 86                        &pane,
 87                        drop_index,
 88                        allow_same_pane,
 89                        split_margin,
 90                        cx,
 91                    );
 92                    cx.notify();
 93                }
 94            })
 95            .on_move(|_, _, cx| {
 96                let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
 97
 98                if drag_and_drop
 99                    .currently_dragged::<DraggedItem>(cx.window())
100                    .is_some()
101                    || drag_and_drop
102                        .currently_dragged::<ProjectEntryId>(cx.window())
103                        .is_some()
104                {
105                    cx.notify();
106                } else {
107                    cx.propagate_event();
108                }
109            })
110    }
111
112    handler
113}
114
115pub fn handle_dropped_item<V: 'static>(
116    event: MouseUp,
117    workspace: WeakViewHandle<Workspace>,
118    pane: &WeakViewHandle<Pane>,
119    index: usize,
120    allow_same_pane: bool,
121    split_margin: Option<f32>,
122    cx: &mut EventContext<V>,
123) {
124    enum Action {
125        Move(WeakViewHandle<Pane>, usize),
126        Open(ProjectEntryId),
127    }
128    let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
129    let action = if let Some((_, dragged_item)) =
130        drag_and_drop.currently_dragged::<DraggedItem>(cx.window())
131    {
132        Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
133    } else if let Some((_, project_entry)) =
134        drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window())
135    {
136        Action::Open(*project_entry)
137    } else {
138        cx.propagate_event();
139        return;
140    };
141
142    if let Some(split_direction) =
143        split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
144    {
145        let pane_to_split = pane.clone();
146        match action {
147            Action::Move(from, item_id_to_move) => {
148                cx.window_context().defer(move |cx| {
149                    if let Some(workspace) = workspace.upgrade(cx) {
150                        workspace.update(cx, |workspace, cx| {
151                            workspace.split_pane_with_item(
152                                pane_to_split,
153                                split_direction,
154                                from,
155                                item_id_to_move,
156                                cx,
157                            );
158                        })
159                    }
160                });
161            }
162            Action::Open(project_entry) => {
163                cx.window_context().defer(move |cx| {
164                    if let Some(workspace) = workspace.upgrade(cx) {
165                        workspace.update(cx, |workspace, cx| {
166                            if let Some(task) = workspace.split_pane_with_project_entry(
167                                pane_to_split,
168                                split_direction,
169                                project_entry,
170                                cx,
171                            ) {
172                                task.detach_and_log_err(cx);
173                            }
174                        })
175                    }
176                });
177            }
178        };
179    } else {
180        match action {
181            Action::Move(from, item_id) => {
182                if pane != &from || allow_same_pane {
183                    let pane = pane.clone();
184                    cx.window_context().defer(move |cx| {
185                        if let Some(((workspace, from), to)) = workspace
186                            .upgrade(cx)
187                            .zip(from.upgrade(cx))
188                            .zip(pane.upgrade(cx))
189                        {
190                            workspace.update(cx, |workspace, cx| {
191                                workspace.move_item(from, to, item_id, index, cx);
192                            })
193                        }
194                    });
195                } else {
196                    cx.propagate_event();
197                }
198            }
199            Action::Open(project_entry) => {
200                let pane = pane.clone();
201                cx.window_context().defer(move |cx| {
202                    if let Some(workspace) = workspace.upgrade(cx) {
203                        workspace.update(cx, |workspace, cx| {
204                            if let Some(path) =
205                                workspace.project.read(cx).path_for_entry(project_entry, cx)
206                            {
207                                workspace
208                                    .open_path(path, Some(pane), true, cx)
209                                    .detach_and_log_err(cx);
210                            }
211                        });
212                    }
213                });
214            }
215        }
216    }
217}
218
219fn drop_split_direction(
220    position: Vector2F,
221    region: RectF,
222    split_margin: f32,
223) -> Option<SplitDirection> {
224    let mut min_direction = None;
225    let mut min_distance = split_margin;
226    for direction in SplitDirection::all() {
227        let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
228
229        if edge_distance < min_distance {
230            min_direction = Some(direction);
231            min_distance = edge_distance;
232        }
233    }
234
235    min_direction
236}
237
238fn overlay_color(cx: &AppContext) -> Color {
239    theme::current(cx).workspace.drop_target_overlay_color
240}