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