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}