dragged_item_receiver.rs

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