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}