drag_and_drop.rs

  1use std::{any::Any, rc::Rc};
  2
  3use collections::HashSet;
  4use gpui::{
  5    elements::{Container, MouseEventHandler},
  6    geometry::vector::Vector2F,
  7    scene::DragRegionEvent,
  8    CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
  9    View, WeakViewHandle,
 10};
 11
 12struct State<V: View> {
 13    window_id: usize,
 14    position: Vector2F,
 15    region_offset: Vector2F,
 16    payload: Rc<dyn Any + 'static>,
 17    render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
 18}
 19
 20impl<V: View> Clone for State<V> {
 21    fn clone(&self) -> Self {
 22        Self {
 23            window_id: self.window_id.clone(),
 24            position: self.position.clone(),
 25            region_offset: self.region_offset.clone(),
 26            payload: self.payload.clone(),
 27            render: self.render.clone(),
 28        }
 29    }
 30}
 31
 32pub struct DragAndDrop<V: View> {
 33    containers: HashSet<WeakViewHandle<V>>,
 34    currently_dragged: Option<State<V>>,
 35}
 36
 37impl<V: View> Default for DragAndDrop<V> {
 38    fn default() -> Self {
 39        Self {
 40            containers: Default::default(),
 41            currently_dragged: Default::default(),
 42        }
 43    }
 44}
 45
 46impl<V: View> DragAndDrop<V> {
 47    pub fn register_container(&mut self, handle: WeakViewHandle<V>) {
 48        self.containers.insert(handle);
 49    }
 50
 51    pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
 52        self.currently_dragged.as_ref().and_then(
 53            |State {
 54                 position,
 55                 payload,
 56                 window_id: window_dragged_from,
 57                 ..
 58             }| {
 59                if &window_id != window_dragged_from {
 60                    return None;
 61                }
 62
 63                payload
 64                    .clone()
 65                    .downcast::<T>()
 66                    .ok()
 67                    .map(|payload| (position.clone(), payload))
 68            },
 69        )
 70    }
 71
 72    pub fn dragging<T: Any>(
 73        event: DragRegionEvent,
 74        payload: Rc<T>,
 75        cx: &mut EventContext,
 76        render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
 77    ) {
 78        let window_id = cx.window_id();
 79        cx.update_global::<Self, _, _>(|this, cx| {
 80            let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() {
 81                previous_state.region_offset
 82            } else {
 83                event.region.origin() - event.prev_mouse_position
 84            };
 85
 86            this.currently_dragged = Some(State {
 87                window_id,
 88                region_offset,
 89                position: event.position,
 90                payload,
 91                render: Rc::new(move |payload, cx| {
 92                    render(payload.downcast_ref::<T>().unwrap(), cx)
 93                }),
 94            });
 95
 96            this.notify_containers_for_window(window_id, cx);
 97        });
 98    }
 99
100    pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
101        let currently_dragged = cx.global::<Self>().currently_dragged.clone();
102
103        currently_dragged.and_then(
104            |State {
105                 window_id,
106                 region_offset,
107                 position,
108                 payload,
109                 render,
110             }| {
111                if cx.window_id() != window_id {
112                    return None;
113                }
114
115                let position = position + region_offset;
116
117                Some(
118                    MouseEventHandler::new::<Self, _, _>(0, cx, |_, cx| {
119                        Container::new(render(payload, cx))
120                            .with_margin_left(position.x())
121                            .with_margin_top(position.y())
122                            .aligned()
123                            .top()
124                            .left()
125                            .boxed()
126                    })
127                    .with_cursor_style(CursorStyle::Arrow)
128                    .on_up(MouseButton::Left, |_, cx| {
129                        cx.defer(|cx| {
130                            cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
131                        });
132                        cx.propogate_event();
133                    })
134                    .on_up_out(MouseButton::Left, |_, cx| {
135                        cx.defer(|cx| {
136                            cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
137                        });
138                    })
139                    // Don't block hover events or invalidations
140                    .with_hoverable(false)
141                    .boxed(),
142                )
143            },
144        )
145    }
146
147    fn stop_dragging(&mut self, cx: &mut MutableAppContext) {
148        if let Some(State { window_id, .. }) = self.currently_dragged.take() {
149            self.notify_containers_for_window(window_id, cx);
150        }
151    }
152
153    fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut MutableAppContext) {
154        self.containers.retain(|container| {
155            if let Some(container) = container.upgrade(cx) {
156                if container.window_id() == window_id {
157                    container.update(cx, |_, cx| cx.notify());
158                }
159                true
160            } else {
161                false
162            }
163        });
164    }
165}
166
167pub trait Draggable {
168    fn as_draggable<V: View, P: Any>(
169        self,
170        payload: P,
171        render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
172    ) -> Self
173    where
174        Self: Sized;
175}
176
177impl Draggable for MouseEventHandler {
178    fn as_draggable<V: View, P: Any>(
179        self,
180        payload: P,
181        render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
182    ) -> Self
183    where
184        Self: Sized,
185    {
186        let payload = Rc::new(payload);
187        let render = Rc::new(render);
188        self.on_drag(MouseButton::Left, move |e, cx| {
189            let payload = payload.clone();
190            let render = render.clone();
191            DragAndDrop::<V>::dragging(e, payload, cx, render)
192        })
193    }
194}