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