use std::{any::Any, rc::Rc}; use collections::HashSet; use gpui::{ elements::{MouseEventHandler, Overlay}, geometry::vector::Vector2F, scene::DragRegionEvent, CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext, View, WeakViewHandle, }; struct State { window_id: usize, position: Vector2F, region_offset: Vector2F, payload: Rc, render: Rc, &mut RenderContext) -> ElementBox>, } impl Clone for State { fn clone(&self) -> Self { Self { window_id: self.window_id.clone(), position: self.position.clone(), region_offset: self.region_offset.clone(), payload: self.payload.clone(), render: self.render.clone(), } } } pub struct DragAndDrop { containers: HashSet>, currently_dragged: Option>, } impl Default for DragAndDrop { fn default() -> Self { Self { containers: Default::default(), currently_dragged: Default::default(), } } } impl DragAndDrop { pub fn register_container(&mut self, handle: WeakViewHandle) { self.containers.insert(handle); } pub fn currently_dragged(&self, window_id: usize) -> Option<(Vector2F, Rc)> { self.currently_dragged.as_ref().and_then( |State { position, payload, window_id: window_dragged_from, .. }| { if &window_id != window_dragged_from { return None; } payload .clone() .downcast::() .ok() .map(|payload| (position.clone(), payload)) }, ) } pub fn dragging( event: DragRegionEvent, payload: Rc, cx: &mut EventContext, render: Rc) -> ElementBox>, ) { let window_id = cx.window_id(); cx.update_global::(|this, cx| { let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() { previous_state.region_offset } else { event.region.origin() - event.prev_mouse_position }; this.currently_dragged = Some(State { window_id, region_offset, position: event.position, payload, render: Rc::new(move |payload, cx| { render(payload.downcast_ref::().unwrap(), cx) }), }); this.notify_containers_for_window(window_id, cx); }); } pub fn render(cx: &mut RenderContext) -> Option { let currently_dragged = cx.global::().currently_dragged.clone(); currently_dragged.and_then( |State { window_id, region_offset, position, payload, render, }| { if cx.window_id() != window_id { return None; } let position = position + region_offset; enum DraggedElementHandler {} Some( Overlay::new( MouseEventHandler::::new(0, cx, |_, cx| { render(payload, cx) }) .with_cursor_style(CursorStyle::Arrow) .on_up(MouseButton::Left, |_, cx| { cx.defer(|cx| { cx.update_global::(|this, cx| this.stop_dragging(cx)); }); cx.propogate_event(); }) .on_up_out(MouseButton::Left, |_, cx| { cx.defer(|cx| { cx.update_global::(|this, cx| this.stop_dragging(cx)); }); }) // Don't block hover events or invalidations .with_hoverable(false) .boxed(), ) .with_anchor_position(position) .boxed(), ) }, ) } fn stop_dragging(&mut self, cx: &mut MutableAppContext) { if let Some(State { window_id, .. }) = self.currently_dragged.take() { self.notify_containers_for_window(window_id, cx); } } fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut MutableAppContext) { self.containers.retain(|container| { if let Some(container) = container.upgrade(cx) { if container.window_id() == window_id { container.update(cx, |_, cx| cx.notify()); } true } else { false } }); } } pub trait Draggable { fn as_draggable( self, payload: P, render: impl 'static + Fn(&P, &mut RenderContext) -> ElementBox, ) -> Self where Self: Sized; } impl Draggable for MouseEventHandler { fn as_draggable( self, payload: P, render: impl 'static + Fn(&P, &mut RenderContext) -> ElementBox, ) -> Self where Self: Sized, { let payload = Rc::new(payload); let render = Rc::new(render); self.on_drag(MouseButton::Left, move |e, cx| { let payload = payload.clone(); let render = render.clone(); DragAndDrop::::dragging(e, payload, cx, render) }) } }