dragged_item_receiver.rs

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