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, ElementBox, EventContext, MouseState, Quad, RenderContext, 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}