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    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}