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