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 (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 RenderContext<V>) -> Option<ElementBox> {
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;
203 Some(
204 Overlay::new(
205 MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
206 render(payload, cx)
207 })
208 .with_cursor_style(CursorStyle::Arrow)
209 .on_up(MouseButton::Left, |_, cx| {
210 cx.defer(|cx| {
211 cx.update_global::<Self, _, _>(|this, cx| {
212 this.finish_dragging(cx)
213 });
214 });
215 cx.propagate_event();
216 })
217 .on_up_out(MouseButton::Left, |_, cx| {
218 cx.defer(|cx| {
219 cx.update_global::<Self, _, _>(|this, cx| {
220 this.finish_dragging(cx)
221 });
222 });
223 })
224 // Don't block hover events or invalidations
225 .with_hoverable(false)
226 .constrained()
227 .with_width(region.width())
228 .with_height(region.height())
229 .boxed(),
230 )
231 .with_anchor_position(position)
232 .boxed(),
233 )
234 }
235
236 State::Canceled => Some(
237 MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| {
238 Empty::new()
239 .constrained()
240 .with_width(0.)
241 .with_height(0.)
242 .boxed()
243 })
244 .on_up(MouseButton::Left, |_, cx| {
245 cx.defer(|cx| {
246 cx.update_global::<Self, _, _>(|this, _| {
247 this.currently_dragged = None;
248 });
249 });
250 })
251 .on_up_out(MouseButton::Left, |_, cx| {
252 cx.defer(|cx| {
253 cx.update_global::<Self, _, _>(|this, _| {
254 this.currently_dragged = None;
255 });
256 });
257 })
258 .boxed(),
259 ),
260 }
261 })
262 }
263
264 pub fn cancel_dragging<P: Any>(&mut self, cx: &mut MutableAppContext) {
265 if let Some(State::Dragging {
266 payload, window_id, ..
267 }) = &self.currently_dragged
268 {
269 if payload.is::<P>() {
270 let window_id = *window_id;
271 self.currently_dragged = Some(State::Canceled);
272 self.notify_containers_for_window(window_id, cx);
273 }
274 }
275 }
276
277 fn finish_dragging(&mut self, cx: &mut MutableAppContext) {
278 if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
279 self.notify_containers_for_window(window_id, cx);
280 }
281 }
282
283 fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut MutableAppContext) {
284 self.containers.retain(|container| {
285 if let Some(container) = container.upgrade(cx) {
286 if container.window_id() == window_id {
287 container.update(cx, |_, cx| cx.notify());
288 }
289 true
290 } else {
291 false
292 }
293 });
294 }
295}
296
297pub trait Draggable {
298 fn as_draggable<V: View, P: Any>(
299 self,
300 payload: P,
301 render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
302 ) -> Self
303 where
304 Self: Sized;
305}
306
307impl<Tag> Draggable for MouseEventHandler<Tag> {
308 fn as_draggable<V: View, P: Any>(
309 self,
310 payload: P,
311 render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
312 ) -> Self
313 where
314 Self: Sized,
315 {
316 let payload = Rc::new(payload);
317 let render = Rc::new(render);
318 self.on_down(MouseButton::Left, move |e, cx| {
319 cx.propagate_event();
320 DragAndDrop::<V>::drag_started(e, cx);
321 })
322 .on_drag(MouseButton::Left, move |e, cx| {
323 let payload = payload.clone();
324 let render = render.clone();
325 DragAndDrop::<V>::dragging(e, payload, cx, render)
326 })
327 }
328}