1use std::{any::Any, collections::HashMap, sync::Arc};
2
3use gpui::{
4 AnyView, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Render, WeakEntity, Window,
5};
6
7use super::layout::PanelId;
8use super::state::PanelState;
9use super::state_convert::PanelSource;
10use super::tab_group::TabGroup;
11
12pub enum PanelEvent {
13 ZoomIn,
14 ZoomOut,
15 LayoutChanged,
16}
17
18#[allow(unused_variables)]
21pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
22 fn panel_name(&self) -> &'static str;
24
25 fn visible(&self, cx: &App) -> bool {
29 true
30 }
31
32 fn closable(&self, cx: &App) -> bool {
35 true
36 }
37
38 fn zoomable(&self, cx: &App) -> bool {
41 true
42 }
43
44 fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {}
54
55 fn set_zoomed(&mut self, zoomed: bool, window: &mut Window, cx: &mut Context<Self>) {}
62
63 fn on_added_to(
69 &mut self,
70 group: WeakEntity<TabGroup>,
71 window: &mut Window,
72 cx: &mut Context<Self>,
73 ) {
74 }
75
76 fn on_removed(&mut self, window: &mut Window, cx: &mut Context<Self>) {}
83
84 fn dump(&self, cx: &App) -> PanelState {
91 PanelState::new(self.panel_name())
92 }
93}
94
95#[allow(unused_variables)]
98pub trait PanelView: 'static + Send + Sync {
99 fn panel_name(&self, cx: &App) -> &'static str;
100 fn panel_id(&self, cx: &App) -> PanelId;
101 fn closable(&self, cx: &App) -> bool;
102 fn zoomable(&self, cx: &App) -> bool;
103 fn visible(&self, cx: &App) -> bool;
104 fn set_active(&self, active: bool, window: &mut Window, cx: &mut App);
105 fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App);
106 fn on_added_to(&self, group: WeakEntity<TabGroup>, window: &mut Window, cx: &mut App);
107 fn on_removed(&self, window: &mut Window, cx: &mut App);
108 fn view(&self) -> AnyView;
109 fn focus_handle(&self, cx: &App) -> FocusHandle;
110 fn dump(&self, cx: &App) -> PanelState;
111
112 fn as_any(&self) -> &dyn Any;
132}
133
134impl<T: Panel> PanelView for Entity<T> {
135 fn panel_name(&self, cx: &App) -> &'static str {
136 self.read(cx).panel_name()
137 }
138
139 fn panel_id(&self, _: &App) -> PanelId {
140 PanelId::from(self.entity_id())
141 }
142
143 fn closable(&self, cx: &App) -> bool {
144 self.read(cx).closable(cx)
145 }
146
147 fn zoomable(&self, cx: &App) -> bool {
148 self.read(cx).zoomable(cx)
149 }
150
151 fn visible(&self, cx: &App) -> bool {
152 self.read(cx).visible(cx)
153 }
154
155 fn set_active(&self, active: bool, window: &mut Window, cx: &mut App) {
156 self.update(cx, |this, cx| {
157 this.set_active(active, window, cx);
158 })
159 }
160
161 fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App) {
162 self.update(cx, |this, cx| {
163 this.set_zoomed(zoomed, window, cx);
164 })
165 }
166
167 fn on_added_to(&self, group: WeakEntity<TabGroup>, window: &mut Window, cx: &mut App) {
168 self.update(cx, |this, cx| this.on_added_to(group, window, cx));
169 }
170
171 fn on_removed(&self, window: &mut Window, cx: &mut App) {
172 self.update(cx, |this, cx| this.on_removed(window, cx));
173 }
174
175 fn view(&self) -> AnyView {
176 self.clone().into()
177 }
178
179 fn focus_handle(&self, cx: &App) -> FocusHandle {
180 self.read(cx).focus_handle(cx)
181 }
182
183 fn dump(&self, cx: &App) -> PanelState {
184 self.read(cx).dump(cx)
185 }
186
187 fn as_any(&self) -> &dyn Any {
188 self
189 }
190}
191
192impl From<&dyn PanelView> for AnyView {
193 fn from(handle: &dyn PanelView) -> Self {
194 handle.view()
195 }
196}
197
198impl<T: Panel> From<&dyn PanelView> for Entity<T> {
199 fn from(value: &dyn PanelView) -> Self {
200 value.view().downcast::<T>().unwrap()
201 }
202}
203
204impl PartialEq for dyn PanelView {
205 fn eq(&self, other: &Self) -> bool {
206 self.view() == other.view()
207 }
208}
209
210pub(crate) struct LivePanels<'a> {
215 panels: &'a HashMap<PanelId, Arc<dyn PanelView>>,
216 cx: &'a App,
217}
218
219impl<'a> LivePanels<'a> {
220 pub(crate) fn new(panels: &'a HashMap<PanelId, Arc<dyn PanelView>>, cx: &'a App) -> Self {
221 Self { panels, cx }
222 }
223}
224
225impl PanelSource for LivePanels<'_> {
226 fn panel_name(&self, id: PanelId) -> &'static str {
227 self.panels
228 .get(&id)
229 .map(|panel| panel.panel_name(self.cx))
230 .unwrap_or("")
231 }
232
233 fn is_visible(&self, id: PanelId) -> bool {
234 self.panels
235 .get(&id)
236 .is_some_and(|panel| panel.visible(self.cx))
237 }
238
239 fn dump(&self, id: PanelId) -> PanelState {
240 self.panels
241 .get(&id)
242 .map(|panel| panel.dump(self.cx))
243 .unwrap_or_default()
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::super::state::PanelInfo;
250 use super::*;
251 use gpui::{
252 AppContext as _, Context, Empty, EventEmitter, FocusHandle, Focusable, IntoElement, Render,
253 TestAppContext, Window,
254 };
255
256 struct Probe {
257 focus_handle: FocusHandle,
258 visible: bool,
259 }
260
261 impl Panel for Probe {
262 fn panel_name(&self) -> &'static str {
263 "Probe"
264 }
265
266 fn visible(&self, _: &App) -> bool {
267 self.visible
268 }
269 }
270
271 impl EventEmitter<PanelEvent> for Probe {}
272 impl Focusable for Probe {
273 fn focus_handle(&self, _: &App) -> FocusHandle {
274 self.focus_handle.clone()
275 }
276 }
277 impl Render for Probe {
278 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
279 Empty
280 }
281 }
282
283 #[gpui::test]
284 fn a_panel_entity_answers_through_the_object_safe_view(cx: &mut TestAppContext) {
285 let panel = cx.new(|cx| Probe {
286 focus_handle: cx.focus_handle(),
287 visible: false,
288 });
289 let view: Arc<dyn PanelView> = Arc::new(panel.clone());
290
291 cx.read(|cx| {
292 assert_eq!(view.panel_name(cx), "Probe");
293 assert_eq!(view.visible(cx), false);
294 assert_eq!(view.panel_id(cx), PanelId::from(panel.entity_id()));
295 });
296 }
297
298 #[gpui::test]
299 fn the_default_dump_records_only_the_panel_name(cx: &mut TestAppContext) {
300 let panel = cx.new(|cx| Probe {
301 focus_handle: cx.focus_handle(),
302 visible: true,
303 });
304 let state = cx.read(|cx| panel.read(cx).dump(cx));
305
306 assert_eq!(state.panel_name, "Probe");
307 assert!(state.children.is_empty());
308 assert_eq!(state.info, PanelInfo::panel(serde_json::Value::Null));
309 }
310}