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