1use std::rc::Rc;
2
3use gpui::{
4 AnyElement, App, Axis, Bounds, DispatchPhase, Element, ElementId, GlobalElementId,
5 InspectorElementId, IntoElement, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent,
6 MouseUpEvent, Pixels, Point, Refineable, RenderOnce, StyleRefinement, Styled, Window, div,
7 fill, point, prelude::*, px, size,
8};
9
10use motion::Painter;
11
12use crate::scroll::Scroller;
13
14use super::{
15 Domain, Drop, Feedback, Outside,
16 state::{Accepts, Commit, Config, Lands, OnDrop, OnOutside},
17};
18
19#[derive(IntoElement)]
22pub struct Region<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> {
23 domain: Domain<R, I>,
24 element_id: ElementId,
25 id: R,
26 axis: Axis,
27 child: AnyElement,
28 accepts: Option<Box<Accepts<I>>>,
29 lands: Option<Box<Lands<I>>>,
30 feedback: Feedback,
31 axis_locked: bool,
32 scroll: Option<Scroller>,
33 dropped: Option<Rc<OnDrop<R, I>>>,
34 outside: Option<Rc<OnOutside<I>>>,
35 style: StyleRefinement,
36}
37
38impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Region<R, I> {
39 pub(super) fn new(
40 domain: Domain<R, I>,
41 element_id: ElementId,
42 id: R,
43 axis: Axis,
44 child: AnyElement,
45 ) -> Self {
46 Self {
47 domain,
48 element_id,
49 id,
50 axis,
51 child,
52 accepts: None,
53 lands: None,
54 feedback: Feedback::default(),
55 axis_locked: false,
56 scroll: None,
57 dropped: None,
58 outside: None,
59 style: StyleRefinement::default(),
60 }
61 }
62
63 pub fn accepts(mut self, accepts: impl Fn(&I) -> bool + 'static) -> Self {
65 self.accepts = Some(Box::new(accepts));
66 self
67 }
68
69 pub fn lands(mut self, lands: impl Fn(&I, Option<&I>, Option<&I>) -> bool + 'static) -> Self {
74 self.lands = Some(Box::new(lands));
75 self
76 }
77
78 pub fn feedback(mut self, feedback: Feedback) -> Self {
79 self.feedback = feedback;
80 self
81 }
82
83 pub fn axis_locked(mut self) -> Self {
86 self.axis_locked = true;
87 self
88 }
89
90 pub fn track_scroll(mut self, handle: impl Into<Scroller>) -> Self {
93 self.scroll = Some(handle.into());
94 self
95 }
96
97 pub fn on_drop(
100 mut self,
101 dropped: impl Fn(&Drop<R, I>, &mut Window, &mut App) + 'static,
102 ) -> Self {
103 self.dropped = Some(Rc::new(dropped));
104 self
105 }
106
107 pub fn on_drop_outside(
110 mut self,
111 outside: impl Fn(&Outside<I>, &mut Window, &mut App) + 'static,
112 ) -> Self {
113 self.outside = Some(Rc::new(outside));
114 self
115 }
116}
117
118impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Styled for Region<R, I> {
119 fn style(&mut self) -> &mut StyleRefinement {
120 &mut self.style
121 }
122}
123
124impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> RenderOnce for Region<R, I> {
125 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
126 let focus = window
127 .use_keyed_state(self.element_id.clone(), cx, |_, cx| cx.focus_handle())
128 .read(cx)
129 .clone();
130 let ended = {
131 let state = self.domain.0.borrow();
132 state.live.is_some() && (state.stale || !cx.has_active_drag())
133 };
134 if ended {
135 self.domain.cancel(window, cx);
136 }
137 let config = Rc::new(Config {
138 accepts: self.accepts,
139 lands: self.lands,
140 feedback: self.feedback,
141 axis_locked: self.axis_locked,
142 scroll: self.scroll,
143 dropped: self.dropped,
144 outside: self.outside,
145 focus: focus.clone(),
146 });
147 let escaped = self.domain.clone();
148 let mut frame =
149 div()
150 .id(self.element_id)
151 .track_focus(&focus)
152 .on_key_down(move |event, window, cx| {
153 if event.keystroke.key == "escape" && escaped.0.borrow().live.is_some() {
154 escaped.cancel(window, cx);
155 cx.stop_propagation();
156 }
157 });
158 frame.style().refine(&self.style);
159 RegionElement {
160 domain: self.domain,
161 id: self.id,
162 axis: self.axis,
163 config,
164 child: frame.child(self.child).into_any_element(),
165 }
166 }
167}
168
169struct RegionElement<R, I> {
170 domain: Domain<R, I>,
171 id: R,
172 axis: Axis,
173 config: Rc<Config<R, I>>,
174 child: AnyElement,
175}
176
177impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> IntoElement
178 for RegionElement<R, I>
179{
180 type Element = Self;
181 fn into_element(self) -> Self {
182 self
183 }
184}
185
186impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Element
187 for RegionElement<R, I>
188{
189 type RequestLayoutState = ();
190 type PrepaintState = ();
191 fn id(&self) -> Option<ElementId> {
192 None
193 }
194 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
195 None
196 }
197 fn request_layout(
198 &mut self,
199 _: Option<&GlobalElementId>,
200 _: Option<&InspectorElementId>,
201 window: &mut Window,
202 cx: &mut App,
203 ) -> (LayoutId, ()) {
204 (self.child.request_layout(window, cx), ())
205 }
206 fn prepaint(
207 &mut self,
208 _: Option<&GlobalElementId>,
209 _: Option<&InspectorElementId>,
210 bounds: Bounds<Pixels>,
211 _: &mut (),
212 window: &mut Window,
213 cx: &mut App,
214 ) {
215 let visible = bounds.intersect(&window.content_mask().bounds);
216 self.domain.0.borrow_mut().drift(cx);
217 let previous = self.domain.0.borrow_mut().enter(
218 self.id.clone(),
219 self.axis,
220 bounds,
221 visible,
222 self.config.clone(),
223 Painter::from(window.current_view()),
224 );
225 self.child.prepaint(window, cx);
226 let mut state = self.domain.0.borrow_mut();
227 state.leave(previous);
228 }
229 fn paint(
230 &mut self,
231 _: Option<&GlobalElementId>,
232 _: Option<&InspectorElementId>,
233 _: Bounds<Pixels>,
234 _: &mut (),
235 _: &mut (),
236 window: &mut Window,
237 cx: &mut App,
238 ) {
239 self.child.paint(window, cx);
240 if self.config.feedback == Feedback::Indicator
241 && let Some((axis, at, visible)) = self.domain.0.borrow().indicator(&self.id)
242 {
243 let line = match axis {
244 Axis::Horizontal => Bounds::new(
245 point(at - px(1.), visible.origin.y),
246 size(px(2.), visible.size.height),
247 ),
248 Axis::Vertical => Bounds::new(
249 point(visible.origin.x, at - px(1.)),
250 size(visible.size.width, px(2.)),
251 ),
252 };
253 window.paint_quad(fill(line, theme::Theme::of(cx).accent));
254 }
255 let moving = self.domain.clone();
256 window.on_mouse_event(move |event: &MouseMoveEvent, phase, _, cx| {
257 if phase != DispatchPhase::Capture {
258 return;
259 }
260 let mut state = moving.0.borrow_mut();
261 if state.aim(event.position) {
262 state.notify(cx);
263 }
264 });
265 let released = self.domain.clone();
266 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
267 if phase != DispatchPhase::Capture || event.button != MouseButton::Left {
268 return;
269 }
270 let commit = released.0.borrow_mut().release(event.position, window, cx);
271 match commit {
272 Some(Commit::Drop(dropped, event)) => dropped(&event, window, cx),
273 Some(Commit::Outside(outside, event)) => outside(&event, window, cx),
274 None => {}
275 }
276 });
277 }
278}
279
280pub struct Handle<R, I> {
282 domain: Domain<R, I>,
283 item: I,
284 child: Option<AnyElement>,
285}
286
287impl<R, I> Handle<R, I> {
288 pub(super) fn new(domain: Domain<R, I>, item: I, child: AnyElement) -> Self {
289 Self {
290 domain,
291 item,
292 child: Some(child),
293 }
294 }
295}
296
297impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> IntoElement for Handle<R, I> {
298 type Element = Self;
299 fn into_element(self) -> Self {
300 self
301 }
302}
303
304#[doc(hidden)]
305pub struct Painted {
306 origin: Point<Pixels>,
307 hidden: bool,
308}
309
310impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Element for Handle<R, I> {
311 type RequestLayoutState = ();
312 type PrepaintState = Painted;
313 fn id(&self) -> Option<ElementId> {
314 None
315 }
316 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
317 None
318 }
319 fn request_layout(
320 &mut self,
321 _: Option<&GlobalElementId>,
322 _: Option<&InspectorElementId>,
323 window: &mut Window,
324 cx: &mut App,
325 ) -> (LayoutId, ()) {
326 (self.child.as_mut().unwrap().request_layout(window, cx), ())
327 }
328 fn prepaint(
329 &mut self,
330 _: Option<&GlobalElementId>,
331 _: Option<&InspectorElementId>,
332 bounds: Bounds<Pixels>,
333 _: &mut (),
334 window: &mut Window,
335 cx: &mut App,
336 ) -> Painted {
337 let pointer = window.mouse_position();
338 let placement = self
339 .domain
340 .0
341 .borrow_mut()
342 .place(&self.item, bounds, pointer, cx);
343 window.with_element_offset(placement.offset, |window| {
344 if placement.floating {
345 let child = self.child.take().unwrap();
346 let offset = window.element_offset();
347 window.defer_draw(child, offset, 1, None);
348 } else {
349 self.child.as_mut().unwrap().prepaint(window, cx);
350 }
351 });
352 Painted {
353 origin: bounds.origin + placement.offset,
354 hidden: placement.hidden,
355 }
356 }
357 fn paint(
358 &mut self,
359 _: Option<&GlobalElementId>,
360 _: Option<&InspectorElementId>,
361 bounds: Bounds<Pixels>,
362 _: &mut (),
363 painted: &mut Painted,
364 window: &mut Window,
365 cx: &mut App,
366 ) {
367 let origin = painted.origin;
368 let pressable = Bounds::new(origin, bounds.size);
369 window.on_mouse_event(move |event: &MouseDownEvent, phase, _, cx| {
372 if phase == DispatchPhase::Bubble
373 && event.button == MouseButton::Left
374 && pressable.contains(&event.position)
375 {
376 cx.stop_propagation();
377 }
378 });
379 if let Some(child) = self.child.as_mut()
380 && !painted.hidden
381 {
382 child.paint(window, cx);
383 }
384 let domain = self.domain.clone();
385 let item = self.item.clone();
386 window.on_mouse_event(move |event: &MouseDownEvent, phase, _, _| {
387 if phase == DispatchPhase::Capture
388 && event.button == MouseButton::Left
389 && pressable.contains(&event.position)
390 {
391 domain.0.borrow_mut().press = Some((item.clone(), event.position - origin));
392 }
393 });
394 }
395}