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