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, 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<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())
32 .map(|(drag_position, _)| drag_position)
33 .or_else(|| {
34 drag_and_drop
35 .currently_dragged::<ProjectEntryId>(cx.window())
36 .map(|(drag_position, _)| drag_position)
37 })
38 } else {
39 None
40 };
41
42 let mut handler = MouseEventHandler::above::<Tag, _>(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.dragging() {
46 drag_position
47 } else {
48 None
49 };
50 Stack::new()
51 .with_child(render_child(state, cx))
52 .with_children(drag_position.map(|drag_position| {
53 Canvas::new(move |scene, bounds, _, _, cx| {
54 if bounds.contains_point(drag_position) {
55 let overlay_region = split_margin
56 .and_then(|split_margin| {
57 drop_split_direction(drag_position, bounds, split_margin)
58 .map(|dir| (dir, split_margin))
59 })
60 .map(|(dir, margin)| dir.along_edge(bounds, margin))
61 .unwrap_or(bounds);
62
63 scene.paint_stacking_context(None, None, |scene| {
64 scene.push_quad(Quad {
65 bounds: overlay_region,
66 background: Some(overlay_color(cx)),
67 border: Default::default(),
68 corner_radii: Default::default(),
69 });
70 });
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 theme::current(cx).workspace.drop_target_overlay_color
239}