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