1use std::{any::Any, rc::Rc};
2
3use collections::HashSet;
4use gpui::{
5 elements::{Container, MouseEventHandler},
6 geometry::vector::Vector2F,
7 scene::DragRegionEvent,
8 CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
9 View, WeakViewHandle,
10};
11
12struct State<V: View> {
13 window_id: usize,
14 position: Vector2F,
15 region_offset: Vector2F,
16 payload: Rc<dyn Any + 'static>,
17 render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
18}
19
20impl<V: View> Clone for State<V> {
21 fn clone(&self) -> Self {
22 Self {
23 window_id: self.window_id.clone(),
24 position: self.position.clone(),
25 region_offset: self.region_offset.clone(),
26 payload: self.payload.clone(),
27 render: self.render.clone(),
28 }
29 }
30}
31
32pub struct DragAndDrop<V: View> {
33 containers: HashSet<WeakViewHandle<V>>,
34 currently_dragged: Option<State<V>>,
35}
36
37impl<V: View> Default for DragAndDrop<V> {
38 fn default() -> Self {
39 Self {
40 containers: Default::default(),
41 currently_dragged: Default::default(),
42 }
43 }
44}
45
46impl<V: View> DragAndDrop<V> {
47 pub fn register_container(&mut self, handle: WeakViewHandle<V>) {
48 self.containers.insert(handle);
49 }
50
51 pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
52 self.currently_dragged.as_ref().and_then(
53 |State {
54 position,
55 payload,
56 window_id: window_dragged_from,
57 ..
58 }| {
59 if &window_id != window_dragged_from {
60 return None;
61 }
62
63 payload
64 .clone()
65 .downcast::<T>()
66 .ok()
67 .map(|payload| (position.clone(), payload))
68 },
69 )
70 }
71
72 pub fn dragging<T: Any>(
73 event: DragRegionEvent,
74 payload: Rc<T>,
75 cx: &mut EventContext,
76 render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
77 ) {
78 let window_id = cx.window_id();
79 cx.update_global::<Self, _, _>(|this, cx| {
80 let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() {
81 previous_state.region_offset
82 } else {
83 event.region.origin() - event.prev_mouse_position
84 };
85
86 this.currently_dragged = Some(State {
87 window_id,
88 region_offset,
89 position: event.position,
90 payload,
91 render: Rc::new(move |payload, cx| {
92 render(payload.downcast_ref::<T>().unwrap(), cx)
93 }),
94 });
95
96 this.notify_containers_for_window(window_id, cx);
97 });
98 }
99
100 pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
101 let currently_dragged = cx.global::<Self>().currently_dragged.clone();
102
103 currently_dragged.and_then(
104 |State {
105 window_id,
106 region_offset,
107 position,
108 payload,
109 render,
110 }| {
111 if cx.window_id() != window_id {
112 return None;
113 }
114
115 let position = position + region_offset;
116
117 Some(
118 MouseEventHandler::new::<Self, _, _>(0, cx, |_, cx| {
119 Container::new(render(payload, cx))
120 .with_margin_left(position.x())
121 .with_margin_top(position.y())
122 .aligned()
123 .top()
124 .left()
125 .boxed()
126 })
127 .with_cursor_style(CursorStyle::Arrow)
128 .on_up(MouseButton::Left, |_, cx| {
129 cx.defer(|cx| {
130 cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
131 });
132 cx.propogate_event();
133 })
134 .on_up_out(MouseButton::Left, |_, cx| {
135 cx.defer(|cx| {
136 cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
137 });
138 })
139 // Don't block hover events or invalidations
140 .with_hoverable(false)
141 .boxed(),
142 )
143 },
144 )
145 }
146
147 fn stop_dragging(&mut self, cx: &mut MutableAppContext) {
148 if let Some(State { window_id, .. }) = self.currently_dragged.take() {
149 self.notify_containers_for_window(window_id, cx);
150 }
151 }
152
153 fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut MutableAppContext) {
154 self.containers.retain(|container| {
155 if let Some(container) = container.upgrade(cx) {
156 if container.window_id() == window_id {
157 container.update(cx, |_, cx| cx.notify());
158 }
159 true
160 } else {
161 false
162 }
163 });
164 }
165}
166
167pub trait Draggable {
168 fn as_draggable<V: View, P: Any>(
169 self,
170 payload: P,
171 render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
172 ) -> Self
173 where
174 Self: Sized;
175}
176
177impl Draggable for MouseEventHandler {
178 fn as_draggable<V: View, P: Any>(
179 self,
180 payload: P,
181 render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
182 ) -> Self
183 where
184 Self: Sized,
185 {
186 let payload = Rc::new(payload);
187 let render = Rc::new(render);
188 self.on_drag(MouseButton::Left, move |e, cx| {
189 let payload = payload.clone();
190 let render = render.clone();
191 DragAndDrop::<V>::dragging(e, payload, cx, render)
192 })
193 }
194}