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