gpui_component/dock/
mod.rs

1mod dock;
2mod invalid_panel;
3mod panel;
4mod stack_panel;
5mod state;
6mod tab_panel;
7mod tiles;
8
9use anyhow::Result;
10use gpui::{
11    actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, App, AppContext, Axis,
12    Bounds, Context, Edges, Entity, EntityId, EventEmitter, InteractiveElement as _, IntoElement,
13    ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window,
14};
15use std::sync::Arc;
16
17pub use dock::*;
18pub use panel::*;
19pub use stack_panel::*;
20pub use state::*;
21pub use tab_panel::*;
22pub use tiles::*;
23
24pub(crate) fn init(cx: &mut App) {
25    PanelRegistry::init(cx);
26}
27
28actions!(dock, [ToggleZoom, ClosePanel]);
29
30pub enum DockEvent {
31    /// The layout of the dock has changed, subscribers this to save the layout.
32    ///
33    /// This event is emitted when every time the layout of the dock has changed,
34    /// So it emits may be too frequently, you may want to debounce the event.
35    LayoutChanged,
36
37    /// The drag item drop event.
38    DragDrop(AnyDrag),
39}
40
41/// The main area of the dock.
42pub struct DockArea {
43    id: SharedString,
44    /// The version is used to special the default layout, this is like the `panel_version` in [`Panel`](Panel).
45    version: Option<usize>,
46    pub(crate) bounds: Bounds<Pixels>,
47
48    /// The center view of the dockarea.
49    items: DockItem,
50
51    /// The entity_id of the [`TabPanel`](TabPanel) where each toggle button should be displayed,
52    toggle_button_panels: Edges<Option<EntityId>>,
53
54    /// Whether to show the toggle button.
55    toggle_button_visible: bool,
56    /// The left dock of the dock_area.
57    left_dock: Option<Entity<Dock>>,
58    /// The bottom dock of the dock_area.
59    bottom_dock: Option<Entity<Dock>>,
60    /// The right dock of the dock_area.
61    right_dock: Option<Entity<Dock>>,
62    /// The top zoom view of the dock_area, if any.
63    zoom_view: Option<AnyView>,
64
65    /// Lock panels layout, but allow to resize.
66    locked: bool,
67
68    /// The panel style, default is [`PanelStyle::Default`](PanelStyle::Default).
69    pub(crate) panel_style: PanelStyle,
70
71    _subscriptions: Vec<Subscription>,
72}
73
74/// DockItem is a tree structure that represents the layout of the dock.
75#[derive(Clone)]
76pub enum DockItem {
77    /// Split layout
78    Split {
79        axis: Axis,
80        items: Vec<DockItem>,
81        sizes: Vec<Option<Pixels>>,
82        view: Entity<StackPanel>,
83    },
84    /// Tab layout
85    Tabs {
86        items: Vec<Arc<dyn PanelView>>,
87        active_ix: usize,
88        view: Entity<TabPanel>,
89    },
90    /// Panel layout
91    Panel { view: Arc<dyn PanelView> },
92    /// Tiles layout
93    Tiles {
94        items: Vec<TileItem>,
95        view: Entity<Tiles>,
96    },
97}
98
99impl std::fmt::Debug for DockItem {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            DockItem::Split {
103                axis, items, sizes, ..
104            } => f
105                .debug_struct("Split")
106                .field("axis", axis)
107                .field("items", &items.len())
108                .field("sizes", sizes)
109                .finish(),
110            DockItem::Tabs {
111                items, active_ix, ..
112            } => f
113                .debug_struct("Tabs")
114                .field("items", &items.len())
115                .field("active_ix", active_ix)
116                .finish(),
117            DockItem::Panel { .. } => f.debug_struct("Panel").finish(),
118            DockItem::Tiles { .. } => f.debug_struct("Tiles").finish(),
119        }
120    }
121}
122
123impl DockItem {
124    /// Create DockItem with split layout, each item of panel have equal size.
125    pub fn split(
126        axis: Axis,
127        items: Vec<DockItem>,
128        dock_area: &WeakEntity<DockArea>,
129        window: &mut Window,
130        cx: &mut App,
131    ) -> Self {
132        let sizes = vec![None; items.len()];
133        Self::split_with_sizes(axis, items, sizes, dock_area, window, cx)
134    }
135
136    /// Create DockItem with split layout, each item of panel have specified size.
137    ///
138    /// Please note that the `items` and `sizes` must have the same length.
139    /// Set `None` in `sizes` to make the index of panel have auto size.
140    pub fn split_with_sizes(
141        axis: Axis,
142        items: Vec<DockItem>,
143        sizes: Vec<Option<Pixels>>,
144        dock_area: &WeakEntity<DockArea>,
145        window: &mut Window,
146        cx: &mut App,
147    ) -> Self {
148        let mut items = items;
149        let stack_panel = cx.new(|cx| {
150            let mut stack_panel = StackPanel::new(axis, window, cx);
151            for (i, item) in items.iter_mut().enumerate() {
152                let view = item.view();
153                let size = sizes.get(i).copied().flatten();
154                stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)
155            }
156
157            for (i, item) in items.iter().enumerate() {
158                let view = item.view();
159                let size = sizes.get(i).copied().flatten();
160                stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)
161            }
162
163            stack_panel
164        });
165
166        window.defer(cx, {
167            let stack_panel = stack_panel.clone();
168            let dock_area = dock_area.clone();
169            move |window, cx| {
170                _ = dock_area.update(cx, |this, cx| {
171                    this.subscribe_panel(&stack_panel, window, cx);
172                });
173            }
174        });
175
176        Self::Split {
177            axis,
178            items,
179            sizes,
180            view: stack_panel,
181        }
182    }
183
184    /// Create DockItem with panel layout
185    pub fn panel(panel: Arc<dyn PanelView>) -> Self {
186        Self::Panel { view: panel }
187    }
188
189    /// Create DockItem with tiles layout
190    ///
191    /// This items and metas should have the same length.
192    pub fn tiles(
193        items: Vec<DockItem>,
194        metas: Vec<impl Into<TileMeta> + Copy>,
195        dock_area: &WeakEntity<DockArea>,
196        window: &mut Window,
197        cx: &mut App,
198    ) -> Self {
199        assert!(items.len() == metas.len());
200
201        let tile_panel = cx.new(|cx| {
202            let mut tiles = Tiles::new(window, cx);
203            for (ix, item) in items.clone().into_iter().enumerate() {
204                match item {
205                    DockItem::Tabs { view, .. } => {
206                        let meta: TileMeta = metas[ix].into();
207                        let tile_item =
208                            TileItem::new(Arc::new(view), meta.bounds).z_index(meta.z_index);
209                        tiles.add_item(tile_item, dock_area, window, cx);
210                    }
211                    DockItem::Panel { view } => {
212                        let meta: TileMeta = metas[ix].into();
213                        let tile_item =
214                            TileItem::new(view.clone(), meta.bounds).z_index(meta.z_index);
215                        tiles.add_item(tile_item, dock_area, window, cx);
216                    }
217                    _ => {
218                        // Ignore non-tabs items
219                    }
220                }
221            }
222            tiles
223        });
224
225        window.defer(cx, {
226            let tile_panel = tile_panel.clone();
227            let dock_area = dock_area.clone();
228            move |window, cx| {
229                _ = dock_area.update(cx, |this, cx| {
230                    this.subscribe_panel(&tile_panel, window, cx);
231                    this.subscribe_tiles_item_drop(&tile_panel, window, cx);
232                });
233            }
234        });
235
236        Self::Tiles {
237            items: tile_panel.read(cx).panels.clone(),
238            view: tile_panel,
239        }
240    }
241
242    /// Create DockItem with tabs layout, items are displayed as tabs.
243    ///
244    /// The `active_ix` is the index of the active tab, if `None` the first tab is active.
245    pub fn tabs(
246        items: Vec<Arc<dyn PanelView>>,
247        active_ix: Option<usize>,
248        dock_area: &WeakEntity<DockArea>,
249        window: &mut Window,
250        cx: &mut App,
251    ) -> Self {
252        let mut new_items: Vec<Arc<dyn PanelView>> = vec![];
253        for item in items.into_iter() {
254            new_items.push(item)
255        }
256        Self::new_tabs(new_items, active_ix, dock_area, window, cx)
257    }
258
259    pub fn tab<P: Panel>(
260        item: Entity<P>,
261        dock_area: &WeakEntity<DockArea>,
262        window: &mut Window,
263        cx: &mut App,
264    ) -> Self {
265        Self::new_tabs(vec![Arc::new(item.clone())], None, dock_area, window, cx)
266    }
267
268    fn new_tabs(
269        items: Vec<Arc<dyn PanelView>>,
270        active_ix: Option<usize>,
271        dock_area: &WeakEntity<DockArea>,
272        window: &mut Window,
273        cx: &mut App,
274    ) -> Self {
275        let active_ix = active_ix.unwrap_or(0);
276        let tab_panel = cx.new(|cx| {
277            let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx);
278            for item in items.iter() {
279                tab_panel.add_panel(item.clone(), window, cx)
280            }
281            tab_panel.active_ix = active_ix;
282            tab_panel
283        });
284
285        Self::Tabs {
286            items,
287            active_ix,
288            view: tab_panel,
289        }
290    }
291
292    /// Returns the views of the dock item.
293    pub fn view(&self) -> Arc<dyn PanelView> {
294        match self {
295            Self::Split { view, .. } => Arc::new(view.clone()),
296            Self::Tabs { view, .. } => Arc::new(view.clone()),
297            Self::Tiles { view, .. } => Arc::new(view.clone()),
298            Self::Panel { view, .. } => view.clone(),
299        }
300    }
301
302    /// Find existing panel in the dock item.
303    pub fn find_panel(&self, panel: Arc<dyn PanelView>) -> Option<Arc<dyn PanelView>> {
304        match self {
305            Self::Split { items, .. } => {
306                items.iter().find_map(|item| item.find_panel(panel.clone()))
307            }
308            Self::Tabs { items, .. } => items.iter().find(|item| *item == &panel).cloned(),
309            Self::Panel { view } => Some(view.clone()),
310            Self::Tiles { items, .. } => items.iter().find_map(|item| {
311                if &item.panel == &panel {
312                    Some(item.panel.clone())
313                } else {
314                    None
315                }
316            }),
317        }
318    }
319
320    /// Add a panel to the dock item.
321    pub fn add_panel(
322        &mut self,
323        panel: Arc<dyn PanelView>,
324        dock_area: &WeakEntity<DockArea>,
325        bounds: Option<Bounds<Pixels>>,
326        window: &mut Window,
327        cx: &mut App,
328    ) {
329        match self {
330            Self::Tabs { view, items, .. } => {
331                items.push(panel.clone());
332                view.update(cx, |tab_panel, cx| {
333                    tab_panel.add_panel(panel, window, cx);
334                });
335            }
336            Self::Split { view, items, .. } => {
337                // Iter items to add panel to the first tabs
338                for item in items.into_iter() {
339                    if let DockItem::Tabs { view, .. } = item {
340                        view.update(cx, |tab_panel, cx| {
341                            tab_panel.add_panel(panel.clone(), window, cx);
342                        });
343                        return;
344                    }
345                }
346
347                // Unable to find tabs, create new tabs
348                let new_item = Self::tabs(vec![panel.clone()], None, dock_area, window, cx);
349                items.push(new_item.clone());
350                view.update(cx, |stack_panel, cx| {
351                    stack_panel.add_panel(new_item.view(), None, dock_area.clone(), window, cx);
352                });
353            }
354            Self::Tiles { view, items } => {
355                let tile_item = TileItem::new(
356                    Arc::new(cx.new(|cx| {
357                        let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx);
358                        tab_panel.add_panel(panel.clone(), window, cx);
359                        tab_panel
360                    })),
361                    bounds.unwrap_or_else(|| TileMeta::default().bounds),
362                );
363
364                items.push(tile_item.clone());
365                view.update(cx, |tiles, cx| {
366                    tiles.add_item(tile_item, dock_area, window, cx);
367                });
368            }
369            Self::Panel { .. } => {}
370        }
371    }
372
373    /// Remove a panel from the dock item.
374    pub fn remove_panel(&self, panel: Arc<dyn PanelView>, window: &mut Window, cx: &mut App) {
375        match self {
376            DockItem::Tabs { view, .. } => {
377                view.update(cx, |tab_panel, cx| {
378                    tab_panel.remove_panel(panel, window, cx);
379                });
380            }
381            DockItem::Split { items, view, .. } => {
382                // For each child item, set collapsed state
383                for item in items {
384                    item.remove_panel(panel.clone(), window, cx);
385                }
386                view.update(cx, |split, cx| {
387                    split.remove_panel(panel, window, cx);
388                });
389            }
390            DockItem::Tiles { view, .. } => {
391                view.update(cx, |tiles, cx| {
392                    tiles.remove(panel, window, cx);
393                });
394            }
395            DockItem::Panel { .. } => {}
396        }
397    }
398
399    pub fn set_collapsed(&self, collapsed: bool, window: &mut Window, cx: &mut App) {
400        match self {
401            DockItem::Tabs { view, .. } => {
402                view.update(cx, |tab_panel, cx| {
403                    tab_panel.set_collapsed(collapsed, window, cx);
404                });
405            }
406            DockItem::Split { items, .. } => {
407                // For each child item, set collapsed state
408                for item in items {
409                    item.set_collapsed(collapsed, window, cx);
410                }
411            }
412            DockItem::Tiles { .. } => {}
413            DockItem::Panel { view } => view.set_active(!collapsed, window, cx),
414        }
415    }
416
417    /// Recursively traverses to find the left-most and top-most TabPanel.
418    pub(crate) fn left_top_tab_panel(&self, cx: &App) -> Option<Entity<TabPanel>> {
419        match self {
420            DockItem::Tabs { view, .. } => Some(view.clone()),
421            DockItem::Split { view, .. } => view.read(cx).left_top_tab_panel(true, cx),
422            DockItem::Tiles { .. } => None,
423            DockItem::Panel { .. } => None,
424        }
425    }
426
427    /// Recursively traverses to find the right-most and top-most TabPanel.
428    pub(crate) fn right_top_tab_panel(&self, cx: &App) -> Option<Entity<TabPanel>> {
429        match self {
430            DockItem::Tabs { view, .. } => Some(view.clone()),
431            DockItem::Split { view, .. } => view.read(cx).right_top_tab_panel(true, cx),
432            DockItem::Tiles { .. } => None,
433            DockItem::Panel { .. } => None,
434        }
435    }
436}
437
438impl DockArea {
439    pub fn new(
440        id: impl Into<SharedString>,
441        version: Option<usize>,
442        window: &mut Window,
443        cx: &mut Context<Self>,
444    ) -> Self {
445        let stack_panel = cx.new(|cx| StackPanel::new(Axis::Horizontal, window, cx));
446
447        let dock_item = DockItem::Split {
448            axis: Axis::Horizontal,
449            items: vec![],
450            sizes: vec![],
451            view: stack_panel.clone(),
452        };
453
454        let mut this = Self {
455            id: id.into(),
456            version,
457            bounds: Bounds::default(),
458            items: dock_item,
459            zoom_view: None,
460            toggle_button_panels: Edges::default(),
461            toggle_button_visible: true,
462            left_dock: None,
463            right_dock: None,
464            bottom_dock: None,
465            locked: false,
466            panel_style: PanelStyle::Default,
467            _subscriptions: vec![],
468        };
469
470        this.subscribe_panel(&stack_panel, window, cx);
471
472        this
473    }
474
475    /// Return the bounds of the dock area.
476    pub fn bounds(&self) -> Bounds<Pixels> {
477        self.bounds
478    }
479
480    /// Return the items of the dock area.
481    pub fn items(&self) -> &DockItem {
482        &self.items
483    }
484
485    /// Subscribe to the tiles item drag item drop event
486    fn subscribe_tiles_item_drop(
487        &mut self,
488        tile_panel: &Entity<Tiles>,
489        _: &mut Window,
490        cx: &mut Context<Self>,
491    ) {
492        self._subscriptions
493            .push(cx.subscribe(tile_panel, move |_, _, evt: &DragDrop, cx| {
494                let item = evt.0.clone();
495                cx.emit(DockEvent::DragDrop(item));
496            }));
497    }
498
499    /// Set the panel style of the dock area.
500    pub fn panel_style(mut self, style: PanelStyle) -> Self {
501        self.panel_style = style;
502        self
503    }
504
505    /// Set version of the dock area.
506    pub fn set_version(&mut self, version: usize, _: &mut Window, cx: &mut Context<Self>) {
507        self.version = Some(version);
508        cx.notify();
509    }
510
511    // FIXME: Remove this method after 2025-01-01
512    #[deprecated(note = "Use `set_center` instead")]
513    pub fn set_root(&mut self, item: DockItem, window: &mut Window, cx: &mut Context<Self>) {
514        self.set_center(item, window, cx);
515    }
516
517    /// The the DockItem as the center of the dock area.
518    ///
519    /// This is used to render at the Center of the DockArea.
520    pub fn set_center(&mut self, item: DockItem, window: &mut Window, cx: &mut Context<Self>) {
521        self.subscribe_item(&item, window, cx);
522        self.items = item;
523        self.update_toggle_button_tab_panels(window, cx);
524        cx.notify();
525    }
526
527    pub fn set_left_dock(
528        &mut self,
529        panel: DockItem,
530        size: Option<Pixels>,
531        open: bool,
532        window: &mut Window,
533        cx: &mut Context<Self>,
534    ) {
535        self.subscribe_item(&panel, window, cx);
536        let weak_self = cx.entity().downgrade();
537        self.left_dock = Some(cx.new(|cx| {
538            let mut dock = Dock::left(weak_self.clone(), window, cx);
539            if let Some(size) = size {
540                dock.set_size(size, window, cx);
541            }
542            dock.set_panel(panel, window, cx);
543            dock.set_open(open, window, cx);
544            dock
545        }));
546        self.update_toggle_button_tab_panels(window, cx);
547    }
548
549    pub fn set_bottom_dock(
550        &mut self,
551        panel: DockItem,
552        size: Option<Pixels>,
553        open: bool,
554        window: &mut Window,
555        cx: &mut Context<Self>,
556    ) {
557        self.subscribe_item(&panel, window, cx);
558        let weak_self = cx.entity().downgrade();
559        self.bottom_dock = Some(cx.new(|cx| {
560            let mut dock = Dock::bottom(weak_self.clone(), window, cx);
561            if let Some(size) = size {
562                dock.set_size(size, window, cx);
563            }
564            dock.set_panel(panel, window, cx);
565            dock.set_open(open, window, cx);
566            dock
567        }));
568        self.update_toggle_button_tab_panels(window, cx);
569    }
570
571    pub fn set_right_dock(
572        &mut self,
573        panel: DockItem,
574        size: Option<Pixels>,
575        open: bool,
576        window: &mut Window,
577        cx: &mut Context<Self>,
578    ) {
579        self.subscribe_item(&panel, window, cx);
580        let weak_self = cx.entity().downgrade();
581        self.right_dock = Some(cx.new(|cx| {
582            let mut dock = Dock::right(weak_self.clone(), window, cx);
583            if let Some(size) = size {
584                dock.set_size(size, window, cx);
585            }
586            dock.set_panel(panel, window, cx);
587            dock.set_open(open, window, cx);
588            dock
589        }));
590        self.update_toggle_button_tab_panels(window, cx);
591    }
592
593    /// Set locked state of the dock area, if locked, the dock area cannot be split or move, but allows to resize panels.
594    pub fn set_locked(&mut self, locked: bool, _window: &mut Window, _cx: &mut App) {
595        self.locked = locked;
596    }
597
598    /// Determine if the dock area is locked.
599    #[inline]
600    pub fn is_locked(&self) -> bool {
601        self.locked
602    }
603
604    /// Determine if the dock area has a dock at the given placement.
605    pub fn has_dock(&self, placement: DockPlacement) -> bool {
606        match placement {
607            DockPlacement::Left => self.left_dock.is_some(),
608            DockPlacement::Bottom => self.bottom_dock.is_some(),
609            DockPlacement::Right => self.right_dock.is_some(),
610            DockPlacement::Center => false,
611        }
612    }
613
614    /// Determine if the dock at the given placement is open.
615    pub fn is_dock_open(&self, placement: DockPlacement, cx: &App) -> bool {
616        match placement {
617            DockPlacement::Left => self
618                .left_dock
619                .as_ref()
620                .map(|dock| dock.read(cx).is_open())
621                .unwrap_or(false),
622            DockPlacement::Bottom => self
623                .bottom_dock
624                .as_ref()
625                .map(|dock| dock.read(cx).is_open())
626                .unwrap_or(false),
627            DockPlacement::Right => self
628                .right_dock
629                .as_ref()
630                .map(|dock| dock.read(cx).is_open())
631                .unwrap_or(false),
632            DockPlacement::Center => false,
633        }
634    }
635
636    /// Set the dock at the given placement to be open or closed.
637    ///
638    /// Only the left, bottom, right dock can be toggled.
639    pub fn set_dock_collapsible(
640        &mut self,
641        collapsible_edges: Edges<bool>,
642        window: &mut Window,
643        cx: &mut Context<Self>,
644    ) {
645        if let Some(left_dock) = self.left_dock.as_ref() {
646            left_dock.update(cx, |dock, cx| {
647                dock.set_collapsible(collapsible_edges.left, window, cx);
648            });
649        }
650
651        if let Some(bottom_dock) = self.bottom_dock.as_ref() {
652            bottom_dock.update(cx, |dock, cx| {
653                dock.set_collapsible(collapsible_edges.bottom, window, cx);
654            });
655        }
656
657        if let Some(right_dock) = self.right_dock.as_ref() {
658            right_dock.update(cx, |dock, cx| {
659                dock.set_collapsible(collapsible_edges.right, window, cx);
660            });
661        }
662    }
663
664    /// Determine if the dock at the given placement is collapsible.
665    pub fn is_dock_collapsible(&self, placement: DockPlacement, cx: &App) -> bool {
666        match placement {
667            DockPlacement::Left => self
668                .left_dock
669                .as_ref()
670                .map(|dock| dock.read(cx).collapsible)
671                .unwrap_or(false),
672            DockPlacement::Bottom => self
673                .bottom_dock
674                .as_ref()
675                .map(|dock| dock.read(cx).collapsible)
676                .unwrap_or(false),
677            DockPlacement::Right => self
678                .right_dock
679                .as_ref()
680                .map(|dock| dock.read(cx).collapsible)
681                .unwrap_or(false),
682            DockPlacement::Center => false,
683        }
684    }
685
686    /// Toggle the dock at the given placement.
687    pub fn toggle_dock(
688        &self,
689        placement: DockPlacement,
690        window: &mut Window,
691        cx: &mut Context<Self>,
692    ) {
693        let dock = match placement {
694            DockPlacement::Left => &self.left_dock,
695            DockPlacement::Bottom => &self.bottom_dock,
696            DockPlacement::Right => &self.right_dock,
697            DockPlacement::Center => return,
698        };
699
700        if let Some(dock) = dock {
701            dock.update(cx, |view, cx| {
702                view.toggle_open(window, cx);
703            })
704        }
705    }
706
707    /// Set the visibility of the toggle button.
708    pub fn set_toggle_button_visible(&mut self, visible: bool, _: &mut Context<Self>) {
709        self.toggle_button_visible = visible;
710    }
711
712    /// Add a panel item to the dock area at the given placement.
713    pub fn add_panel(
714        &mut self,
715        panel: Arc<dyn PanelView>,
716        placement: DockPlacement,
717        bounds: Option<Bounds<Pixels>>,
718        window: &mut Window,
719        cx: &mut Context<Self>,
720    ) {
721        let weak_self = cx.entity().downgrade();
722        match placement {
723            DockPlacement::Left => {
724                if let Some(dock) = self.left_dock.as_ref() {
725                    dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
726                } else {
727                    self.set_left_dock(
728                        DockItem::tabs(vec![panel], None, &weak_self, window, cx),
729                        None,
730                        true,
731                        window,
732                        cx,
733                    );
734                }
735            }
736            DockPlacement::Bottom => {
737                if let Some(dock) = self.bottom_dock.as_ref() {
738                    dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
739                } else {
740                    self.set_bottom_dock(
741                        DockItem::tabs(vec![panel], None, &weak_self, window, cx),
742                        None,
743                        true,
744                        window,
745                        cx,
746                    );
747                }
748            }
749            DockPlacement::Right => {
750                if let Some(dock) = self.right_dock.as_ref() {
751                    dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
752                } else {
753                    self.set_right_dock(
754                        DockItem::tabs(vec![panel], None, &weak_self, window, cx),
755                        None,
756                        true,
757                        window,
758                        cx,
759                    );
760                }
761            }
762            DockPlacement::Center => {
763                self.items
764                    .add_panel(panel, &cx.entity().downgrade(), bounds, window, cx);
765            }
766        }
767    }
768
769    /// Remove panel from the DockArea at the given placement.
770    pub fn remove_panel(
771        &mut self,
772        panel: Arc<dyn PanelView>,
773        placement: DockPlacement,
774        window: &mut Window,
775        cx: &mut Context<Self>,
776    ) {
777        match placement {
778            DockPlacement::Left => {
779                if let Some(dock) = self.left_dock.as_mut() {
780                    dock.update(cx, |dock, cx| {
781                        dock.remove_panel(panel, window, cx);
782                    });
783                }
784            }
785            DockPlacement::Right => {
786                if let Some(dock) = self.right_dock.as_mut() {
787                    dock.update(cx, |dock, cx| {
788                        dock.remove_panel(panel, window, cx);
789                    });
790                }
791            }
792            DockPlacement::Bottom => {
793                if let Some(dock) = self.bottom_dock.as_mut() {
794                    dock.update(cx, |dock, cx| {
795                        dock.remove_panel(panel, window, cx);
796                    });
797                }
798            }
799            DockPlacement::Center => {
800                self.items.remove_panel(panel, window, cx);
801            }
802        }
803        cx.notify();
804    }
805
806    /// Remove a panel from all docks.
807    pub fn remove_panel_from_all_docks(
808        &mut self,
809        panel: Arc<dyn PanelView>,
810        window: &mut Window,
811        cx: &mut Context<Self>,
812    ) {
813        self.remove_panel(panel.clone(), DockPlacement::Center, window, cx);
814        self.remove_panel(panel.clone(), DockPlacement::Left, window, cx);
815        self.remove_panel(panel.clone(), DockPlacement::Right, window, cx);
816        self.remove_panel(panel.clone(), DockPlacement::Bottom, window, cx);
817    }
818
819    /// Load the state of the DockArea from the DockAreaState.
820    ///
821    /// See also [DockeArea::dump].
822    pub fn load(
823        &mut self,
824        state: DockAreaState,
825        window: &mut Window,
826        cx: &mut Context<Self>,
827    ) -> Result<()> {
828        self.version = state.version;
829        let weak_self = cx.entity().downgrade();
830
831        if let Some(left_dock_state) = state.left_dock {
832            self.left_dock = Some(left_dock_state.to_dock(weak_self.clone(), window, cx));
833        }
834
835        if let Some(right_dock_state) = state.right_dock {
836            self.right_dock = Some(right_dock_state.to_dock(weak_self.clone(), window, cx));
837        }
838
839        if let Some(bottom_dock_state) = state.bottom_dock {
840            self.bottom_dock = Some(bottom_dock_state.to_dock(weak_self.clone(), window, cx));
841        }
842
843        self.items = state.center.to_item(weak_self, window, cx);
844        self.update_toggle_button_tab_panels(window, cx);
845        Ok(())
846    }
847
848    /// Dump the dock panels layout to PanelState.
849    ///
850    /// See also [DockArea::load].
851    pub fn dump(&self, cx: &App) -> DockAreaState {
852        let root = self.items.view();
853        let center = root.dump(cx);
854
855        let left_dock = self
856            .left_dock
857            .as_ref()
858            .map(|dock| DockState::new(dock.clone(), cx));
859        let right_dock = self
860            .right_dock
861            .as_ref()
862            .map(|dock| DockState::new(dock.clone(), cx));
863        let bottom_dock = self
864            .bottom_dock
865            .as_ref()
866            .map(|dock| DockState::new(dock.clone(), cx));
867
868        DockAreaState {
869            version: self.version,
870            center,
871            left_dock,
872            right_dock,
873            bottom_dock,
874        }
875    }
876
877    /// Subscribe event on the panels
878    #[allow(clippy::only_used_in_recursion)]
879    fn subscribe_item(&mut self, item: &DockItem, window: &mut Window, cx: &mut Context<Self>) {
880        match item {
881            DockItem::Split { items, view, .. } => {
882                for item in items {
883                    self.subscribe_item(item, window, cx);
884                }
885
886                self._subscriptions.push(cx.subscribe_in(
887                    view,
888                    window,
889                    move |_, _, event, window, cx| match event {
890                        PanelEvent::LayoutChanged => {
891                            cx.spawn_in(window, async move |view, window| {
892                                _ = view.update_in(window, |view, window, cx| {
893                                    view.update_toggle_button_tab_panels(window, cx)
894                                });
895                            })
896                            .detach();
897                            cx.emit(DockEvent::LayoutChanged);
898                        }
899                        _ => {}
900                    },
901                ));
902            }
903            DockItem::Tabs { .. } => {
904                // We subscribe to the tab panel event in StackPanel's insert_panel
905            }
906            DockItem::Tiles { .. } => {
907                // We subscribe to the tab panel event in Tiles's [`add_item`](Tiles::add_item)
908            }
909            DockItem::Panel { .. } => {
910                // Not supported
911            }
912        }
913    }
914
915    /// Subscribe zoom event on the panel
916    pub(crate) fn subscribe_panel<P: Panel>(
917        &mut self,
918        view: &Entity<P>,
919        window: &mut Window,
920        cx: &mut Context<DockArea>,
921    ) {
922        let subscription =
923            cx.subscribe_in(
924                view,
925                window,
926                move |_, panel, event, window, cx| match event {
927                    PanelEvent::ZoomIn => {
928                        let panel = panel.clone();
929                        cx.spawn_in(window, async move |view, window| {
930                            _ = view.update_in(window, |view, window, cx| {
931                                view.set_zoomed_in(panel, window, cx);
932                                cx.notify();
933                            });
934                        })
935                        .detach();
936                    }
937                    PanelEvent::ZoomOut => cx
938                        .spawn_in(window, async move |view, window| {
939                            _ = view.update_in(window, |view, window, cx| {
940                                view.set_zoomed_out(window, cx);
941                            });
942                        })
943                        .detach(),
944                    PanelEvent::LayoutChanged => {
945                        cx.spawn_in(window, async move |view, window| {
946                            _ = view.update_in(window, |view, window, cx| {
947                                view.update_toggle_button_tab_panels(window, cx)
948                            });
949                        })
950                        .detach();
951                        cx.emit(DockEvent::LayoutChanged);
952                    }
953                },
954            );
955
956        self._subscriptions.push(subscription);
957    }
958
959    /// Returns the ID of the dock area.
960    pub fn id(&self) -> SharedString {
961        self.id.clone()
962    }
963
964    pub fn set_zoomed_in<P: Panel>(
965        &mut self,
966        panel: Entity<P>,
967        _: &mut Window,
968        cx: &mut Context<Self>,
969    ) {
970        self.zoom_view = Some(panel.into());
971        cx.notify();
972    }
973
974    pub fn set_zoomed_out(&mut self, _: &mut Window, cx: &mut Context<Self>) {
975        self.zoom_view = None;
976        cx.notify();
977    }
978
979    fn render_items(&self, _window: &mut Window, _cx: &mut Context<Self>) -> AnyElement {
980        match &self.items {
981            DockItem::Split { view, .. } => view.clone().into_any_element(),
982            DockItem::Tabs { view, .. } => view.clone().into_any_element(),
983            DockItem::Tiles { view, .. } => view.clone().into_any_element(),
984            DockItem::Panel { view, .. } => view.clone().view().into_any_element(),
985        }
986    }
987
988    pub fn update_toggle_button_tab_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {
989        // Left toggle button
990        self.toggle_button_panels.left = self
991            .items
992            .left_top_tab_panel(cx)
993            .map(|view| view.entity_id());
994
995        // Right toggle button
996        self.toggle_button_panels.right = self
997            .items
998            .right_top_tab_panel(cx)
999            .map(|view| view.entity_id());
1000
1001        // Bottom toggle button
1002        self.toggle_button_panels.bottom = self
1003            .bottom_dock
1004            .as_ref()
1005            .and_then(|dock| dock.read(cx).panel.left_top_tab_panel(cx))
1006            .map(|view| view.entity_id());
1007    }
1008}
1009impl EventEmitter<DockEvent> for DockArea {}
1010impl Render for DockArea {
1011    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1012        let view = cx.entity().clone();
1013
1014        div()
1015            .id("dock-area")
1016            .relative()
1017            .size_full()
1018            .overflow_hidden()
1019            .child(
1020                canvas(
1021                    move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
1022                    |_, _, _, _| {},
1023                )
1024                .absolute()
1025                .size_full(),
1026            )
1027            .map(|this| {
1028                if let Some(zoom_view) = self.zoom_view.clone() {
1029                    this.child(zoom_view)
1030                } else {
1031                    match &self.items {
1032                        DockItem::Tiles { view, .. } => {
1033                            // render tiles
1034                            this.child(view.clone())
1035                        }
1036                        _ => {
1037                            // render dock
1038                            this.child(
1039                                div()
1040                                    .flex()
1041                                    .flex_row()
1042                                    .h_full()
1043                                    // Left dock
1044                                    .when_some(self.left_dock.clone(), |this, dock| {
1045                                        this.child(div().flex().flex_none().child(dock))
1046                                    })
1047                                    // Center
1048                                    .child(
1049                                        div()
1050                                            .flex()
1051                                            .flex_1()
1052                                            .flex_col()
1053                                            .overflow_hidden()
1054                                            // Top center
1055                                            .child(
1056                                                div()
1057                                                    .flex_1()
1058                                                    .overflow_hidden()
1059                                                    .child(self.render_items(window, cx)),
1060                                            )
1061                                            // Bottom Dock
1062                                            .when_some(self.bottom_dock.clone(), |this, dock| {
1063                                                this.child(dock)
1064                                            }),
1065                                    )
1066                                    // Right Dock
1067                                    .when_some(self.right_dock.clone(), |this, dock| {
1068                                        this.child(div().flex().flex_none().child(dock))
1069                                    }),
1070                            )
1071                        }
1072                    }
1073                }
1074            })
1075    }
1076}