gpui_component/dock/
panel.rs

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