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 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 popup_menu(&self, this: PopupMenu, window: &Window, cx: &App) -> PopupMenu {
123 this
124 }
125
126 fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {
128 None
129 }
130
131 fn dump(&self, cx: &App) -> PanelState {
133 PanelState::new(self)
134 }
135
136 fn inner_padding(&self, cx: &App) -> bool {
138 true
139 }
140}
141
142#[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 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 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 Box::new(cx.new(|cx| InvalidPanel::new(&panel_name, panel_state.clone(), window, cx)))
313 }
314 }
315}
316impl Global for PanelRegistry {}
317
318pub 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}