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