Skip to main content

ui/docking/
element.rs

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