Skip to main content

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