1use std::{cell::RefCell, rc::Rc};
18
19use gpui::{
20 AnyElement, App, Axis, Bounds, Element, ElementId, GlobalElementId, InspectorElementId,
21 IntoElement, LayoutId, Pixels, Point, Render, SharedString, StatefulInteractiveElement, Window,
22 div, point, prelude::*, px,
23};
24use theme::{TextStyle, Theme, Typeset};
25
26use crate::surface::{Surface, Surfaced};
27
28pub trait Carried: Styled + IntoElement + Sized {
30 fn ghost(self, theme: &Theme) -> Surface {
33 self.surface(theme, theme.carried_surface)
34 }
35}
36
37impl<E: Styled + IntoElement> Carried for E {}
38
39pub fn ghost_label(label: impl Into<SharedString>, theme: &Theme) -> Surface {
41 div()
42 .rounded(px(Theme::control_radius()))
43 .px(px(10.0))
44 .py(px(4.0))
45 .text_style(TextStyle::Callout)
46 .text_color(theme.text)
47 .truncate()
48 .child(label.into())
49 .ghost(theme)
50}
51
52pub fn file_drop_wash(theme: &Theme) -> gpui::Div {
56 let wash = theme.drop_target;
57 div()
58 .absolute()
59 .inset_0()
60 .drag_over::<gpui::ExternalPaths>(move |style, _, _, _| style.bg(wash))
61}
62
63mod element;
64mod state;
65
66pub use element::{Handle, Region};
67pub(crate) use state::Gesture;
68use state::State;
69
70pub struct Carry<I> {
72 item: I,
73 pub(crate) gesture: Rc<Gesture>,
74}
75
76impl<I> Carry<I> {
77 pub fn item(&self) -> &I {
78 &self.item
79 }
80
81 pub fn claimed(&self) -> bool {
84 self.gesture.claimed.get()
85 }
86
87 pub fn ghost_bounds(&self, pointer: Point<Pixels>) -> gpui::Bounds<Pixels> {
90 self.gesture.ghost_bounds(pointer)
91 }
92
93 pub fn host(&self, origin: Point<Pixels>) {
96 self.gesture.host(origin);
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
104pub struct Drop<R, I> {
105 pub item: I,
106 pub from: R,
107 pub region: R,
108 pub after: Option<I>,
109 pub before: Option<I>,
110}
111
112#[derive(Clone, Debug)]
114pub struct Outside<I> {
115 pub item: I,
116 pub position: Point<Pixels>,
117}
118
119#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
121pub enum Feedback {
122 #[default]
126 Displace,
127 Indicator,
129}
130
131type Ghost<I> = dyn Fn(&I, &mut Window, &mut App) -> AnyElement;
132type Paint = dyn Fn(&mut Window, &mut App) -> AnyElement;
133
134pub struct Domain<R, I>(Rc<RefCell<State<R, I>>>);
136
137impl<R, I> Clone for Domain<R, I> {
138 fn clone(&self) -> Self {
139 Self(self.0.clone())
140 }
141}
142
143impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Default for Domain<R, I> {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Domain<R, I> {
150 pub fn new() -> Self {
152 Self(Rc::new(RefCell::new(State::new(None))))
153 }
154
155 pub fn with_ghost(ghost: impl Fn(&I, &mut Window, &mut App) -> AnyElement + 'static) -> Self {
159 Self(Rc::new(RefCell::new(State::new(Some(Rc::new(ghost))))))
160 }
161
162 pub fn region(
164 &self,
165 element_id: impl Into<ElementId>,
166 id: R,
167 axis: Axis,
168 child: impl IntoElement,
169 ) -> Region<R, I> {
170 self.0.borrow_mut().begin_frame();
171 Region::new(
172 self.clone(),
173 element_id.into(),
174 id,
175 axis,
176 child.into_any_element(),
177 )
178 }
179
180 pub fn handle<E>(&self, item: I, element: E) -> Handle<R, I>
186 where
187 E: StatefulInteractiveElement + IntoElement + 'static,
188 {
189 let domain = self.clone();
190 let carry = Carry {
191 item: item.clone(),
192 gesture: Rc::new(Gesture::default()),
193 };
194 let element = element.on_drag(carry, move |carry, grab, window, cx| {
195 domain.start(carry, grab, window, cx)
196 });
197 Handle::new(self.clone(), item, element.into_any_element())
198 }
199
200 pub fn fixed(&self, item: I, element: impl IntoElement) -> Handle<R, I> {
203 Handle::new(self.clone(), item, element.into_any_element())
204 }
205
206 pub fn carries(&self, item: &I) -> bool {
208 self.0
209 .borrow()
210 .live
211 .as_ref()
212 .is_some_and(|live| &live.item == item)
213 }
214
215 pub fn cancel(&self, window: &mut Window, cx: &mut App) {
218 let live = self.0.borrow_mut().end(window, cx);
219 if live.is_some() {
220 cx.stop_active_drag(window);
221 }
222 }
223
224 pub fn carried(&self) -> Option<I> {
226 self.0.borrow().live.as_ref().map(|live| live.item.clone())
227 }
228
229 pub(crate) fn invalidate(&self) {
231 self.0.borrow_mut().stale = true;
232 }
233
234 fn start(
235 &self,
236 carry: &Carry<I>,
237 cursor: Point<Pixels>,
238 window: &mut Window,
239 cx: &mut App,
240 ) -> gpui::Entity<Ghosted> {
241 let (grab, ghost) = {
242 let mut state = self.0.borrow_mut();
243 let grab = state.start(&carry.item, carry.gesture.clone(), cursor, window, cx);
244 (grab, state.ghost.clone())
245 };
246 carry.gesture.ghosted.set(ghost.is_some());
247 let item = carry.item.clone();
248 let gesture = carry.gesture.clone();
249 cx.new(|_| Ghosted {
250 gesture,
251 cursor,
252 grab,
253 render: ghost.map(|ghost| {
254 Rc::new(move |window: &mut Window, cx: &mut App| ghost(&item, window, cx))
255 as Rc<Paint>
256 }),
257 })
258 }
259}
260
261pub struct Ghosted {
263 gesture: Rc<Gesture>,
264 cursor: Point<Pixels>,
266 grab: Point<Pixels>,
268 render: Option<Rc<Paint>>,
269}
270
271impl Render for Ghosted {
272 fn render(&mut self, window: &mut Window, cx: &mut gpui::Context<Self>) -> impl IntoElement {
273 let hosted = self.gesture.hosted.get() && !self.gesture.claimed.get();
274 match self.render.as_ref().filter(|_| !hosted) {
275 Some(render) => {
276 let gesture = self.gesture.clone();
277 gpui::div()
280 .child(AtGrab {
281 cursor: self.cursor,
282 grab: self.grab,
283 child: gpui::div()
284 .child(render(window, cx))
285 .child(
286 gpui::canvas(
287 move |bounds, _, _| gesture.shown.set(Some(bounds.origin)),
288 |_, _, _, _| {},
289 )
290 .absolute()
291 .top_0()
292 .left_0()
293 .size_full(),
294 )
295 .into_any_element(),
296 })
297 .into_any_element()
298 }
299 None => gpui::Empty.into_any_element(),
300 }
301 }
302}
303
304struct AtGrab {
307 cursor: Point<Pixels>,
308 grab: Point<Pixels>,
309 child: AnyElement,
310}
311
312impl IntoElement for AtGrab {
313 type Element = Self;
314
315 fn into_element(self) -> Self {
316 self
317 }
318}
319
320impl Element for AtGrab {
321 type RequestLayoutState = ();
322 type PrepaintState = ();
323
324 fn id(&self) -> Option<ElementId> {
325 None
326 }
327
328 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
329 None
330 }
331
332 fn request_layout(
333 &mut self,
334 _id: Option<&GlobalElementId>,
335 _inspector_id: Option<&InspectorElementId>,
336 window: &mut Window,
337 cx: &mut App,
338 ) -> (LayoutId, ()) {
339 (self.child.request_layout(window, cx), ())
340 }
341
342 fn prepaint(
343 &mut self,
344 _id: Option<&GlobalElementId>,
345 _inspector_id: Option<&InspectorElementId>,
346 bounds: Bounds<Pixels>,
347 _request_layout: &mut (),
348 window: &mut Window,
349 cx: &mut App,
350 ) {
351 let grab = point(
352 self.grab.x.clamp(Pixels::ZERO, bounds.size.width),
353 self.grab.y.clamp(Pixels::ZERO, bounds.size.height),
354 );
355 window.with_element_offset(self.cursor - grab, |window| self.child.prepaint(window, cx));
356 }
357
358 fn paint(
359 &mut self,
360 _id: Option<&GlobalElementId>,
361 _inspector_id: Option<&InspectorElementId>,
362 _bounds: Bounds<Pixels>,
363 _request_layout: &mut (),
364 _prepaint: &mut (),
365 window: &mut Window,
366 cx: &mut App,
367 ) {
368 self.child.paint(window, cx);
369 }
370}