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, View, 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<Tag, 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_id())
 32            .map(|(drag_position, _)| drag_position)
 33            .or_else(|| {
 34                drag_and_drop
 35                    .currently_dragged::<ProjectEntryId>(cx.window_id())
 36                    .map(|(drag_position, _)| drag_position)
 37            })
 38    } else {
 39        None
 40    };
 41
 42    let mut handler = MouseEventHandler::<Tag, _>::above(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.hovered() { drag_position } else { None };
 46        Stack::new()
 47            .with_child(render_child(state, cx))
 48            .with_children(drag_position.map(|drag_position| {
 49                Canvas::new(move |scene, bounds, _, _, cx| {
 50                    if bounds.contains_point(drag_position) {
 51                        let overlay_region = split_margin
 52                            .and_then(|split_margin| {
 53                                drop_split_direction(drag_position, bounds, split_margin)
 54                                    .map(|dir| (dir, split_margin))
 55                            })
 56                            .map(|(dir, margin)| dir.along_edge(bounds, margin))
 57                            .unwrap_or(bounds);
 58
 59                        scene.paint_stacking_context(None, None, |scene| {
 60                            scene.push_quad(Quad {
 61                                bounds: overlay_region,
 62                                background: Some(overlay_color(cx)),
 63                                border: Default::default(),
 64                                corner_radius: 0.,
 65                            });
 66                        });
 67                    }
 68                })
 69            }))
 70    });
 71
 72    if drag_position.is_some() {
 73        handler = handler
 74            .on_up(MouseButton::Left, {
 75                move |event, pane, cx| {
 76                    let workspace = pane.workspace.clone();
 77                    let pane = cx.weak_handle();
 78                    handle_dropped_item(
 79                        event,
 80                        workspace,
 81                        &pane,
 82                        drop_index,
 83                        allow_same_pane,
 84                        split_margin,
 85                        cx,
 86                    );
 87                    cx.notify();
 88                }
 89            })
 90            .on_move(|_, _, cx| {
 91                let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
 92
 93                if drag_and_drop
 94                    .currently_dragged::<DraggedItem>(cx.window_id())
 95                    .is_some()
 96                    || drag_and_drop
 97                        .currently_dragged::<ProjectEntryId>(cx.window_id())
 98                        .is_some()
 99                {
100                    cx.notify();
101                } else {
102                    cx.propagate_event();
103                }
104            })
105    }
106
107    handler
108}
109
110pub fn handle_dropped_item<V: View>(
111    event: MouseUp,
112    workspace: WeakViewHandle<Workspace>,
113    pane: &WeakViewHandle<Pane>,
114    index: usize,
115    allow_same_pane: bool,
116    split_margin: Option<f32>,
117    cx: &mut EventContext<V>,
118) {
119    enum Action {
120        Move(WeakViewHandle<Pane>, usize),
121        Open(ProjectEntryId),
122    }
123    let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
124    let action = if let Some((_, dragged_item)) =
125        drag_and_drop.currently_dragged::<DraggedItem>(cx.window_id())
126    {
127        Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
128    } else if let Some((_, project_entry)) =
129        drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id())
130    {
131        Action::Open(*project_entry)
132    } else {
133        cx.propagate_event();
134        return;
135    };
136
137    if let Some(split_direction) =
138        split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
139    {
140        let pane_to_split = pane.clone();
141        match action {
142            Action::Move(from, item_id_to_move) => {
143                cx.window_context().defer(move |cx| {
144                    if let Some(workspace) = workspace.upgrade(cx) {
145                        workspace.update(cx, |workspace, cx| {
146                            workspace.split_pane_with_item(
147                                pane_to_split,
148                                split_direction,
149                                from,
150                                item_id_to_move,
151                                cx,
152                            );
153                        })
154                    }
155                });
156            }
157            Action::Open(project_entry) => {
158                cx.window_context().defer(move |cx| {
159                    if let Some(workspace) = workspace.upgrade(cx) {
160                        workspace.update(cx, |workspace, cx| {
161                            if let Some(task) = workspace.split_pane_with_project_entry(
162                                pane_to_split,
163                                split_direction,
164                                project_entry,
165                                cx,
166                            ) {
167                                task.detach_and_log_err(cx);
168                            }
169                        })
170                    }
171                });
172            }
173        };
174    } else {
175        match action {
176            Action::Move(from, item_id) => {
177                if pane != &from || allow_same_pane {
178                    let pane = pane.clone();
179                    cx.window_context().defer(move |cx| {
180                        if let Some(((workspace, from), to)) = workspace
181                            .upgrade(cx)
182                            .zip(from.upgrade(cx))
183                            .zip(pane.upgrade(cx))
184                        {
185                            workspace.update(cx, |workspace, cx| {
186                                workspace.move_item(from, to, item_id, index, cx);
187                            })
188                        }
189                    });
190                } else {
191                    cx.propagate_event();
192                }
193            }
194            Action::Open(project_entry) => {
195                let pane = pane.clone();
196                cx.window_context().defer(move |cx| {
197                    if let Some(workspace) = workspace.upgrade(cx) {
198                        workspace.update(cx, |workspace, cx| {
199                            if let Some(path) =
200                                workspace.project.read(cx).path_for_entry(project_entry, cx)
201                            {
202                                workspace
203                                    .open_path(path, Some(pane), true, cx)
204                                    .detach_and_log_err(cx);
205                            }
206                        });
207                    }
208                });
209            }
210        }
211    }
212}
213
214fn drop_split_direction(
215    position: Vector2F,
216    region: RectF,
217    split_margin: f32,
218) -> Option<SplitDirection> {
219    let mut min_direction = None;
220    let mut min_distance = split_margin;
221    for direction in SplitDirection::all() {
222        let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
223
224        if edge_distance < min_distance {
225            min_direction = Some(direction);
226            min_distance = edge_distance;
227        }
228    }
229
230    min_direction
231}
232
233fn overlay_color(cx: &AppContext) -> Color {
234    theme::current(cx).workspace.drop_target_overlay_color
235}