use std::{any::Any, rc::Rc}; use collections::HashSet; use gpui::{ elements::{Empty, MouseEventHandler, Overlay}, geometry::{rect::RectF, vector::Vector2F}, platform::{CursorStyle, MouseButton}, scene::{MouseDown, MouseDrag}, Drawable, Element, View, ViewContext, WeakViewHandle, WindowContext, }; const DEAD_ZONE: f32 = 4.; enum State { Down { region_offset: Vector2F, region: RectF, }, DeadZone { region_offset: Vector2F, region: RectF, }, Dragging { window_id: usize, position: Vector2F, region_offset: Vector2F, region: RectF, payload: Rc, render: Rc, &mut ViewContext) -> Element>, }, Canceled, } impl Clone for State { fn clone(&self) -> Self { match self { &State::Down { region_offset, region, } => State::Down { region_offset, region, }, &State::DeadZone { region_offset, region, } => State::DeadZone { region_offset, region, }, State::Dragging { window_id, position, region_offset, region, payload, render, } => Self::Dragging { window_id: window_id.clone(), position: position.clone(), region_offset: region_offset.clone(), region: region.clone(), payload: payload.clone(), render: render.clone(), }, State::Canceled => State::Canceled, } } } 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| { if let State::Dragging { position, payload, window_id: window_dragged_from, .. } = state { if &window_id != window_dragged_from { return None; } payload .is::() .then(|| payload.clone().downcast::().ok()) .flatten() .map(|payload| (position.clone(), payload)) } else { None } }) } pub fn drag_started(event: MouseDown, cx: &mut WindowContext) { cx.update_global(|this: &mut Self, _| { this.currently_dragged = Some(State::Down { region_offset: event.position - event.region.origin(), region: event.region, }); }) } pub fn dragging( event: MouseDrag, payload: Rc, cx: &mut WindowContext, render: Rc) -> Element>, ) { let window_id = cx.window_id(); cx.update_global(|this: &mut Self, cx| { this.notify_containers_for_window(window_id, cx); match this.currently_dragged.as_ref() { Some(&State::Down { region_offset, region, }) | Some(&State::DeadZone { region_offset, region, }) => { if (event.position - (region.origin() + region_offset)).length() > DEAD_ZONE { this.currently_dragged = Some(State::Dragging { window_id, region_offset, region, position: event.position, payload, render: Rc::new(move |payload, cx| { render(payload.downcast_ref::().unwrap(), cx) }), }); } else { this.currently_dragged = Some(State::DeadZone { region_offset, region, }) } } Some(&State::Dragging { region_offset, region, .. }) => { this.currently_dragged = Some(State::Dragging { window_id, region_offset, region, position: event.position, payload, render: Rc::new(move |payload, cx| { render(payload.downcast_ref::().unwrap(), cx) }), }); } _ => {} } }); } pub fn render(cx: &mut ViewContext) -> Option> { enum DraggedElementHandler {} cx.global::() .currently_dragged .clone() .and_then(|state| { match state { State::Down { .. } => None, State::DeadZone { .. } => None, State::Dragging { window_id, region_offset, position, region, payload, render, } => { if cx.window_id() != window_id { return None; } let position = position - region_offset; Some( Overlay::new( MouseEventHandler::::new( 0, cx, |_, cx| render(payload, cx), ) .with_cursor_style(CursorStyle::Arrow) .on_up(MouseButton::Left, |_, _, cx| { cx.window_context().defer(|cx| { cx.update_global::(|this, cx| { this.finish_dragging(cx) }); }); cx.propagate_event(); }) .on_up_out(MouseButton::Left, |_, _, cx| { cx.window_context().defer(|cx| { cx.update_global::(|this, cx| { this.finish_dragging(cx) }); }); }) // Don't block hover events or invalidations .with_hoverable(false) .constrained() .with_width(region.width()) .with_height(region.height()) .boxed(), ) .with_anchor_position(position) .boxed(), ) } State::Canceled => Some( MouseEventHandler::::new(0, cx, |_, _| { Empty::new() .constrained() .with_width(0.) .with_height(0.) .boxed() }) .on_up(MouseButton::Left, |_, _, cx| { cx.window_context().defer(|cx| { cx.update_global::(|this, _| { this.currently_dragged = None; }); }); }) .on_up_out(MouseButton::Left, |_, _, cx| { cx.window_context().defer(|cx| { cx.update_global::(|this, _| { this.currently_dragged = None; }); }); }) .boxed(), ), } }) } pub fn cancel_dragging(&mut self, cx: &mut WindowContext) { if let Some(State::Dragging { payload, window_id, .. }) = &self.currently_dragged { if payload.is::

() { let window_id = *window_id; self.currently_dragged = Some(State::Canceled); self.notify_containers_for_window(window_id, cx); } } } fn finish_dragging(&mut self, cx: &mut WindowContext) { if let Some(State::Dragging { 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 WindowContext) { 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 ViewContext) -> Element, ) -> Self where Self: Sized; } impl Draggable for MouseEventHandler { fn as_draggable( self, payload: P, render: impl 'static + Fn(&P, &mut ViewContext) -> Element, ) -> Self where Self: Sized, { let payload = Rc::new(payload); let render = Rc::new(render); self.on_down(MouseButton::Left, move |e, _, cx| { cx.propagate_event(); DragAndDrop::::drag_started(e, cx); }) .on_drag(MouseButton::Left, move |e, _, cx| { let payload = payload.clone(); let render = render.clone(); DragAndDrop::::dragging(e, payload, cx, render) }) } }