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