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 Default,
23 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#[allow(unused_variables)]
55pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
56 fn panel_name(&self) -> &'static str;
61
62 fn tab_name(&self, cx: &App) -> Option<SharedString> {
66 None
67 }
68
69 fn title(&self, window: &Window, cx: &App) -> AnyElement {
71 SharedString::from(t!("Dock.Unnamed")).into_any_element()
72 }
73
74 fn title_style(&self, cx: &App) -> Option<TitleStyle> {
76 None
77 }
78
79 fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {
83 None
84 }
85
86 fn closable(&self, cx: &App) -> bool {
90 true
91 }
92
93 fn zoomable(&self, cx: &App) -> Option<PanelControl> {
97 Some(PanelControl::Menu)
98 }
99
100 fn visible(&self, cx: &App) -> bool {
104 true
105 }
106
107 fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut App) {}
113
114 fn set_zoomed(&mut self, zoomed: bool, window: &mut Window, cx: &mut App) {}
120
121 fn on_added_to(&mut self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App) {}
123
124 fn on_removed(&mut self, window: &mut Window, cx: &mut App) {}
126
127 fn dropdown_menu(&self, this: PopupMenu, window: &Window, cx: &App) -> PopupMenu {
129 this
130 }
131
132 fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {
134 None
135 }
136
137 fn dump(&self, cx: &App) -> PanelState {
139 PanelState::new(self)
140 }
141
142 fn inner_padding(&self, cx: &App) -> bool {
144 true
145 }
146}
147
148#[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 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 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 Box::new(cx.new(|cx| InvalidPanel::new(&panel_name, panel_state.clone(), window, cx)))
329 }
330 }
331}
332impl Global for PanelRegistry {}
333
334pub 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}