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 |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 cx.scene().push_stacking_context(None, None);
64 let background = overlay_color(cx);
65 cx.scene().push_quad(Quad {
66 bounds: overlay_region,
67 background: Some(background),
68 border: Default::default(),
69 corner_radii: Default::default(),
70 });
71 cx.scene().pop_stacking_context();
72 }
73 })
74 }))
75 });
76
77 if drag_position.is_some() {
78 handler = handler
79 .on_up(MouseButton::Left, {
80 move |event, pane, cx| {
81 let workspace = pane.workspace.clone();
82 let pane = cx.weak_handle();
83 handle_dropped_item(
84 event,
85 workspace,
86 &pane,
87 drop_index,
88 allow_same_pane,
89 split_margin,
90 cx,
91 );
92 cx.notify();
93 }
94 })
95 .on_move(|_, _, cx| {
96 let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
97
98 if drag_and_drop
99 .currently_dragged::<DraggedItem>(cx.window())
100 .is_some()
101 || drag_and_drop
102 .currently_dragged::<ProjectEntryId>(cx.window())
103 .is_some()
104 {
105 cx.notify();
106 } else {
107 cx.propagate_event();
108 }
109 })
110 }
111
112 handler
113}
114
115pub fn handle_dropped_item<V: 'static>(
116 event: MouseUp,
117 workspace: WeakViewHandle<Workspace>,
118 pane: &WeakViewHandle<Pane>,
119 index: usize,
120 allow_same_pane: bool,
121 split_margin: Option<f32>,
122 cx: &mut EventContext<V>,
123) {
124 enum Action {
125 Move(WeakViewHandle<Pane>, usize),
126 Open(ProjectEntryId),
127 }
128 let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
129 let action = if let Some((_, dragged_item)) =
130 drag_and_drop.currently_dragged::<DraggedItem>(cx.window())
131 {
132 Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
133 } else if let Some((_, project_entry)) =
134 drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window())
135 {
136 Action::Open(*project_entry)
137 } else {
138 cx.propagate_event();
139 return;
140 };
141
142 if let Some(split_direction) =
143 split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
144 {
145 let pane_to_split = pane.clone();
146 match action {
147 Action::Move(from, item_id_to_move) => {
148 cx.window_context().defer(move |cx| {
149 if let Some(workspace) = workspace.upgrade(cx) {
150 workspace.update(cx, |workspace, cx| {
151 workspace.split_pane_with_item(
152 pane_to_split,
153 split_direction,
154 from,
155 item_id_to_move,
156 cx,
157 );
158 })
159 }
160 });
161 }
162 Action::Open(project_entry) => {
163 cx.window_context().defer(move |cx| {
164 if let Some(workspace) = workspace.upgrade(cx) {
165 workspace.update(cx, |workspace, cx| {
166 if let Some(task) = workspace.split_pane_with_project_entry(
167 pane_to_split,
168 split_direction,
169 project_entry,
170 cx,
171 ) {
172 task.detach_and_log_err(cx);
173 }
174 })
175 }
176 });
177 }
178 };
179 } else {
180 match action {
181 Action::Move(from, item_id) => {
182 if pane != &from || allow_same_pane {
183 let pane = pane.clone();
184 cx.window_context().defer(move |cx| {
185 if let Some(((workspace, from), to)) = workspace
186 .upgrade(cx)
187 .zip(from.upgrade(cx))
188 .zip(pane.upgrade(cx))
189 {
190 workspace.update(cx, |workspace, cx| {
191 workspace.move_item(from, to, item_id, index, cx);
192 })
193 }
194 });
195 } else {
196 cx.propagate_event();
197 }
198 }
199 Action::Open(project_entry) => {
200 let pane = pane.clone();
201 cx.window_context().defer(move |cx| {
202 if let Some(workspace) = workspace.upgrade(cx) {
203 workspace.update(cx, |workspace, cx| {
204 if let Some(path) =
205 workspace.project.read(cx).path_for_entry(project_entry, cx)
206 {
207 workspace
208 .open_path(path, Some(pane), true, cx)
209 .detach_and_log_err(cx);
210 }
211 });
212 }
213 });
214 }
215 }
216 }
217}
218
219fn drop_split_direction(
220 position: Vector2F,
221 region: RectF,
222 split_margin: f32,
223) -> Option<SplitDirection> {
224 let mut min_direction = None;
225 let mut min_distance = split_margin;
226 for direction in SplitDirection::all() {
227 let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
228
229 if edge_distance < min_distance {
230 min_direction = Some(direction);
231 min_distance = edge_distance;
232 }
233 }
234
235 min_direction
236}
237
238fn overlay_color(cx: &AppContext) -> Color {
239 theme::current(cx).workspace.drop_target_overlay_color
240}