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 settings::Settings;
11
12use crate::{MoveItem, Pane, SplitDirection, SplitWithItem, Workspace};
13
14use super::DraggedItem;
15
16pub fn dragged_item_receiver<Tag, F>(
17 region_id: usize,
18 drop_index: usize,
19 allow_same_pane: bool,
20 split_margin: Option<f32>,
21 cx: &mut RenderContext<Pane>,
22 render_child: F,
23) -> MouseEventHandler<Tag>
24where
25 Tag: 'static,
26 F: FnOnce(&mut MouseState, &mut RenderContext<Pane>) -> ElementBox,
27{
28 MouseEventHandler::<Tag>::above(region_id, cx, |state, cx| {
29 // Observing hovered will cause a render when the mouse enters regardless
30 // of if mouse position was accessed before
31 let hovered = state.hovered();
32 let drag_position = cx
33 .global::<DragAndDrop<Workspace>>()
34 .currently_dragged::<DraggedItem>(cx.window_id())
35 .filter(|_| hovered)
36 .map(|(drag_position, _)| drag_position);
37
38 Stack::new()
39 .with_child(render_child(state, cx))
40 .with_children(drag_position.map(|drag_position| {
41 Canvas::new(move |bounds, _, cx| {
42 if bounds.contains_point(drag_position) {
43 let overlay_region = split_margin
44 .and_then(|split_margin| {
45 drop_split_direction(drag_position, bounds, split_margin)
46 .map(|dir| (dir, split_margin))
47 })
48 .map(|(dir, margin)| dir.along_edge(bounds, margin))
49 .unwrap_or(bounds);
50
51 cx.paint_stacking_context(None, None, |cx| {
52 cx.scene.push_quad(Quad {
53 bounds: overlay_region,
54 background: Some(overlay_color(cx)),
55 border: Default::default(),
56 corner_radius: 0.,
57 });
58 });
59 }
60 })
61 .boxed()
62 }))
63 .boxed()
64 })
65 .on_up(MouseButton::Left, {
66 let pane = cx.handle();
67 move |event, cx| {
68 handle_dropped_item(event, &pane, drop_index, allow_same_pane, split_margin, cx);
69 cx.notify();
70 }
71 })
72 .on_move(|_, cx| {
73 if cx
74 .global::<DragAndDrop<Workspace>>()
75 .currently_dragged::<DraggedItem>(cx.window_id())
76 .is_some()
77 {
78 cx.notify();
79 } else {
80 cx.propagate_event();
81 }
82 })
83}
84
85pub fn handle_dropped_item(
86 event: MouseUp,
87 pane: &WeakViewHandle<Pane>,
88 index: usize,
89 allow_same_pane: bool,
90 split_margin: Option<f32>,
91 cx: &mut EventContext,
92) {
93 if let Some((_, dragged_item)) = cx
94 .global::<DragAndDrop<Workspace>>()
95 .currently_dragged::<DraggedItem>(cx.window_id)
96 {
97 if let Some(split_direction) = split_margin
98 .and_then(|margin| drop_split_direction(event.position, event.region, margin))
99 {
100 cx.dispatch_action(SplitWithItem {
101 from: dragged_item.pane.clone(),
102 item_id_to_move: dragged_item.item.id(),
103 pane_to_split: pane.clone(),
104 split_direction,
105 });
106 } else if pane != &dragged_item.pane || allow_same_pane {
107 // If no split margin or not close enough to the edge, just move the item
108 cx.dispatch_action(MoveItem {
109 item_id: dragged_item.item.id(),
110 from: dragged_item.pane.clone(),
111 to: pane.clone(),
112 destination_index: index,
113 })
114 }
115 } else {
116 cx.propagate_event();
117 }
118}
119
120fn drop_split_direction(
121 position: Vector2F,
122 region: RectF,
123 split_margin: f32,
124) -> Option<SplitDirection> {
125 let mut min_direction = None;
126 let mut min_distance = split_margin;
127 for direction in SplitDirection::all() {
128 let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
129
130 if edge_distance < min_distance {
131 min_direction = Some(direction);
132 min_distance = edge_distance;
133 }
134 }
135
136 min_direction
137}
138
139fn overlay_color(cx: &AppContext) -> Color {
140 cx.global::<Settings>()
141 .theme
142 .workspace
143 .drop_target_overlay_color
144}