dragged_item_receiver.rs

  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}