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
 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 RenderContext<V>) -> ElementBox>,
 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 EventContext) {
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 EventContext,
127        render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
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 (dbg!(event.position) - (dbg!(region.origin() + region_offset))).length()
143                        > DEAD_ZONE
144                    {
145                        this.currently_dragged = Some(State::Dragging {
146                            window_id,
147                            region_offset,
148                            region,
149                            position: event.position,
150                            payload,
151                            render: Rc::new(move |payload, cx| {
152                                render(payload.downcast_ref::<T>().unwrap(), cx)
153                            }),
154                        });
155                    } else {
156                        this.currently_dragged = Some(State::DeadZone {
157                            region_offset,
158                            region,
159                        })
160                    }
161                }
162                Some(&State::Dragging {
163                    region_offset,
164                    region,
165                    ..
166                }) => {
167                    this.currently_dragged = Some(State::Dragging {
168                        window_id,
169                        region_offset,
170                        region,
171                        position: event.position,
172                        payload,
173                        render: Rc::new(move |payload, cx| {
174                            render(payload.downcast_ref::<T>().unwrap(), cx)
175                        }),
176                    });
177                }
178                _ => {}
179            }
180        });
181    }
182
183    pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
184        enum DraggedElementHandler {}
185        cx.global::<Self>()
186            .currently_dragged
187            .clone()
188            .and_then(|state| {
189                match state {
190                    State::Down { .. } => None,
191                    State::DeadZone { .. } => None,
192                    State::Dragging {
193                        window_id,
194                        region_offset,
195                        position,
196                        region,
197                        payload,
198                        render,
199                    } => {
200                        if cx.window_id() != window_id {
201                            return None;
202                        }
203
204                        let position = position - region_offset;
205                        Some(
206                            Overlay::new(
207                                MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
208                                    render(payload, cx)
209                                })
210                                .with_cursor_style(CursorStyle::Arrow)
211                                .on_up(MouseButton::Left, |_, cx| {
212                                    cx.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.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                                .boxed(),
232                            )
233                            .with_anchor_position(position)
234                            .boxed(),
235                        )
236                    }
237
238                    State::Canceled => Some(
239                        MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| {
240                            Empty::new()
241                                .constrained()
242                                .with_width(0.)
243                                .with_height(0.)
244                                .boxed()
245                        })
246                        .on_up(MouseButton::Left, |_, cx| {
247                            cx.defer(|cx| {
248                                cx.update_global::<Self, _, _>(|this, _| {
249                                    this.currently_dragged = None;
250                                });
251                            });
252                        })
253                        .on_up_out(MouseButton::Left, |_, cx| {
254                            cx.defer(|cx| {
255                                cx.update_global::<Self, _, _>(|this, _| {
256                                    this.currently_dragged = None;
257                                });
258                            });
259                        })
260                        .boxed(),
261                    ),
262                }
263            })
264    }
265
266    pub fn cancel_dragging<P: Any>(&mut self, cx: &mut MutableAppContext) {
267        if let Some(State::Dragging {
268            payload, window_id, ..
269        }) = &self.currently_dragged
270        {
271            if payload.is::<P>() {
272                let window_id = *window_id;
273                self.currently_dragged = Some(State::Canceled);
274                self.notify_containers_for_window(window_id, cx);
275            }
276        }
277    }
278
279    fn finish_dragging(&mut self, cx: &mut MutableAppContext) {
280        if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
281            self.notify_containers_for_window(window_id, cx);
282        }
283    }
284
285    fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut MutableAppContext) {
286        self.containers.retain(|container| {
287            if let Some(container) = container.upgrade(cx) {
288                if container.window_id() == window_id {
289                    container.update(cx, |_, cx| cx.notify());
290                }
291                true
292            } else {
293                false
294            }
295        });
296    }
297}
298
299pub trait Draggable {
300    fn as_draggable<V: View, P: Any>(
301        self,
302        payload: P,
303        render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
304    ) -> Self
305    where
306        Self: Sized;
307}
308
309impl<Tag> Draggable for MouseEventHandler<Tag> {
310    fn as_draggable<V: View, P: Any>(
311        self,
312        payload: P,
313        render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
314    ) -> Self
315    where
316        Self: Sized,
317    {
318        let payload = Rc::new(payload);
319        let render = Rc::new(render);
320        self.on_down(MouseButton::Left, move |e, cx| {
321            cx.propagate_event();
322            DragAndDrop::<V>::drag_started(e, cx);
323        })
324        .on_drag(MouseButton::Left, move |e, cx| {
325            let payload = payload.clone();
326            let render = render.clone();
327            DragAndDrop::<V>::dragging(e, payload, cx, render)
328        })
329    }
330}