1use std::{cell::RefCell, rc::Rc};
17
18use gpui::{
19 AnyElement, App, Axis, ElementId, IntoElement, Pixels, Point, Render,
20 StatefulInteractiveElement, Window, prelude::*,
21};
22use motion::Painter;
23
24mod element;
25mod state;
26
27pub use element::{Handle, Region};
28pub(crate) use state::Gesture;
29use state::State;
30
31pub struct Carry<I> {
33 item: I,
34 pub(crate) gesture: Rc<Gesture>,
35}
36
37impl<I> Carry<I> {
38 pub fn item(&self) -> &I {
39 &self.item
40 }
41
42 pub fn claimed(&self) -> bool {
45 self.gesture.claimed.get()
46 }
47
48 pub fn ghost_bounds(&self, pointer: Point<Pixels>) -> gpui::Bounds<Pixels> {
50 self.gesture.ghost_bounds(pointer)
51 }
52
53 pub fn host(&self, origin: Point<Pixels>) {
56 self.gesture.host(origin);
57 }
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct Drop<R, I> {
65 pub item: I,
66 pub from: R,
67 pub region: R,
68 pub after: Option<I>,
69 pub before: Option<I>,
70}
71
72#[derive(Clone, Debug)]
74pub struct Outside<I> {
75 pub item: I,
76 pub position: Point<Pixels>,
77}
78
79#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
81pub enum Feedback {
82 #[default]
84 Displace,
85 Indicator,
87}
88
89type Ghost<I> = dyn Fn(&I, &mut Window, &mut App) -> AnyElement;
90type Paint = dyn Fn(&mut Window, &mut App) -> AnyElement;
91
92pub struct Domain<R, I>(Rc<RefCell<State<R, I>>>);
94
95impl<R, I> Clone for Domain<R, I> {
96 fn clone(&self) -> Self {
97 Self(self.0.clone())
98 }
99}
100
101impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Domain<R, I> {
102 pub fn new(painter: Painter) -> Self {
104 Self(Rc::new(RefCell::new(State::new(painter, None))))
105 }
106
107 pub fn with_ghost(
111 painter: Painter,
112 ghost: impl Fn(&I, &mut Window, &mut App) -> AnyElement + 'static,
113 ) -> Self {
114 Self(Rc::new(RefCell::new(State::new(
115 painter,
116 Some(Rc::new(ghost)),
117 ))))
118 }
119
120 pub fn region(
122 &self,
123 element_id: impl Into<ElementId>,
124 id: R,
125 axis: Axis,
126 child: impl IntoElement,
127 ) -> Region<R, I> {
128 self.0.borrow_mut().begin_frame();
129 Region::new(
130 self.clone(),
131 element_id.into(),
132 id,
133 axis,
134 child.into_any_element(),
135 )
136 }
137
138 pub fn handle<E>(&self, item: I, element: E) -> Handle<R, I>
142 where
143 E: StatefulInteractiveElement + IntoElement + 'static,
144 {
145 let domain = self.clone();
146 let carry = Carry {
147 item: item.clone(),
148 gesture: Rc::new(Gesture::default()),
149 };
150 let element = element.on_drag(carry, move |carry, grab, window, cx| {
151 domain.start(carry, grab, window, cx)
152 });
153 Handle::new(self.clone(), item, element.into_any_element())
154 }
155
156 pub fn fixed(&self, item: I, element: impl IntoElement) -> Handle<R, I> {
159 Handle::new(self.clone(), item, element.into_any_element())
160 }
161
162 pub fn carries(&self, item: &I) -> bool {
164 self.0
165 .borrow()
166 .live
167 .as_ref()
168 .is_some_and(|live| &live.item == item)
169 }
170
171 pub fn cancel(&self, window: &mut Window, cx: &mut App) {
173 let live = self.0.borrow_mut().end(window, cx);
174 if live.is_some() {
175 cx.stop_active_drag(window);
176 }
177 }
178
179 pub fn carried(&self) -> Option<I> {
181 self.0.borrow().live.as_ref().map(|live| live.item.clone())
182 }
183
184 pub(crate) fn invalidate(&self) {
186 self.0.borrow_mut().stale = true;
187 }
188
189 fn start(
190 &self,
191 carry: &Carry<I>,
192 cursor: Point<Pixels>,
193 window: &mut Window,
194 cx: &mut App,
195 ) -> gpui::Entity<Ghosted> {
196 let (grab, ghost) = {
197 let mut state = self.0.borrow_mut();
198 let grab = state.start(&carry.item, carry.gesture.clone(), cursor, window, cx);
199 (grab, state.ghost.clone())
200 };
201 let item = carry.item.clone();
202 let gesture = carry.gesture.clone();
203 cx.new(|_| Ghosted {
204 gesture,
205 shift: cursor - grab,
207 render: ghost.map(|ghost| {
208 Rc::new(move |window: &mut Window, cx: &mut App| ghost(&item, window, cx))
209 as Rc<Paint>
210 }),
211 })
212 }
213}
214
215pub struct Ghosted {
217 gesture: Rc<Gesture>,
218 shift: Point<Pixels>,
219 render: Option<Rc<Paint>>,
220}
221
222impl Render for Ghosted {
223 fn render(&mut self, window: &mut Window, cx: &mut gpui::Context<Self>) -> impl IntoElement {
224 let hosted = self.gesture.hosted.get() && !self.gesture.claimed.get();
225 match self.render.as_ref().filter(|_| !hosted) {
226 Some(render) => {
227 let gesture = self.gesture.clone();
228 gpui::div()
231 .child(
232 gpui::div()
233 .relative()
234 .left(self.shift.x)
235 .top(self.shift.y)
236 .child(render(window, cx))
237 .child(
238 gpui::canvas(
239 move |bounds, _, _| gesture.shown.set(Some(bounds.origin)),
240 |_, _, _, _| {},
241 )
242 .absolute()
243 .top_0()
244 .left_0()
245 .size_full(),
246 ),
247 )
248 .into_any_element()
249 }
250 None => gpui::Empty.into_any_element(),
251 }
252 }
253}