use std::{any::Any, rc::Rc}; use collections::HashSet; use gpui::{ elements::{Empty, MouseEventHandler, Overlay}, geometry::{rect::RectF, vector::Vector2F}, platform::{CursorStyle, Modifiers, MouseButton}, scene::{MouseDown, MouseDrag}, AnyElement, AnyWindowHandle, 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 { modifiers: Modifiers, window: AnyWindowHandle, position: Vector2F, region_offset: Vector2F, region: RectF, payload: Rc, render: Rc, &mut ViewContext) -> AnyElement>, }, 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 { modifiers, window, position, region_offset, region, payload, render, } => Self::Dragging { window: window.clone(), position: position.clone(), region_offset: region_offset.clone(), region: region.clone(), payload: payload.clone(), render: render.clone(), modifiers: modifiers.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: AnyWindowHandle) -> Option<(Vector2F, Rc)> { self.currently_dragged.as_ref().and_then(|state| { if let State::Dragging { position, payload, window: window_dragged_from, .. } = state { if &window != window_dragged_from { return None; } payload .is::() .then(|| payload.clone().downcast::().ok()) .flatten() .map(|payload| (position.clone(), payload)) } else { None } }) } pub fn any_currently_dragged(&self, window: AnyWindowHandle) -> bool { self.currently_dragged .as_ref() .map(|state| { if let State::Dragging { window: window_dragged_from, .. } = state { if &window != window_dragged_from { return false; } true } else { false } }) .unwrap_or(false) } 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) -> AnyElement>, ) { let window = cx.window(); cx.update_global(|this: &mut Self, cx| { this.notify_containers_for_window(window, 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 { modifiers: event.modifiers, window, region_offset, region, position: event.position, payload, render: Rc::new(move |modifiers, payload, cx| { render(modifiers, payload.downcast_ref::().unwrap(), cx) }), }); } else { this.currently_dragged = Some(State::DeadZone { region_offset, region, }) } } Some(&State::Dragging { region_offset, region, modifiers, .. }) => { this.currently_dragged = Some(State::Dragging { modifiers, window, region_offset, region, position: event.position, payload, render: Rc::new(move |modifiers, payload, cx| { render(modifiers, payload.downcast_ref::().unwrap(), cx) }), }); } _ => {} } }); } pub fn update_modifiers(new_modifiers: Modifiers, cx: &mut ViewContext) -> bool { let result = cx.update_global(|this: &mut Self, _| match &mut this.currently_dragged { Some(state) => match state { State::Dragging { modifiers, .. } => { *modifiers = new_modifiers; true } _ => false, }, None => false, }); if result { cx.notify(); } result } 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 { modifiers, window, region_offset, position, region, payload, render, } => { if cx.window() != window { return None; } let position = (position - region_offset).round(); Some( Overlay::new( MouseEventHandler::new::( 0, cx, |_, cx| render(&modifiers, 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()), ) .with_anchor_position(position) .into_any(), ) } State::Canceled => Some( MouseEventHandler::new::(0, cx, |_, _| { Empty::new().constrained().with_width(0.).with_height(0.) }) .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; }); }); }) .into_any(), ), } }) } pub fn cancel_dragging(&mut self, cx: &mut WindowContext) { if let Some(State::Dragging { payload, window, .. }) = &self.currently_dragged { if payload.is::

() { let window = *window; self.currently_dragged = Some(State::Canceled); self.notify_containers_for_window(window, cx); } } } fn finish_dragging(&mut self, cx: &mut WindowContext) { if let Some(State::Dragging { window, .. }) = self.currently_dragged.take() { self.notify_containers_for_window(window, cx); } } fn notify_containers_for_window(&mut self, window: AnyWindowHandle, cx: &mut WindowContext) { self.containers.retain(|container| { if let Some(container) = container.upgrade(cx) { if container.window() == window { container.update(cx, |_, cx| cx.notify()); } true } else { false } }); } } pub trait Draggable { fn as_draggable( self, payload: P, render: impl 'static + Fn(&Modifiers, &P, &mut ViewContext) -> AnyElement, ) -> Self where Self: Sized; } impl Draggable for MouseEventHandler { fn as_draggable( self, payload: P, render: impl 'static + Fn(&Modifiers, &P, &mut ViewContext) -> AnyElement, ) -> 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| { if e.end { cx.update_global::, _, _>(|drag_and_drop, cx| { drag_and_drop.finish_dragging(cx) }) } else { let payload = payload.clone(); let render = render.clone(); DragAndDrop::::dragging(e, payload, cx, render) } }) } }