gpui_component/dock/
panel.rs

1use std::{collections::HashMap, sync::Arc};
2
3use crate::{button::Button, dock::TabPanel, 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    /// When this Panel is added to a TabPanel, this will be called.
122    fn on_added_to(&mut self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App) {}
123
124    /// When this Panel is removed from a TabPanel, this will be called.
125    fn on_removed(&mut self, window: &mut Window, cx: &mut App) {}
126
127    /// The addition dropdown menu of the panel, default is `None`.
128    fn dropdown_menu(&self, this: PopupMenu, window: &Window, cx: &App) -> PopupMenu {
129        this
130    }
131
132    /// The addition toolbar buttons of the panel used to show in the right of the title bar, default is `None`.
133    fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {
134        None
135    }
136
137    /// Dump the panel, used to serialize the panel.
138    fn dump(&self, cx: &App) -> PanelState {
139        PanelState::new(self)
140    }
141
142    /// Whether the panel has inner padding when the panel is in the tabs layout, default is `true`.
143    fn inner_padding(&self, cx: &App) -> bool {
144        true
145    }
146}
147
148/// The PanelView trait used to define the panel view.
149#[allow(unused_variables)]
150pub trait PanelView: 'static + Send + Sync {
151    fn panel_name(&self, cx: &App) -> &'static str;
152    fn panel_id(&self, cx: &App) -> EntityId;
153    fn tab_name(&self, cx: &App) -> Option<SharedString>;
154    fn title(&self, window: &Window, cx: &App) -> AnyElement;
155    fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement>;
156    fn title_style(&self, cx: &App) -> Option<TitleStyle>;
157    fn closable(&self, cx: &App) -> bool;
158    fn zoomable(&self, cx: &App) -> Option<PanelControl>;
159    fn visible(&self, cx: &App) -> bool;
160    fn set_active(&self, active: bool, window: &mut Window, cx: &mut App);
161    fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App);
162    fn on_added_to(&self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App);
163    fn on_removed(&self, window: &mut Window, cx: &mut App);
164    fn dropdown_menu(&self, menu: PopupMenu, window: &Window, cx: &App) -> PopupMenu;
165    fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>>;
166    fn view(&self) -> AnyView;
167    fn focus_handle(&self, cx: &App) -> FocusHandle;
168    fn dump(&self, cx: &App) -> PanelState;
169    fn inner_padding(&self, cx: &App) -> bool;
170}
171
172impl<T: Panel> PanelView for Entity<T> {
173    fn panel_name(&self, cx: &App) -> &'static str {
174        self.read(cx).panel_name()
175    }
176
177    fn panel_id(&self, _: &App) -> EntityId {
178        self.entity_id()
179    }
180
181    fn tab_name(&self, cx: &App) -> Option<SharedString> {
182        self.read(cx).tab_name(cx)
183    }
184
185    fn title(&self, window: &Window, cx: &App) -> AnyElement {
186        self.read(cx).title(window, cx)
187    }
188
189    fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {
190        self.update(cx, |this, cx| this.title_suffix(window, cx))
191    }
192
193    fn title_style(&self, cx: &App) -> Option<TitleStyle> {
194        self.read(cx).title_style(cx)
195    }
196
197    fn closable(&self, cx: &App) -> bool {
198        self.read(cx).closable(cx)
199    }
200
201    fn zoomable(&self, cx: &App) -> Option<PanelControl> {
202        self.read(cx).zoomable(cx)
203    }
204
205    fn visible(&self, cx: &App) -> bool {
206        self.read(cx).visible(cx)
207    }
208
209    fn set_active(&self, active: bool, window: &mut Window, cx: &mut App) {
210        self.update(cx, |this, cx| {
211            this.set_active(active, window, cx);
212        })
213    }
214
215    fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App) {
216        self.update(cx, |this, cx| {
217            this.set_zoomed(zoomed, window, cx);
218        })
219    }
220
221    fn on_added_to(&self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App) {
222        self.update(cx, |this, cx| this.on_added_to(tab_panel, window, cx));
223    }
224
225    fn on_removed(&self, window: &mut Window, cx: &mut App) {
226        self.update(cx, |this, cx| this.on_removed(window, cx));
227    }
228
229    fn dropdown_menu(&self, menu: PopupMenu, window: &Window, cx: &App) -> PopupMenu {
230        self.read(cx).dropdown_menu(menu, window, cx)
231    }
232
233    fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {
234        self.update(cx, |this, cx| this.toolbar_buttons(window, cx))
235    }
236
237    fn view(&self) -> AnyView {
238        self.clone().into()
239    }
240
241    fn focus_handle(&self, cx: &App) -> FocusHandle {
242        self.read(cx).focus_handle(cx)
243    }
244
245    fn dump(&self, cx: &App) -> PanelState {
246        self.read(cx).dump(cx)
247    }
248
249    fn inner_padding(&self, cx: &App) -> bool {
250        self.read(cx).inner_padding(cx)
251    }
252}
253
254impl From<&dyn PanelView> for AnyView {
255    fn from(handle: &dyn PanelView) -> Self {
256        handle.view()
257    }
258}
259
260impl<T: Panel> From<&dyn PanelView> for Entity<T> {
261    fn from(value: &dyn PanelView) -> Self {
262        value.view().downcast::<T>().unwrap()
263    }
264}
265
266impl PartialEq for dyn PanelView {
267    fn eq(&self, other: &Self) -> bool {
268        self.view() == other.view()
269    }
270}
271
272pub struct PanelRegistry {
273    pub(super) items: HashMap<
274        String,
275        Arc<
276            dyn Fn(
277                WeakEntity<DockArea>,
278                &PanelState,
279                &PanelInfo,
280                &mut Window,
281                &mut App,
282            ) -> Box<dyn PanelView>,
283        >,
284    >,
285}
286impl PanelRegistry {
287    /// Initialize the panel registry.
288    pub(crate) fn init(cx: &mut App) {
289        if let None = cx.try_global::<PanelRegistry>() {
290            cx.set_global(PanelRegistry::new());
291        }
292    }
293
294    pub fn new() -> Self {
295        Self {
296            items: HashMap::new(),
297        }
298    }
299
300    pub fn global(cx: &App) -> &Self {
301        cx.global::<PanelRegistry>()
302    }
303
304    pub fn global_mut(cx: &mut App) -> &mut Self {
305        cx.global_mut::<PanelRegistry>()
306    }
307
308    /// Build a panel by name.
309    ///
310    /// If not registered, return InvalidPanel.
311    pub fn build_panel(
312        panel_name: &str,
313        dock_area: WeakEntity<DockArea>,
314        panel_state: &PanelState,
315        panel_info: &PanelInfo,
316        window: &mut Window,
317        cx: &mut App,
318    ) -> Box<dyn PanelView> {
319        if let Some(view) = Self::global(cx)
320            .items
321            .get(panel_name)
322            .cloned()
323            .map(|f| f(dock_area, panel_state, panel_info, window, cx))
324        {
325            return view;
326        } else {
327            // Show an invalid panel if the panel is not registered.
328            Box::new(cx.new(|cx| InvalidPanel::new(&panel_name, panel_state.clone(), window, cx)))
329        }
330    }
331}
332impl Global for PanelRegistry {}
333
334/// Register the Panel init by panel_name to global registry.
335pub fn register_panel<F>(cx: &mut App, panel_name: &str, deserialize: F)
336where
337    F: Fn(
338            WeakEntity<DockArea>,
339            &PanelState,
340            &PanelInfo,
341            &mut Window,
342            &mut App,
343        ) -> Box<dyn PanelView>
344        + 'static,
345{
346    PanelRegistry::init(cx);
347    PanelRegistry::global_mut(cx)
348        .items
349        .insert(panel_name.to_string(), Arc::new(deserialize));
350}