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