drag_and_drop.rs

  1use std::{any::Any, rc::Rc};
  2
  3use collections::HashSet;
  4use gpui::{
  5    elements::{Empty, MouseEventHandler, Overlay},
  6    geometry::{rect::RectF, vector::Vector2F},
  7    scene::{MouseDown, MouseDrag},
  8    CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
  9    View, WeakViewHandle,
 10};
 11
 12enum State<V: View> {
 13    Down {
 14        region_offset: Vector2F,
 15        region: RectF,
 16    },
 17    Dragging {
 18        window_id: usize,
 19        position: Vector2F,
 20        region_offset: Vector2F,
 21        region: RectF,
 22        payload: Rc<dyn Any + 'static>,
 23        render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
 24    },
 25    Canceled,
 26}
 27
 28impl<V: View> Clone for State<V> {
 29    fn clone(&self) -> Self {
 30        match self {
 31            &State::Down {
 32                region_offset,
 33                region,
 34            } => State::Down {
 35                region_offset,
 36                region,
 37            },
 38            State::Dragging {
 39                window_id,
 40                position,
 41                region_offset,
 42                region,
 43                payload,
 44                render,
 45            } => Self::Dragging {
 46                window_id: window_id.clone(),
 47                position: position.clone(),
 48                region_offset: region_offset.clone(),
 49                region: region.clone(),
 50                payload: payload.clone(),
 51                render: render.clone(),
 52            },
 53            State::Canceled => State::Canceled,
 54        }
 55    }
 56}
 57
 58pub struct DragAndDrop<V: View> {
 59    containers: HashSet<WeakViewHandle<V>>,
 60    currently_dragged: Option<State<V>>,
 61}
 62
 63impl<V: View> Default for DragAndDrop<V> {
 64    fn default() -> Self {
 65        Self {
 66            containers: Default::default(),
 67            currently_dragged: Default::default(),
 68        }
 69    }
 70}
 71
 72impl<V: View> DragAndDrop<V> {
 73    pub fn register_container(&mut self, handle: WeakViewHandle<V>) {
 74        self.containers.insert(handle);
 75    }
 76
 77    pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
 78        self.currently_dragged.as_ref().and_then(|state| {
 79            if let State::Dragging {
 80                position,
 81                payload,
 82                window_id: window_dragged_from,
 83                ..
 84            } = state
 85            {
 86                if &window_id != window_dragged_from {
 87                    return None;
 88                }
 89
 90                payload
 91                    .is::<T>()
 92                    .then(|| payload.clone().downcast::<T>().ok())
 93                    .flatten()
 94                    .map(|payload| (position.clone(), payload))
 95            } else {
 96                None
 97            }
 98        })
 99    }
