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}