gpui_component/dock/
panel.rs

1use std::{collections::HashMap, sync::Arc};
2
3use crate::{button::Button, popup_menu::PopupMenu};
4use gpui::{
5    AnyElement, AnyView, App, AppContext as _, Entity, EntityId, EventEmitter, FocusHandle,
6    Focusable, Global, Hsla, IntoElement, Render, SharedString, WeakEntity, Window,
7};
8
9use rust_i18n::t;
10
11use super::{invalid_panel::InvalidPanel, DockArea, PanelInfo, PanelState};
12
13pub enum PanelEvent {
14    ZoomIn,
15    ZoomOut,
16    LayoutChanged,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PanelStyle {
21    /// Display the TabBar when there are multiple tabs, otherwise display the simple title.
22    Default,
23    /// Always display the tab bar.
24    TabBar,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct TitleStyle {
29    pub background: Hsla,
30    pub foreground: Hsla,
31}
32
33#[derive(Clone, Copy, Default)]
34pub enum PanelControl {
35    Both,
36    #[default]
37    Menu,
38    Toolbar,
39}
40
41impl PanelControl {
42    #[inline]
43    pub fn toolbar_visible(&self) -> bool {
44        matches!(self, PanelControl::Both | PanelControl::Toolbar)
45    }
46
47    #[inline]
48    pub fn menu_visible(&self) -> bool {
49        matches!(self, PanelControl::Both | PanelControl::Menu)
50    }
51}
52
53/// The Panel trait used to define the panel.
54#[allow(unused_variables)]
55pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
56    /// The name of the panel used to serialize, deserialize and identify the panel.
57    ///
58    /// This is used to identify the panel when deserializing the panel.
59    /// Once you have defined a panel name, this must not be changed.
60    fn panel_name(&self) -> &'static str;
61
62    /// The name of the tab of the panel, default is `None`.
63    ///
64    /// Used to display in the already collapsed tab panel.
65    fn tab_name(&self, cx: &App) -> Option<SharedString> {
66        None
67    }
68
69    /// The title of the panel
70    fn title(&self, window: &Window, cx: &App) -> AnyElement {
71        SharedString::from(t!("Dock.Unnamed")).into_any_element()
72    }
73
74    /// The theme of the panel title, default is `None`.
75    fn title_style(&self, cx: &App) -> Option<TitleStyle> {
76        None
77    }
78
79    /// The suffix of the panel title, default is `None`.
80    ///
81    /// This is used to add a suffix element to the panel title.
82    fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {
83        None
84    }
85
86    /// Whether the panel can be closed, default is `true`.
87    ///
88    /// This method called in Panel render, we should make sure it is fast.
89    fn closable(&self, cx: &App) -> bool {
90        true
91    }
92
93    /// Return `PanelControl` if the panel is zoomable, default is `PanelControl::Menu`.
94    ///
95    /// This method called in Panel render, we should make sure it is fast.
96    fn zoomable(&self, cx: &App) -> Option<PanelControl> {
97        Some(PanelControl::Menu)
98    }
99
100    /// Return false to hide panel, true to show panel, default is `true`.
101    ///
102    /// This method called in Panel render, we should make sure it is fast.
103    fn visible(&self, cx: &App) -> bool {
104        true
105    }
106
107    /// Set active state of the panel.
108    ///
109    /// This method will be called when the panel is active or inactive.
110    ///
111    /// The last_active_panel and current_active_panel will be touched when the panel is active.
112    fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut App) {}
113
114    /// Set zoomed state of the panel.
115    ///
116    /// This method will be called when the panel is zoomed or unzoomed.
117    ///
118    /// Only current Panel will touch this method.
119    fn set_zoomed(&mut self, zoomed: bool, window: &mut Window, cx: &mut App) {}
120
121    /// The addition popup menu of the panel, default is `None`.
122    fn popup_menu(&self, this: PopupMenu, window: &Window, cx: &App) -> PopupMenu {
123        this
124    }
125
126    /// The addition toolbar buttons of the panel used to show in the right of the title bar, default is `None`.
127    fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {
128        None
129    }
130
131    /// Dump the panel, used to serialize the panel.
132    fn dump(&self, cx: &App) -> PanelState {
133        PanelState::new(self)
134    }
135
136    /// Whether the panel has inner padding when the panel is in the tabs layout, default is `true`.
137    fn inner_padding(&self, cx: &App) -> bool {
138        true
139    }
140}
141
142/// The PanelView trait used to define the panel view.
143#[allow(unused_variables)]
144pub trait PanelView: 'static + Send + Sync {
145    fn panel_name(&self, cx: &App) -> &'static str;
146    fn panel_id(&self, cx: &App) -> EntityId;
147    fn tab_name(&self, cx: &App) -> Option<SharedString>;
148    fn title(&self, window: &Window, cx: &App) -> AnyElement;
149    fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement>;
150    fn title_style(&self, cx: &App) -> Option<TitleStyle>;
151    fn closable(&self, cx: &App) -> bool;
152    fn zoomable(&self, cx: &App) -> Option<PanelControl>;
153    fn visible(&self, cx: &App) -> bool;
154    fn set_active(&self, active: bool, window: &mut Window, cx: &mut App);
155    fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App);
156    fn popup_menu(&self, menu: PopupMenu, window: &Window, cx: &App) -> PopupMenu;
157    fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>>;
158    fn view(&self) -> AnyView;
159    fn focus_handle(&self, cx: &App) -> FocusHandle;
160    fn dump(&self, cx: &App) -> PanelState;
161    fn inner_padding(&self, cx: &App) -> bool;
162}
163
164impl<T: Panel> PanelView for Entity<T> {
165    fn panel_name(&self, cx: &App) -> &'static str {
166        self.read(cx).panel_name()
167    }
168
169    fn panel_id(&self, _: &App) -> EntityId {
170        self.entity_id()
171    }
172
173    fn tab_name(&self, cx: &App) -> Option<SharedString> {
174        self.read(cx).tab_name(cx)
175    }
176
177    fn title(&self, window: &Window, cx: &App) -> AnyElement {
178        self.read(cx).title(window, cx)
179    }
180
181    fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {
182        self.update(cx, |this, cx| this.title_suffix(window, cx))
183    }
184
185    fn title_style(&self, cx: &App) -> Option<TitleStyle> {
186        self.read(cx).title_style(cx)
187    }
188
189    fn closable(&self, cx: &App) -> bool {
190        self.read(cx).closable(cx)
191    }
192
193    fn zoomable(&self, cx: &App) -> Option<PanelControl> {
194        self.read(cx).zoomable(cx)
195    }
196
197    fn visible(&self, cx: &App) -> bool {
198        self.read(cx).visible(cx)
199    }
200
201    fn set_active(&self, active: bool, window: &mut Window, cx: &mut App) {
202        self.update(cx, |this, cx| {
203            this.set_active(active, window, cx);
204        })
205    }
206
207    fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App) {
208        self.update(cx, |this, cx| {
209            this.set_zoomed(zoomed, window, cx);
210        })
211    }
212
213    fn popup_menu(&self, menu: PopupMenu, window: &Window, cx: &App) -> PopupMenu {
214        self.read(cx).popup_menu(menu, window, cx)
215    }
216
217    fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {
218        self.update(cx, |this, cx| this.toolbar_buttons(window, cx))
219    }
220
221    fn view(&self) -> AnyView {
222        self.clone().into()
223    }
224
225    fn focus_handle(&self, cx: &App) -> FocusHandle {
226        self.read(cx).focus_handle(cx)
227    }
228
229    fn dump(&self, cx: &App) -> PanelState {
230        self.read(cx).dump(cx)
231    }
232
233    fn inner_padding(&self, cx: &App) -> bool {
234        self.read(cx).inner_padding(cx)
235    }
236}
237
238impl From<&dyn PanelView> for AnyView {
239    fn from(handle: &dyn PanelView) -> Self {
240        handle.view()
241    }
242}
243
244impl<T: Panel> From<&dyn PanelView> for Entity<T> {
245    fn from(value: &dyn PanelView) -> Self {
246        value.view().downcast::<T>().unwrap()
247    }
248}
249
250impl PartialEq for dyn PanelView {
251    fn eq(&self, other: &Self) -> bool {
252        self.view() == other.view()
253    }
254}
255
256pub struct PanelRegistry {
257    pub(super) items: HashMap<
258        String,
259        Arc<
260            dyn Fn(
261                WeakEntity<DockArea>,
262                &PanelState,
263                &PanelInfo,
264                &mut Window,
265                &mut App,
266            ) -> Box<dyn PanelView>,
267        >,
268    >,
269}
270impl PanelRegistry {
271    /// Initialize the panel registry.
272    pub(crate) fn init(cx: &mut App) {
273        if let None = cx.try_global::<PanelRegistry>() {
274            cx.set_global(PanelRegistry::new());
275        }
276    }
277
278    pub fn new() -> Self {
279        Self {
280            items: HashMap::new(),
281        }
282    }
283
284    pub fn global(cx: &App) -> &Self {
285        cx.global::<PanelRegistry>()
286    }
287
288    pub fn global_mut(cx: &mut App) -> &mut Self {
289        cx.global_mut::<PanelRegistry>()
290    }
291
292    /// Build a panel by name.
293    ///
294    /// If not registered, return InvalidPanel.
295    pub fn build_panel(
296        panel_name: &str,
297        dock_area: WeakEntity<DockArea>,
298        panel_state: &PanelState,
299        panel_info: &PanelInfo,
300        window: &mut Window,
301        cx: &mut App,
302    ) -> Box<dyn PanelView> {
303        if let Some(view) = Self::global(cx)
304            .items
305            .get(panel_name)
306            .cloned()
307            .map(|f| f(dock_area, panel_state, panel_info, window, cx))
308        {
309            return view;
310        } else {
311            // Show an invalid panel if the panel is not registered.
312            Box::new(cx.new(|cx| InvalidPanel::new(&panel_name, panel_state.clone(), window, cx)))
313        }
314    }
315}
316impl Global for PanelRegistry {}
317
318/// Register the Panel init by panel_name to global registry.
319pub fn register_panel<F>(cx: &mut App, panel_name: &str, deserialize: F)
320where
321    F: Fn(
322            WeakEntity<DockArea>,
323            &PanelState,
324            &PanelInfo,
325            &mut Window,
326            &mut App,
327        ) -> Box<dyn PanelView>
328        + 'static,
329{
330    PanelRegistry::init(cx);
331    PanelRegistry::global_mut(cx)
332        .items
333        .insert(panel_name.to_string(), Arc::new(deserialize));
334}