use drag_and_drop::DragAndDrop; use gpui::{ color::Color, elements::{Canvas, MouseEventHandler, ParentElement, Stack}, geometry::{rect::RectF, vector::Vector2F}, scene::MouseUp, AppContext, Element, ElementBox, EventContext, MouseButton, MouseState, Quad, RenderContext, WeakViewHandle, }; use project::ProjectEntryId; use settings::Settings; use crate::{ MoveItem, OpenProjectEntryInPane, Pane, SplitDirection, SplitWithItem, SplitWithProjectEntry, Workspace, }; use super::DraggedItem; pub fn dragged_item_receiver( region_id: usize, drop_index: usize, allow_same_pane: bool, split_margin: Option, cx: &mut RenderContext, render_child: F, ) -> MouseEventHandler where Tag: 'static, F: FnOnce(&mut MouseState, &mut RenderContext) -> ElementBox, { MouseEventHandler::::above(region_id, cx, |state, cx| { // Observing hovered will cause a render when the mouse enters regardless // of if mouse position was accessed before let drag_position = if state.hovered() { cx.global::>() .currently_dragged::(cx.window_id()) .map(|(drag_position, _)| drag_position) .or_else(|| { cx.global::>() .currently_dragged::(cx.window_id()) .map(|(drag_position, _)| drag_position) }) } else { None }; Stack::new() .with_child(render_child(state, cx)) .with_children(drag_position.map(|drag_position| { Canvas::new(move |bounds, _, cx| { if bounds.contains_point(drag_position) { let overlay_region = split_margin .and_then(|split_margin| { drop_split_direction(drag_position, bounds, split_margin) .map(|dir| (dir, split_margin)) }) .map(|(dir, margin)| dir.along_edge(bounds, margin)) .unwrap_or(bounds); cx.paint_stacking_context(None, None, |cx| { cx.scene.push_quad(Quad { bounds: overlay_region, background: Some(overlay_color(cx)), border: Default::default(), corner_radius: 0., }); }); } }) .boxed() })) .boxed() }) .on_up(MouseButton::Left, { let pane = cx.handle(); move |event, cx| { handle_dropped_item(event, &pane, drop_index, allow_same_pane, split_margin, cx); cx.notify(); } }) .on_move(|_, cx| { let drag_and_drop = cx.global::>(); if drag_and_drop .currently_dragged::(cx.window_id()) .is_some() || drag_and_drop .currently_dragged::(cx.window_id()) .is_some() { cx.notify(); } else { cx.propagate_event(); } }) } pub fn handle_dropped_item( event: MouseUp, pane: &WeakViewHandle, index: usize, allow_same_pane: bool, split_margin: Option, cx: &mut EventContext, ) { enum Action { Move(WeakViewHandle, usize), Open(ProjectEntryId), } let drag_and_drop = cx.global::>(); let action = if let Some((_, dragged_item)) = drag_and_drop.currently_dragged::(cx.window_id) { Action::Move(dragged_item.pane.clone(), dragged_item.item.id()) } else if let Some((_, project_entry)) = drag_and_drop.currently_dragged::(cx.window_id) { Action::Open(*project_entry) } else { cx.propagate_event(); return; }; if let Some(split_direction) = split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin)) { let pane_to_split = pane.clone(); match action { Action::Move(from, item_id_to_move) => cx.dispatch_action(SplitWithItem { from, item_id_to_move, pane_to_split, split_direction, }), Action::Open(project_entry) => cx.dispatch_action(SplitWithProjectEntry { pane_to_split, split_direction, project_entry, }), }; } else { match action { Action::Move(from, item_id) => { if pane != &from || allow_same_pane { cx.dispatch_action(MoveItem { item_id, from, to: pane.clone(), destination_index: index, }) } else { cx.propagate_event(); } } Action::Open(project_entry) => cx.dispatch_action(OpenProjectEntryInPane { pane: pane.clone(), project_entry, }), } } } fn drop_split_direction( position: Vector2F, region: RectF, split_margin: f32, ) -> Option { let mut min_direction = None; let mut min_distance = split_margin; for direction in SplitDirection::all() { let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs(); if edge_distance < min_distance { min_direction = Some(direction); min_distance = edge_distance; } } min_direction } fn overlay_color(cx: &AppContext) -> Color { cx.global::() .theme .workspace .drop_target_overlay_color }