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
12enum State<V: View> {
13 Down {
14 region_offset: Vector2F,
15 region: RectF,
16 },
17 Dragging {
18 window_id: usize,
19 position: Vector2F,
20 region_offset: Vector2F,
21 region: RectF,
22 payload: Rc<dyn Any + 'static>,
23 render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
24 },
25 Canceled,
26}
27
28impl<V: View> Clone for State<V> {
29 fn clone(&self) -> Self {
30 match self {
31 &State::Down {
32 region_offset,
33 region,
34 } => State::Down {
35 region_offset,
36 region,
37 },
38 State::Dragging {
39 window_id,
40 position,
41 region_offset,
42 region,
43 payload,
44 render,
45 } => Self::Dragging {
46 window_id: window_id.clone(),
47 position: position.clone(),
48 region_offset: region_offset.clone(),
49 region: region.clone(),
50 payload: payload.clone(),
51 render: render.clone(),
52 },
53 State::Canceled => State::Canceled,
54 }
55 }
56}
57
58pub struct DragAndDrop<V: View> {
59 containers: HashSet<WeakViewHandle<V>>,
60 currently_dragged: Option<State<V>>,
61}
62
63impl<V: View> Default for DragAndDrop<V> {
64 fn default() -> Self {
65 Self {
66 containers: Default::default(),
67 currently_dragged: Default::default(),
68 }
69 }
70}
71
72impl<V: View> DragAndDrop<V> {
73 pub fn register_container(&mut self, handle: WeakViewHandle<V>) {
74 self.containers.insert(handle);
75 }
76
77 pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
78 self.currently_dragged.as_ref().and_then(|state| {
79 if let State::Dragging {
80 position,
81 payload,
82 window_id: window_dragged_from,
83 ..
84 } = state
85 {
86 if &window_id != window_dragged_from {
87 return None;
88 }
89
90 payload
91 .is::<T>()
92 .then(|| payload.clone().downcast::<T>().ok())
93 .flatten()
94 .map(|payload| (position.clone(), payload))
95 } else {
96 None
97 }
98 })
99 }
100
101 pub fn drag_started(event: MouseDown, cx: &mut EventContext) {
102 cx.update_global(|this: &mut Self, _| {
103 this.currently_dragged = Some(State::Down {
104 region_offset: event.region.origin() - event.position,
105 region: event.region,
106 });
107 })
108 }
109
110 pub fn dragging<T: Any>(
111 event: MouseDrag,
112 payload: Rc<T>,
113 cx: &mut EventContext,
114 render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
115 ) {
116 let window_id = cx.window_id();
117 cx.update_global(|this: &mut Self, cx| {
118 this.notify_containers_for_window(window_id, cx);
119
120 match this.currently_dragged.as_ref() {
121 Some(&State::Down {
122 region_offset,
123 region,
124 })
125 | Some(&State::Dragging {
126 region_offset,
127 region,
128 ..
129 }) => {
130 this.currently_dragged = Some(State::Dragging {
131 window_id,
132 region_offset,
133 region,
134 position: event.position,
135 payload,
136 render: Rc::new(move |payload, cx| {
137 render(payload.downcast_ref::<T>().unwrap(), cx)
138 }),
139 });
140 }
141 _ => {}
142 }
143 });
144 }
145
146 pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
147 enum DraggedElementHandler {}
148 cx.global::<Self>()
149 .currently_dragged
150 .clone()
151 .and_then(|state| {
152 match state {
153 State::Down { .. } => None,
154 State::Dragging {
155 window_id,
156 region_offset,
157 position,
158 region,
159 payload,
160 render,
161 } => {
162 if cx.window_id() != window_id {
163 return None;
164 }
165
166 let position = position + region_offset;
167 Some(
168 Overlay::new(
169 MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
170 render(payload, cx)
171 })
172 .with_cursor_style(CursorStyle::Arrow)
173 .on_up(MouseButton::Left, |_, cx| {
174 cx.defer(|cx| {
175 cx.update_global::<Self, _, _>(|this, cx| {
176 this.finish_dragging(cx)
177 });
178 });
179 cx.propagate_event();
180 })
181 .on_up_out(MouseButton::Left, |_, cx| {
182 cx.defer(|cx| {
183 cx.update_global::<Self, _, _>(|this, cx| {
184 this.finish_dragging(cx)
185 });
186 });
187 })
188 // Don't block hover events or invalidations
189 .with_hoverable(false)
190 .constrained()
191 .with_width(region.width())
192 .with_height(region.height())
193 .boxed(),
194 )
195 .with_anchor_position(position)
196 .boxed(),
197 )
198 }
199
200 State::Canceled => Some(
201 MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| {
202 Empty::new()
203 .constrained()
204 .with_width(0.)
205 .with_height(0.)
206 .boxed()
207 })
208 .on_up(MouseButton::Left, |_, cx| {
209 cx.defer(|cx| {
210 cx.update_global::<Self, _, _>(|this, _| {
211 this.currently_dragged = None;
212 });
213 });
214 })
215 .on_up_out(MouseButton::Left, |_, cx| {
216 cx.defer(|cx| {
217 cx.update_global::<Self, _, _>(|this, _| {
218 this.currently_dragged = None;
219 });
220 });
221 })
222 .boxed(),
223 ),
224 }
225 })
226 }
227
228 pub fn cancel_dragging<P: Any>(&mut self, cx: &mut MutableAppContext) {
229 if let Some(State::Dragging {
230 payload, window_id, ..
231 }) = &self.currently_dragged
232 {
233 if payload.is::<P>() {
234 let window_id = *window_id;
235 self.currently_dragged = Some(State::Canceled);
236 self.notify_containers_for_window(window_id, cx);
237 }
238 }
239 }
240
241 fn finish_dragging(&mut self, cx: &mut MutableAppContext) {
242 if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
243 self.notify_containers_for_window(window_id, cx);
244 }
245 }
246
247 fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut MutableAppContext) {
248 self.containers.retain(|container| {
249 if let Some(container) = container.upgrade(cx) {
250 if container.window_id() == window_id {
251 container.update(cx, |_, cx| cx.notify());
252 }
253 true
254 } else {
255 false
256 }
257 });
258 }
259}
260
261pub trait Draggable {
262 fn as_draggable<V: View, P: Any>(
263 self,
264 payload: P,
265 render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
266 ) -> Self
267 where
268 Self: Sized;
269}
270
271impl<Tag> Draggable for MouseEventHandler<Tag> {
272 fn as_draggable<V: View, P: Any>(
273 self,
274 payload: P,
275 render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
276 ) -> Self
277 where
278 Self: Sized,
279 {
280 let payload = Rc::new(payload);
281 let render = Rc::new(render);
282 self.on_down(MouseButton::Left, move |e, cx| {
283 cx.propagate_event();
284 DragAndDrop::<V>::drag_started(e, cx);
285 })
286 .on_drag(MouseButton::Left, move |e, cx| {
287 let payload = payload.clone();
288 let render = render.clone();
289 DragAndDrop::<V>::dragging(e, payload, cx, render)
290 })
291 }
292}