1use std::{cell::RefCell, rc::Rc};
17
18use gpui::{
19 AnyElement, App, Axis, Bounds, Element, ElementId, GlobalElementId, InspectorElementId,
20 IntoElement, LayoutId, Pixels, Point, Render, SharedString, StatefulInteractiveElement, Window,
21 div, point, prelude::*, px,
22};
23use motion::Painter;
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> {
89 self.gesture.ghost_bounds(pointer)
90 }
91
92 pub fn host(&self, origin: Point<Pixels>) {
95 self.gesture.host(origin);
96 }
97}
98
99#[derive(Clone, Debug, PartialEq, Eq)]
103pub struct Drop<R, I> {
104 pub item: I,
105 pub from: R,
106 pub region: R,
107 pub after: Option<I>,
108 pub before: Option<I>,
109}
110
111#[derive(Clone, Debug)]
113pub struct Outside<I> {
114 pub item: I,
115 pub position: Point<Pixels>,
116}
117
118#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
120pub enum Feedback {
121 #[default]
123 Displace,
124 Indicator,
126}
127
128type Ghost<I> = dyn Fn(&I, &mut Window, &mut App) -> AnyElement;
129type Paint = dyn Fn(&mut Window, &mut App) -> AnyElement;
130
131pub struct Domain<R, I>(Rc<RefCell<State<R, I>>>);
133
134impl<R, I> Clone for Domain<R, I> {
135 fn clone(&self) -> Self {
136 Self(self.0.clone())
137 }
138}
139
140impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Domain<R, I> {
141 pub fn new(painter: Painter) -> Self {
143 Self(Rc::new(RefCell::new(State::new(painter, None))))
144 }
145
146 pub fn with_ghost(
150 painter: Painter,
151 ghost: impl Fn(&I, &mut Window, &mut App) -> AnyElement + 'static,
152 ) -> Self {
153 Self(Rc::new(RefCell::new(State::new(
154 painter,
155 Some(Rc::new(ghost)),
156 ))))
157 }
158
159 pub fn region(
161 &self,
162 element_id: impl Into<ElementId>,
163 id: R,
164 axis: Axis,
165 child: impl IntoElement,
166 ) -> Region<R, I> {
167 self.0.borrow_mut().begin_frame();
168 Region::new(
169 self.clone(),
170 element_id.into(),
171 id,
172 axis,
173 child.into_any_element(),
174 )
175 }
176
177 pub fn handle<E>(&self, item: I, element: E) -> Handle<R, I>
181 where
182 E: StatefulInteractiveElement + IntoElement + 'static,
183 {
184 let domain = self.clone();
185 let carry = Carry {
186 item: item.clone(),
187 gesture: Rc::new(Gesture::default()),
188 };
189 let element = element.on_drag(carry, move |carry, grab, window, cx| {
190 domain.start(carry, grab, window, cx)
191 });
192 Handle::new(self.clone(), item, element.into_any_element())
193 }
194
195 pub fn fixed(&self, item: I, element: impl IntoElement) -> Handle<R, I> {
198 Handle::new(self.clone(), item, element.into_any_element())
199 }
200
201 pub fn carries(&self, item: &I) -> bool {
203 self.0
204 .borrow()
205 .live
206 .as_ref()
207 .is_some_and(|live| &live.item == item)
208 }
209
210 pub fn cancel(&self, window: &mut Window, cx: &mut App) {
212 let live = self.0.borrow_mut().end(window, cx);
213 if live.is_some() {
214 cx.stop_active_drag(window);
215 }
216 }
217
218 pub fn carried(&self) -> Option<I> {
220 self.0.borrow().live.as_ref().map(|live| live.item.clone())
221 }
222
223 pub(crate) fn invalidate(&self) {
225 self.0.borrow_mut().stale = true;
226 }
227
228 fn start(
229 &self,
230 carry: &Carry<I>,
231 cursor: Point<Pixels>,
232 window: &mut Window,
233 cx: &mut App,
234 ) -> gpui::Entity<Ghosted> {
235 let (grab, ghost) = {
236 let mut state = self.0.borrow_mut();
237 let grab = state.start(&carry.item, carry.gesture.clone(), cursor, window, cx);
238 (grab, state.ghost.clone())
239 };
240 carry.gesture.ghosted.set(ghost.is_some());
241 let item = carry.item.clone();
242 let gesture = carry.gesture.clone();
243 cx.new(|_| Ghosted {
244 gesture,
245 cursor,
246 grab,
247 render: ghost.map(|ghost| {
248 Rc::new(move |window: &mut Window, cx: &mut App| ghost(&item, window, cx))
249 as Rc<Paint>
250 }),
251 })
252 }
253}
254
255pub struct Ghosted {
257 gesture: Rc<Gesture>,
258 cursor: Point<Pixels>,
260 grab: Point<Pixels>,
262 render: Option<Rc<Paint>>,
263}
264
265impl Render for Ghosted {
266 fn render(&mut self, window: &mut Window, cx: &mut gpui::Context<Self>) -> impl IntoElement {
267 let hosted = self.gesture.hosted.get() && !self.gesture.claimed.get();
268 match self.render.as_ref().filter(|_| !hosted) {
269 Some(render) => {
270 let gesture = self.gesture.clone();
271 gpui::div()
274 .child(AtGrab {
275 cursor: self.cursor,
276 grab: self.grab,
277 child: gpui::div()
278 .child(render(window, cx))
279 .child(
280 gpui::canvas(
281 move |bounds, _, _| gesture.shown.set(Some(bounds.origin)),
282 |_, _, _, _| {},
283 )
284 .absolute()
285 .top_0()
286 .left_0()
287 .size_full(),
288 )
289 .into_any_element(),
290 })
291 .into_any_element()
292 }
293 None => gpui::Empty.into_any_element(),
294 }
295 }
296}
297
298struct AtGrab {
301 cursor: Point<Pixels>,
302 grab: Point<Pixels>,
303 child: AnyElement,
304}
305
306impl IntoElement for AtGrab {
307 type Element = Self;
308
309 fn into_element(self) -> Self {
310 self
311 }
312}
313
314impl Element for AtGrab {
315 type RequestLayoutState = ();
316 type PrepaintState = ();
317
318 fn id(&self) -> Option<ElementId> {
319 None
320 }
321
322 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
323 None
324 }
325
326 fn request_layout(
327 &mut self,
328 _id: Option<&GlobalElementId>,
329 _inspector_id: Option<&InspectorElementId>,
330 window: &mut Window,
331 cx: &mut App,
332 ) -> (LayoutId, ()) {
333 (self.child.request_layout(window, cx), ())
334 }
335
336 fn prepaint(
337 &mut self,
338 _id: Option<&GlobalElementId>,
339 _inspector_id: Option<&InspectorElementId>,
340 bounds: Bounds<Pixels>,
341 _request_layout: &mut (),
342 window: &mut Window,
343 cx: &mut App,
344 ) {
345 let grab = point(
346 self.grab.x.clamp(Pixels::ZERO, bounds.size.width),
347 self.grab.y.clamp(Pixels::ZERO, bounds.size.height),
348 );
349 window.with_element_offset(self.cursor - grab, |window| self.child.prepaint(window, cx));
350 }
351
352 fn paint(
353 &mut self,
354 _id: Option<&GlobalElementId>,
355 _inspector_id: Option<&InspectorElementId>,
356 _bounds: Bounds<Pixels>,
357 _request_layout: &mut (),
358 _prepaint: &mut (),
359 window: &mut Window,
360 cx: &mut App,
361 ) {
362 self.child.paint(window, cx);
363 }
364}