100
101    pub fn drag_started(event: MouseDown, cx: &mut EventContext) {
102        cx.update_global(|this: &mut Self, _| {
103            this.currently_dragged = Some(State::Down {
104                region_offset: event.region.origin() - event.position,
105                region: event.region,
106            });
107        })
108    }
109
110    pub fn dragging<T: Any>(
111        event: MouseDrag,
112        payload: Rc<T>,
113        cx: &mut EventContext,
114        render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
115    ) {
116        let window_id = cx.window_id();
117        cx.update_global(|this: &mut Self, cx| {
118            this.notify_containers_for_window(window_id, cx);
119
120            match this.currently_dragged.as_ref() {
121                Some(&State::Down {
122                    region_offset,
123                    region,
124                })
125                | Some(&State::Dragging {
126                    region_offset,
127                    region,
128                    ..
129                }) => {
130                    this.currently_dragged = Some(State::Dragging {
131                        window_id,
132                        region_offset,
133                        region,
134                        position: event.position,
135                        payload,
136                        render: Rc::new(move |payload, cx| {
137                            render(payload.downcast_ref::<T>().unwrap(), cx)
138                        }),
139                    });
140                }
141                _ => {}
142            }
143        });
144    }
145
146    pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
147        enum DraggedElementHandler {}
148        cx.global::<Self>()
149            .currently_dragged
150            .clone()
151            .and_then(|state| {
152                match state {
153                    State::Down { .. } => None,
154                    State::Dragging {
155                        window_id,
156                        region_offset,
157                        position,
158                        region,
159                        payload,
160                        render,
161                    } => {
162                        if cx.window_id() != window_id {
163                            return None;
164                        }
165
166                        let position = position + region_offset;
167                        Some(
168                            Overlay::new(
169                                MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
170                                    render(payload, cx)
171                                })
172                                .with_cursor_style(CursorStyle::Arrow)
173                                .on_up(MouseButton::Left, |_, cx| {
174                                    cx.defer(|cx| {
175                                        cx.update_global::<Self, _, _>(|this, cx| {
176                                            this.finish_dragging(cx)
177                                        });
178                                    });
179                                    cx.propagate_event();
180                                })
181                                .on_up_out(MouseButton::Left, |_, cx| {
182                                    cx.defer(|cx| {
183                                        cx.update_global::<Self, _, _>(|this, cx| {
184                                            this.finish_dragging(cx)
185                                        });
186                                    });
187                                })
188                                // Don't block hover events or invalidations
189                                .with_hoverable(false)
190                                .constrained()
191                                .with_width(region.width())
192                                .with_height(region.height())
193                                .boxed(),
194                            )
195                            .with_anchor_position(position)
196                            .boxed(),
197                        )
198                    }
199
200                    State::Canceled => Some(
201                        MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| {
202                            Empty::new()
203                                .constrained()
204                                .with_width(0.)
205                                .with_height(0.)
206                                .boxed()
207                        })
208                        .on_up(MouseButton::Left, |_, cx| {
209                            cx.defer(|cx| {
210                                cx.update_global::<Self, _, _>(|this, _| {
211                                    this.currently_dragged = None;
212                                });
213                            });
214                        })
215                        .on_up_out(MouseButton::Left, |_, cx| {
216                            cx.defer(|cx| {
217                                cx.update_global::<Self, _, _>(|this, _| {
218                                    this.currently_dragged = None;
219                                });
220                            });
221                        })
222                        .boxed(),
223                    ),
224                }
225            })
226    }
227
228    pub fn cancel_dragging<P: Any>(&mut self, cx: &mut MutableAppContext) {
229        if let Some(State::Dragging {
230            payload, window_id, ..
231        }) = &self.currently_dragged
232        {
233            if payload.is::<P>() {
234                let window_id = *window_id;
235                self.currently_dragged = Some(State::Canceled);
236                self.notify_containers_for_window(window_id, cx);
237            }
238        }
239    }
240
241    fn finish_dragging(&mut self, cx: &mut MutableAppContext) {
242        if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
243            self.notify_containers_for_window(window_id, cx);
244        }
245    }
246
247    fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut MutableAppContext) {
248        self.containers.retain(|container| {
249            if let Some(container) = container.upgrade(cx) {
250                if container.window_id() == window_id {
251                    container.update(cx, |_, cx| cx.notify());
252                }
253                true
254            } else {
255                false
256            }
257        });
258    }
259}
260
261pub trait Draggable {
262    fn as_draggable<V: View, P: Any>(
263        self,
264        payload: P,
265        render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
266    ) -> Self
267    where
268        Self: Sized;
269}
270
271impl<Tag> Draggable for MouseEventHandler<Tag> {
272    fn as_draggable<V: View, P: Any>(
273        self,
274        payload: P,
275        render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
276    ) -> Self
277    where
278        Self: Sized,
279    {
280        let payload = Rc::new(payload);
281        let render = Rc::new(render);
282        self.on_down(MouseButton::Left, move |e, cx| {
283            cx.propagate_event();
284            DragAndDrop::<V>::drag_started(e, cx);
285        })
286        .on_drag(MouseButton::Left, move |e, cx| {
287            let payload = payload.clone();
288            let render = render.clone();
289            DragAndDrop::<V>::dragging(e, payload, cx, render)
290        })
291    }
292}