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 #[default]
21 Auto,
22 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#[allow(unused_variables)]
54pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
55 fn panel_name(&self) -> &'static str;
60
61 fn tab_name(&self, cx: &App) -> Option<SharedString> {
65 None
66 }
67
68 fn title(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
70 SharedString::from(t!("Dock.Unnamed"))
71 }
72
73 fn title_style(&self, cx: &App) -> Option<TitleStyle> {
75 None
76 }
77
78 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 fn closable(&self, cx: &App) -> bool {
93 true
94 }
95
96 fn zoomable(&self, cx: &App) -> Option<PanelControl> {
100 Some(PanelControl::Menu)
101 }
102
103 fn visible(&self, cx: &App) -> bool {
107 true
108 }
109
110 fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {}
116
117 fn set_zoomed(&mut self, zoomed: bool, window: &mut Window, cx: &mut Context<Self>) {}
123
124 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 fn on_removed(&mut self, window: &mut Window, cx: &mut Context<Self>) {}
135
136 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 fn toolbar_buttons(
148 &mut self,
149 window: &mut Window,
150 cx: &mut Context<Self>,
151 ) -> Option<Vec<Button>> {
152 None
153 }
154
155 fn dump(&self, cx: &App) -> PanelState {
157 PanelState::new(self)
158 }
159
160 fn inner_padding(&self, cx: &App) -> bool {
162 true
163 }
164}
165
166#[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 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 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 Box::new(cx.new(|cx| InvalidPanel::new(&panel_name, panel_state.clone(), window, cx)))
350 }
351 }
352}
353impl Global for PanelRegistry {}
354
355pub 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}