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}