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 near the
91    /// region's visible edges scrolls it.
92    pub fn track_scroll(mut self, handle: impl Into<Scroller>) -> Self {
93        self.scroll = Some(handle.into());
94        self
95    }
96
97    /// Runs once on a release that lands in this region somewhere other than
98    /// where the item started. Apply it synchronously.
99    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    /// Runs when an item carried out of this region is released over no
108    /// region that accepts it.
109    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
280/// An element that can be picked up, measured by the region it is painted in.
281pub 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        // Registered before the child's own, so it runs after them as the
370        // press bubbles: the press is the handle's, not an ancestor's.
371        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}