Skip to main content

ui/docking/
element.rs

1use super::{Dock, OnDrop, Target, Tween};
2use crate::drag::Carry;
3use gpui::{
4    AnyElement, App, AvailableSpace, Bounds, ContentMask, DragMoveEvent, Element, ElementId,
5    GlobalElementId, InspectorElementId, IntoElement, LayoutId, Pixels, RenderOnce, Window, div,
6    prelude::*, px,
7};
8use std::rc::Rc;
9
10#[derive(IntoElement)]
11pub struct Surface<P: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> {
12    pub(super) dock: Dock<P, I>,
13    pub(super) id: ElementId,
14    pub(super) child: AnyElement,
15    pub(super) dropped: Option<Rc<OnDrop<P, I>>>,
16}
17
18impl<P: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Surface<P, I> {
19    /// Apply the split/join synchronously and return its resulting pane; `None` rejects it.
20    pub fn on_drop(
21        mut self,
22        callback: impl Fn(&super::Drop<P, I>, &mut Window, &mut App) -> Option<P> + 'static,
23    ) -> Self {
24        self.dropped = Some(Rc::new(callback));
25        self
26    }
27}
28
29impl<P: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> RenderOnce for Surface<P, I> {
30    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
31        self.dock.0.borrow_mut().dropped = self.dropped;
32        let moved = self.dock.clone();
33        let dropped = self.dock.clone();
34        SurfaceElement {
35            dock: self.dock,
36            child: div()
37                .id(self.id)
38                .size_full()
39                .on_drag_move(move |event: &DragMoveEvent<Carry<I>>, _, cx| {
40                    let carry = event.drag(cx);
41                    let (item, gesture) = (carry.item().clone(), carry.gesture.clone());
42                    moved.update(item, gesture, event.event.position, cx);
43                })
44                .on_drop(move |carry: &Carry<I>, window, cx| {
45                    dropped.release(carry, window.mouse_position(), window, cx);
46                })
47                .child(self.child)
48                .into_any_element(),
49        }
50    }
51}
52
53struct SurfaceElement<P, I> {
54    dock: Dock<P, I>,
55    child: AnyElement,
56}
57
58impl<P: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> IntoElement
59    for SurfaceElement<P, I>
60{
61    type Element = Self;
62    fn into_element(self) -> Self {
63        self
64    }
65}
66
67impl<P: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Element
68    for SurfaceElement<P, I>
69{
70    type RequestLayoutState = ();
71    type PrepaintState = Option<AnyElement>;
72    fn id(&self) -> Option<ElementId> {
73        None
74    }
75    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
76        None
77    }
78    fn request_layout(
79        &mut self,
80        _: Option<&GlobalElementId>,
81        _: Option<&InspectorElementId>,
82        window: &mut Window,
83        cx: &mut App,
84    ) -> (LayoutId, ()) {
85        (self.child.request_layout(window, cx), ())
86    }
87    fn prepaint(
88        &mut self,
89        _: Option<&GlobalElementId>,
90        _: Option<&InspectorElementId>,
91        bounds: Bounds<Pixels>,
92        _: &mut (),
93        window: &mut Window,
94        cx: &mut App,
95    ) -> Option<AnyElement> {
96        self.dock.0.borrow_mut().targets.clear();
97        if !cx.has_active_drag() {
98            self.dock.clear(cx);
99        }
100        self.child.prepaint(window, cx);
101        let mut state = self.dock.0.borrow_mut();
102        state.aim(cx);
103        let theme = theme::Theme::of(cx);
104        let mut overlay = div()
105            .absolute()
106            .w(bounds.size.width)
107            .h(bounds.size.height)
108            .overflow_hidden();
109        let mut active = false;
110        if let Some(preview) = &state.preview {
111            let rect = preview.bounds(cx);
112            let offset = rect.origin - bounds.origin;
113            overlay = overlay.child(
114                div()
115                    .debug_selector(|| "dock-preview".into())
116                    .absolute()
117                    .left(offset.x)
118                    .top(offset.y)
119                    .w(rect.size.width)
120                    .h(rect.size.height)
121                    .rounded(px(6.))
122                    .bg(theme.accent.opacity(0.16))
123                    .border_2()
124                    .border_color(theme.accent.opacity(0.7)),
125            );
126            active |= preview.progress(cx) < 1.;
127        }
128        let ghost = state.settling.as_ref().and_then(|settle| {
129            state
130                .targets
131                .iter()
132                .find(|target| target.id == settle.pane)
133                .map(|target| {
134                    let tween = Tween {
135                        from: settle.ghost,
136                        to: Bounds::new(target.bounds.origin, settle.ghost.size),
137                        since: settle.since,
138                    };
139                    (settle.item.clone(), tween.bounds(cx), tween.progress(cx))
140                })
141        });
142        let carried = state
143            .carried
144            .as_ref()
145            .filter(|carried| carried.detached())
146            .map(|carried| {
147                carried.gesture.hosted.set(true);
148                (carried.item.clone(), carried.ghost_bounds(), 0.)
149            });
150        let render_ghost = state.ghost.clone();
151        if let Some((_, _, progress)) = &ghost {
152            if *progress >= 1. {
153                state.settling = None;
154            } else {
155                active = true;
156            }
157        } else {
158            state.settling = None;
159        }
160        if active {
161            state.painter.lease(120., motion::TAB_SLIDE.total(), cx);
162        }
163        drop(state);
164        if let Some((item, rect, progress)) = carried.or(ghost)
165            && progress < 1.
166        {
167            let offset = rect.origin - bounds.origin;
168            let selector = if progress == 0. && self.dock.0.borrow().carried.is_some() {
169                "dock-carry-ghost"
170            } else {
171                "dock-settle-ghost"
172            };
173            overlay = overlay.child(
174                div()
175                    .debug_selector(move || selector.into())
176                    .absolute()
177                    .left(offset.x)
178                    .top(offset.y)
179                    .w(rect.size.width)
180                    .h(rect.size.height)
181                    .overflow_hidden()
182                    .opacity(1. - progress)
183                    .child(render_ghost(&item, window, cx)),
184            );
185        }
186        let mut overlay = overlay.into_any_element();
187        overlay.layout_as_root(bounds.size.map(AvailableSpace::Definite), window, cx);
188        overlay.prepaint_at(bounds.origin, window, cx);
189        Some(overlay)
190    }
191    fn paint(
192        &mut self,
193        _: Option<&GlobalElementId>,
194        _: Option<&InspectorElementId>,
195        _: Bounds<Pixels>,
196        _: &mut (),
197        overlay: &mut Option<AnyElement>,
198        window: &mut Window,
199        cx: &mut App,
200    ) {
201        self.child.paint(window, cx);
202        if let Some(overlay) = overlay {
203            overlay.paint(window, cx);
204        }
205    }
206}
207
208/// A measured target; style its child to set its layout size.
209pub struct Pane<P, I> {
210    pub(super) dock: Dock<P, I>,
211    pub(super) id: P,
212    pub(super) bar_height: Pixels,
213    pub(super) child: AnyElement,
214}
215
216impl<P: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> IntoElement for Pane<P, I> {
217    type Element = Self;
218    fn into_element(self) -> Self {
219        self
220    }
221}
222impl<P: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Element for Pane<P, I> {
223    type RequestLayoutState = ();
224    type PrepaintState = Bounds<Pixels>;
225    fn id(&self) -> Option<ElementId> {
226        None
227    }
228    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
229        None
230    }
231    fn request_layout(
232        &mut self,
233        _: Option<&GlobalElementId>,
234        _: Option<&InspectorElementId>,
235        window: &mut Window,
236        cx: &mut App,
237    ) -> (LayoutId, ()) {
238        (self.child.request_layout(window, cx), ())
239    }
240    fn prepaint(
241        &mut self,
242        _: Option<&GlobalElementId>,
243        _: Option<&InspectorElementId>,
244        bounds: Bounds<Pixels>,
245        _: &mut (),
246        window: &mut Window,
247        cx: &mut App,
248    ) -> Bounds<Pixels> {
249        let mut state = self.dock.0.borrow_mut();
250        state.targets.push(Target {
251            id: self.id.clone(),
252            bounds,
253            visible: bounds.intersect(&window.content_mask().bounds),
254            bar_height: self.bar_height,
255        });
256        let rect = state
257            .settling
258            .as_ref()
259            .filter(|settle| settle.pane == self.id)
260            .map(|settle| {
261                Tween {
262                    from: settle.preview,
263                    to: bounds,
264                    since: settle.since,
265                }
266                .bounds(cx)
267            })
268            .unwrap_or(bounds);
269        drop(state);
270        window.with_content_mask(
271            Some(ContentMask {
272                bounds: rect,
273                ..Default::default()
274            }),
275            |window| {
276                window.with_element_offset(rect.origin - bounds.origin, |window| {
277                    self.child.prepaint(window, cx)
278                });
279            },
280        );
281        rect
282    }
283    fn paint(
284        &mut self,
285        _: Option<&GlobalElementId>,
286        _: Option<&InspectorElementId>,
287        _: Bounds<Pixels>,
288        _: &mut (),
289        rect: &mut Bounds<Pixels>,
290        window: &mut Window,
291        cx: &mut App,
292    ) {
293        window.with_content_mask(
294            Some(ContentMask {
295                bounds: *rect,
296                ..Default::default()
297            }),
298            |window| self.child.paint(window, cx),
299        );
300    }
301}