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::{
14 MoveItem, OpenProjectEntryInPane, Pane, SplitDirection, SplitWithItem, SplitWithProjectEntry,
15 Workspace,
16};
17
18use super::DraggedItem;
19
20pub fn dragged_item_receiver<Tag, D, F>(
21 region_id: usize,
22 drop_index: usize,
23 allow_same_pane: bool,
24 split_margin: Option<f32>,
25 cx: &mut ViewContext<Pane>,
26 render_child: F,
27) -> MouseEventHandler<Tag, Pane>
28where
29 Tag: 'static,
30 D: Element<Pane>,
31 F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> D,
32{
33 MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
34 // Observing hovered will cause a render when the mouse enters regardless
35 // of if mouse position was accessed before
36 let drag_position = if state.hovered() {
37 cx.global::<DragAndDrop<Workspace>>()
38 .currently_dragged::<DraggedItem>(cx.window_id())
39 .map(|(drag_position, _)| drag_position)
40 .or_else(|| {
41 cx.global::<DragAndDrop<Workspace>>()
42 .currently_dragged::<ProjectEntryId>(cx.window_id())
43 .map(|(drag_position, _)| drag_position)
44 })
45 } else {
46 None
47 };
48
49 Stack::new()
50 .with_child(render_child(state, cx))
51 .with_children(drag_position.map(|drag_position| {
52 Canvas::new(move |scene, bounds, _, _, cx| {
53 if bounds.contains_point(drag_position) {
54 let overlay_region = split_margin
55 .and_then(|split_margin| {
56 drop_split_direction(drag_position, bounds, split_margin)
57 .map(|dir| (dir, split_margin))
58 })
59 .map(|(dir, margin)| dir.along_edge(bounds, margin))
60 .unwrap_or(bounds);
61
62 scene.paint_stacking_context(None, None, |scene| {
63 scene.push_quad(Quad {
64 bounds: overlay_region,
65 background: Some(overlay_color(cx)),
66 border: Default::default(),
67 corner_radius: 0.,
68 });
69 });
70 }
71 })
72 }))
73 })
74 .on_up(MouseButton::Left, {
75 move |event, _, cx| {
76 let pane = cx.weak_handle();
77 handle_dropped_item(event, &pane, drop_index, allow_same_pane, split_margin, cx);
78 cx.notify();
79 }
80 })
81 .on_move(|_, _, cx| {
82 let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
83
84 if drag_and_drop
85 .currently_dragged::<DraggedItem>(cx.window_id())
86 .is_some()
87 || drag_and_drop
88 .currently_dragged::<ProjectEntryId>(cx.window_id())
89 .is_some()
90 {
91 cx.notify();
92 } else {
93 cx.propagate_event();
94 }
95 })
96}
97
98pub fn handle_dropped_item<V: View>(
99 event: MouseUp,
100 pane: &WeakViewHandle<Pane>,
101 index: usize,
102 allow_same_pane: bool,
103 split_margin: Option<f32>,
104 cx: &mut EventContext<V>,
105) {
106 enum Action {
107 Move(WeakViewHandle<Pane>, usize),
108 Open(ProjectEntryId),
109 }
110 let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
111 let action = if let Some((_, dragged_item)) =
112 drag_and_drop.currently_dragged::<DraggedItem>(cx.window_id())
113 {
114 Action::Move(dragged_item.pane.clone(), dragged_item.item.id())
115 } else if let Some((_, project_entry)) =
116 drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id())
117 {
118 Action::Open(*project_entry)
119 } else {
120 cx.propagate_event();
121 return;
122 };
123
124 if let Some(split_direction) =
125 split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
126 {
127 let pane_to_split = pane.clone();
128 match action {
129 Action::Move(from, item_id_to_move) => cx.dispatch_action(SplitWithItem {
130 from,
131 item_id_to_move,
132 pane_to_split,
133 split_direction,
134 }),
135 Action::Open(project_entry) => cx.dispatch_action(SplitWithProjectEntry {
136 pane_to_split,
137 split_direction,
138 project_entry,
139 }),
140 };
141 } else {
142 match action {
143 Action::Move(from, item_id) => {
144 if pane != &from || allow_same_pane {
145 cx.dispatch_action(MoveItem {
146 item_id,
147 from,
148 to: pane.clone(),
149 destination_index: index,
150 })
151 } else {
152 cx.propagate_event();
153 }
154 }
155 Action::Open(project_entry) => cx.dispatch_action(OpenProjectEntryInPane {
156 pane: pane.clone(),
157 project_entry,
158 }),
159 }
160 }
161}
162
163fn drop_split_direction(
164 position: Vector2F,
165 region: RectF,
166 split_margin: f32,
167) -> Option<SplitDirection> {
168 let mut min_direction = None;
169 let mut min_distance = split_margin;
170 for direction in SplitDirection::all() {
171 let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
172
173 if edge_distance < min_distance {
174 min_direction = Some(direction);
175 min_distance = edge_distance;
176 }
177 }
178
179 min_direction
180}
181
182fn overlay_color(cx: &AppContext) -> Color {
183 cx.global::<Settings>()
184 .theme
185 .workspace
186 .drop_target_overlay_color
187}