Skip to main content

gpui_base/dock/
registry.rs

1use std::{collections::HashMap, sync::Arc};
2
3use gpui::{App, Global, WeakEntity, Window};
4
5use super::{DockArea, PanelInfo, PanelState, PanelView};
6
7/// Everything a panel builder needs to reconstruct a panel from persisted data.
8pub struct PanelBuildContext<'a> {
9    dock_area: WeakEntity<DockArea>,
10    state: &'a PanelState,
11    info: &'a PanelInfo,
12}
13
14impl<'a> PanelBuildContext<'a> {
15    pub fn new(
16        dock_area: WeakEntity<DockArea>,
17        state: &'a PanelState,
18        info: &'a PanelInfo,
19    ) -> Self {
20        Self {
21            dock_area,
22            state,
23            info,
24        }
25    }
26
27    pub fn dock_area(&self) -> WeakEntity<DockArea> {
28        self.dock_area.clone()
29    }
30
31    pub fn state(&self) -> &PanelState {
32        self.state
33    }
34
35    pub fn info(&self) -> &PanelInfo {
36        self.info
37    }
38}
39
40/// Global registry of panel builders, keyed by panel name, used to reconstruct
41/// a panel view from persisted [`PanelState`]/[`PanelInfo`] data.
42///
43/// A builder returns an [`Arc<dyn PanelView>`](PanelView), not a bare
44/// `AnyView`. An earlier revision of this module returned `AnyView` so that a
45/// builder would not have to depend on the panel traits, on the reasoning
46/// that "a caller that needs a richer handle downcasts or wraps the
47/// `AnyView` itself". [`DockArea::load`](super::DockArea::load) is that
48/// caller, and it cannot: downcasting needs the concrete type, which the
49/// registry is precisely the mechanism for not knowing. Without a
50/// `PanelView` a restored panel could not be asked for its
51/// [`dump`](PanelView::dump), so every registered panel's own persisted
52/// payload would be dropped on the first save after a load.
53///
54/// Building a panel that was never registered returns `None` rather than a
55/// placeholder view: rendering an "invalid panel" placeholder is presentation
56/// behavior that belongs above this seam, not inside `gpui-base`. `DockArea`
57/// substitutes a draw-nothing placeholder that carries the original
58/// `PanelState` forward, so an unknown panel survives a round trip.
59pub struct PanelRegistry {
60    items: HashMap<
61        String,
62        Arc<dyn Fn(PanelBuildContext, &mut Window, &mut App) -> Arc<dyn PanelView>>,
63    >,
64}
65
66impl PanelRegistry {
67    /// Initialize the panel registry.
68    pub(crate) fn init(cx: &mut App) {
69        if cx.try_global::<PanelRegistry>().is_none() {
70            cx.set_global(PanelRegistry::new());
71        }
72    }
73
74    pub fn new() -> Self {
75        Self {
76            items: HashMap::new(),
77        }
78    }
79
80    pub fn global(cx: &App) -> &Self {
81        cx.global::<PanelRegistry>()
82    }
83
84    pub fn global_mut(cx: &mut App) -> &mut Self {
85        cx.global_mut::<PanelRegistry>()
86    }
87
88    /// Build a panel by name.
89    ///
90    /// Returns `None` if no builder is registered for `panel_name`.
91    pub fn build_panel(
92        panel_name: &str,
93        context: PanelBuildContext,
94        window: &mut Window,
95        cx: &mut App,
96    ) -> Option<Arc<dyn PanelView>> {
97        let build = Self::global(cx).items.get(panel_name).cloned()?;
98        Some(build(context, window, cx))
99    }
100}
101
102impl Default for PanelRegistry {
103    fn default() -> Self {
104        Self::new()
105    }
106}
107
108impl Global for PanelRegistry {}
109
110/// Register the Panel init by panel_name to global registry.
111pub fn register_panel<F>(cx: &mut App, panel_name: &str, deserialize: F)
112where
113    F: Fn(PanelBuildContext, &mut Window, &mut App) -> Arc<dyn PanelView> + 'static,
114{
115    PanelRegistry::init(cx);
116    PanelRegistry::global_mut(cx)
117        .items
118        .insert(panel_name.to_string(), Arc::new(deserialize));
119}