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}