Skip to main content

ui/drag/
element.rs

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 crate::scroll::Scroller;
11
12use super::{
13    Domain, Drop, Feedback, Outside,
14    state::{Accepts, Carries, Commit, Config, Lands, OnDrop, OnOutside},
15};
16
17/// A drop target whose content the host lays out. Style it as the box that
18/// holds the content: its visible bounds are where it takes the pointer.
19#[derive(IntoElement)]
20pub struct Region<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> {
21    domain: Domain<R, I>,
22    element_id: ElementId,
23    id: R,
24    axis: Axis,
25    child: AnyElement,
26    accepts: Option<Box<Accepts<I>>>,
27    lands: Option<Box<Lands<I>>>,
28    carries: Option<Box<Carries<I>>>,
29    feedback: Feedback,
30    axis_locked: bool,
31    scroll: Option<Scroller>,
32    dropped: Option<Rc<OnDrop<R, I>>>,
33    outside: Option<Rc<OnOutside<I>>>,
34    style: StyleRefinement,
35}
36
37impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Region<R, I> {
38    pub(super) fn new(
39        domain: Domain<R, I>,
40        element_id: ElementId,
41        id: R,
42        axis: Axis,
43        child: AnyElement,
44    ) -> Self {
45        Self {
46            domain,
47            element_id,
48            id,
49            axis,
50            child,
51            accepts: None,
52            lands: None,
53            carries: 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    /// Items this region takes. It takes every item of its domain by default.
64    pub fn accepts(mut self, accepts: impl Fn(&I) -> bool + 'static) -> Self {
65        self.accepts = Some(Box::new(accepts));
66        self
67    }
68
69    /// Where in this region an item may land, by its neighbours there. The
70    /// pointer's spot moves to the nearest allowed one among painted items;
71    /// with none allowed the region holds the item without a landing, and a
72    /// release commits nothing.
73    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    /// The items that go along with `item` when it is picked up here, in
79    /// order after it. They are hidden with it and land with it.
80    pub fn carries(mut self, carries: impl Fn(&I) -> Vec<I> + 'static) -> Self {
81        self.carries = Some(Box::new(carries));
82        self
83    }
84
85    pub fn feedback(mut self, feedback: Feedback) -> Self {
86        self.feedback = feedback;
87        self
88    }
89
90    /// An item carried out of this region stays on its axis until the pointer
91    /// leaves it by 12px across that axis, and returns once it re-enters.
92    pub fn axis_locked(mut self) -> Self {
93        self.axis_locked = true;
94        self
95    }
96
97    /// The handle scrolling this region's content. Holding an item near the
98    /// region's visible edges scrolls it.
99    pub fn track_scroll(mut self, handle: impl Into<Scroller>) -> Self {
100        self.scroll = Some(handle.into());
101        self
102    }
103
104    /// Runs once on a release that lands in this region somewhere other than
105    /// where the item started. Apply it synchronously.
106    pub fn on_drop(
107        mut self,
108        dropped: impl Fn(&Drop<R, I>, &mut Window, &mut App) + 'static,
109    ) -> Self {
110        self.dropped = Some(Rc::new(dropped));
111        self
112    }
113
114    /// Runs when an item carried out of this region is released over no
115    /// region that accepts it.
116    pub fn on_drop_outside(
117        mut self,
118        outside: impl Fn(&Outside<I>, &mut Window, &mut App) + 'static,
119    ) -> Self {
120        self.outside = Some(Rc::new(outside));
121        self
122    }
123}
124
125impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Styled for Region<R, I> {
126    fn style(&mut self) -> &mut StyleRefinement {
127        &mut self.style
128    }
129}
130
131impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> RenderOnce for Region<R, I> {
132    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
133        let focus = window
134            .use_keyed_state(self.element_id.clone(), cx, |_, cx| cx.focus_handle())
135            .read(cx)
136            .clone();
137        let ended = {
138            let state = self.domain.0.borrow();
139            state.live.is_some() && (state.stale || !cx.has_active_drag())
140        };
141        if ended {
142            self.domain.cancel(window, cx);
143        }
144        let config = Rc::new(Config {
145            accepts: self.accepts,
146            lands: self.lands,
147            carries: self.carries,
148            feedback: self.feedback,
149            axis_locked: self.axis_locked,
150            scroll: self.scroll,
151            dropped: self.dropped,
152            outside: self.outside,
153            focus: focus.clone(),
154        });
155        let escaped = self.domain.clone();
156        let mut frame =
157            div()
158                .id(self.element_id)
159                .track_focus(&focus)
160                .on_key_down(move |event, window, cx| {
161                    if event.keystroke.key == "escape" && escaped.0.borrow().live.is_some() {
162                        escaped.cancel(window, cx);
163                        cx.stop_propagation();
164                    }
165                });
166        frame.style().refine(&self.style);
167        RegionElement {
168            domain: self.domain,
169            id: self.id,
170            axis: self.axis,
171            config,
172            child: frame.child(self.child).into_any_element(),
173        }
174    }
175}
176
177struct RegionElement<R, I> {
178    domain: Domain<R, I>,
179    id: R,
180    axis: Axis,
181    config: Rc<Config<R, I>>,
182    child: AnyElement,
183}
184
185impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> IntoElement
186    for RegionElement<R, I>
187{
188    type Element = Self;
189    fn into_element(self) -> Self {
190        self
191    }
192}
193
194impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Element
195    for RegionElement<R, I>
196{
197    type RequestLayoutState = ();
198    type PrepaintState = ();
199    fn id(&self) -> Option<ElementId> {
200        None
201    }
202    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
203        None
204    }
205    fn request_layout(
206        &mut self,
207        _: Option<&GlobalElementId>,
208        _: Option<&InspectorElementId>,
209        window: &mut Window,
210        cx: &mut App,
211    ) -> (LayoutId, ()) {
212        (self.child.request_layout(window, cx), ())
213    }
214    fn prepaint(
215        &mut self,
216        _: Option<&GlobalElementId>,
217        _: Option<&InspectorElementId>,
218        bounds: Bounds<Pixels>,
219        _: &mut (),
220        window: &mut Window,
221        cx: &mut App,
222    ) {
223        let visible = bounds.intersect(&window.content_mask().bounds);
224        self.domain.0.borrow_mut().drift(cx);
225        let previous = self.domain.0.borrow_mut().enter(
226            self.id.clone(),
227            self.axis,
228            bounds,
229            visible,
230            self.config.clone(),
231        );
232        self.child.prepaint(window, cx);
233        let mut state = self.domain.0.borrow_mut();
234        state.leave(previous);
235    }
236    fn paint(
237        &mut self,
238        _: Option<&GlobalElementId>,
239        _: Option<&InspectorElementId>,
240        _: Bounds<Pixels>,
241        _: &mut (),
242        _: &mut (),
243        window: &mut Window,
244        cx: &mut App,
245    ) {
246        self.child.paint(window, cx);
247        if self.config.feedback == Feedback::Indicator
248            && let Some((axis, at, visible)) = self.domain.0.borrow().indicator(&self.id)
249        {
250            let line = match axis {
251                Axis::Horizontal => Bounds::new(
252                    point(at - px(1.), visible.origin.y),
253                    size(px(2.), visible.size.height),
254                ),
255                Axis::Vertical => Bounds::new(
256                    point(visible.origin.x, at - px(1.)),
257                    size(visible.size.width, px(2.)),
258                ),
259            };
260            window.paint_quad(fill(line, theme::Theme::of(cx).accent));
261        }
262        let moving = self.domain.clone();
263        window.on_mouse_event(move |event: &MouseMoveEvent, phase, _, cx| {
264            if phase != DispatchPhase::Capture {
265                return;
266            }
267            let mut state = moving.0.borrow_mut();
268            if state.aim(event.position) {
269                state.painter.notify(cx);
270            }
271        });
272        let released = self.domain.clone();
273        window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
274            if phase != DispatchPhase::Capture || event.button != MouseButton::Left {
275                return;
276            }
277            let commit = released.0.borrow_mut().release(event.position, window, cx);
278            match commit {
279                Some(Commit::Drop(dropped, event)) => dropped(&event, window, cx),
280                Some(Commit::Outside(outside, event)) => outside(&event, window, cx),
281                None => {}
282            }
283        });
284    }
285}
286
287/// An element that can be picked up, measured by the region it is painted in.
288pub struct Handle<R, I> {
289    domain: Domain<R, I>,
290    item: I,
291    child: Option<AnyElement>,
292}
293
294impl<R, I> Handle<R, I> {
295    pub(super) fn new(domain: Domain<R, I>, item: I, child: AnyElement) -> Self {
296        Self {
297            domain,
298            item,
299            child: Some(child),
300        }
301    }
302}
303
304impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> IntoElement for Handle<R, I> {
305    type Element = Self;
306    fn into_element(self) -> Self {
307        self
308    }
309}
310
311#[doc(hidden)]
312pub struct Painted {
313    origin: Point<Pixels>,
314    hidden: bool,
315}
316
317impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Element for Handle<R, I> {
318    type RequestLayoutState = ();
319    type PrepaintState = Painted;
320    fn id(&self) -> Option<ElementId> {
321        None
322    }
323    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
324        None
325    }
326    fn request_layout(
327        &mut self,
328        _: Option<&GlobalElementId>,
329        _: Option<&InspectorElementId>,
330        window: &mut Window,
331        cx: &mut App,
332    ) -> (LayoutId, ()) {
333        (self.child.as_mut().unwrap().request_layout(window, cx), ())
334    }
335    fn prepaint(
336        &mut self,
337        _: Option<&GlobalElementId>,
338        _: Option<&InspectorElementId>,
339        bounds: Bounds<Pixels>,
340        _: &mut (),
341        window: &mut Window,
342        cx: &mut App,
343    ) -> Painted {
344        let pointer = window.mouse_position();
345        let placement = self
346            .domain
347            .0
348            .borrow_mut()
349            .place(&self.item, bounds, pointer, cx);
350        window.with_element_offset(placement.offset, |window| {
351            if placement.floating {
352                let child = self.child.take().unwrap();
353                let offset = window.element_offset();
354                window.defer_draw(child, offset, 1, None);
355            } else {
356                self.child.as_mut().unwrap().prepaint(window, cx);
357            }
358        });
359        Painted {
360            origin: bounds.origin + placement.offset,
361            hidden: placement.hidden,
362        }
363    }
364    fn paint(
365        &mut self,
366        _: Option<&GlobalElementId>,
367        _: Option<&InspectorElementId>,
368        bounds: Bounds<Pixels>,
369        _: &mut (),
370        painted: &mut Painted,
371        window: &mut Window,
372        cx: &mut App,
373    ) {
374        let origin = painted.origin;
375        let pressable = Bounds::new(origin, bounds.size);
376        // Registered before the child's own, so it runs after them as the
377        // press bubbles: the press is the handle's, not an ancestor's.
378        window.on_mouse_event(move |event: &MouseDownEvent, phase, _, cx| {
379            if phase == DispatchPhase::Bubble
380                && event.button == MouseButton::Left
381                && pressable.contains(&event.position)
382            {
383                cx.stop_propagation();
384            }
385        });
386        if let Some(child) = self.child.as_mut()
387            && !painted.hidden
388        {
389            child.paint(window, cx);
390        }
391        let domain = self.domain.clone();
392        let item = self.item.clone();
393        window.on_mouse_event(move |event: &MouseDownEvent, phase, _, _| {
394            if phase == DispatchPhase::Capture
395                && event.button == MouseButton::Left
396                && pressable.contains(&event.position)
397            {
398                domain.0.borrow_mut().press = Some((item.clone(), event.position - origin));
399            }
400        });
401    }
402}