1use std::sync::Arc;
2
3use crate::{
4 dock::PanelInfo,
5 h_flex,
6 resizable::{
7 resizable_panel, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState,
8 ResizableState, PANEL_MIN_SIZE,
9 },
10 ActiveTheme, AxisExt as _, Placement,
11};
12
13use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel};
14use gpui::{
15 App, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
16 ParentElement, Pixels, Render, Styled, Subscription, WeakEntity, Window,
17};
18use smallvec::SmallVec;
19
20pub struct StackPanel {
21 pub(super) parent: Option<WeakEntity<StackPanel>>,
22 pub(super) axis: Axis,
23 focus_handle: FocusHandle,
24 pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>,
25 state: Entity<ResizableState>,
26 _subscriptions: Vec<Subscription>,
27}
28
29impl Panel for StackPanel {
30 fn panel_name(&self) -> &'static str {
31 "StackPanel"
32 }
33
34 fn title(&self, _window: &gpui::Window, _cx: &gpui::App) -> gpui::AnyElement {
35 "StackPanel".into_any_element()
36 }
37 fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut App) {
38 for panel in &self.panels {
39 panel.set_active(active, window, cx);
40 }
41 }
42 fn dump(&self, cx: &App) -> PanelState {
43 let sizes = self.state.read(cx).sizes().clone();
44 let mut state = PanelState::new(self);
45 for panel in &self.panels {
46 state.add_child(panel.dump(cx));
47 state.info = PanelInfo::stack(sizes.clone(), self.axis);
48 }
49
50 state
51 }
52}
53
54impl StackPanel {
55 pub fn new(axis: Axis, _: &mut Window, cx: &mut Context<Self>) -> Self {
56 let state = ResizableState::new(cx);
57
58 let _subscriptions = vec![cx.subscribe(&state, |_, _, _: &ResizablePanelEvent, cx| {
60 cx.emit(PanelEvent::LayoutChanged)
61 })];
62
63 Self {
64 axis,
65 parent: None,
66 focus_handle: cx.focus_handle(),
67 panels: SmallVec::new(),
68 state,
69 _subscriptions,
70 }
71 }
72
73 fn is_root(&self) -> bool {
75 self.parent.is_none()
76 }
77
78 pub(super) fn is_last_panel(&self, cx: &App) -> bool {
80 if self.panels.len() > 1 {
81 return false;
82 }
83
84 if let Some(parent) = &self.parent {
85 if let Some(parent) = parent.upgrade() {
86 return parent.read(cx).is_last_panel(cx);
87 }
88 }
89
90 true
91 }
92
93 pub(super) fn panels_len(&self) -> usize {
94 self.panels.len()
95 }
96
97 pub(crate) fn index_of_panel(&self, panel: Arc<dyn PanelView>) -> Option<usize> {
99 self.panels.iter().position(|p| p == &panel)
100 }
101
102 fn assert_panel_is_valid(&self, panel: &Arc<dyn PanelView>) {
103 assert!(
104 panel.view().downcast::<TabPanel>().is_ok()
105 || panel.view().downcast::<StackPanel>().is_ok(),
106 "Panel must be a `TabPanel` or `StackPanel`"
107 );
108 }
109
110 pub fn add_panel(
116 &mut self,
117 panel: Arc<dyn PanelView>,
118 size: Option<Pixels>,
119 dock_area: WeakEntity<DockArea>,
120 window: &mut Window,
121 cx: &mut Context<Self>,
122 ) {
123 self.insert_panel(panel, self.panels.len(), size, dock_area, window, cx);
124 }
125
126 pub fn add_panel_at(
130 &mut self,
131 panel: Arc<dyn PanelView>,
132 placement: Placement,
133 size: Option<Pixels>,
134 dock_area: WeakEntity<DockArea>,
135 window: &mut Window,
136 cx: &mut Context<Self>,
137 ) {
138 self.insert_panel_at(
139 panel,
140 self.panels_len(),
141 placement,
142 size,
143 dock_area,
144 window,
145 cx,
146 );
147 }
148
149 #[allow(clippy::too_many_arguments)]
153 pub fn insert_panel_at(
154 &mut self,
155 panel: Arc<dyn PanelView>,
156 ix: usize,
157 placement: Placement,
158 size: Option<Pixels>,
159 dock_area: WeakEntity<DockArea>,
160 window: &mut Window,
161 cx: &mut Context<Self>,
162 ) {
163 match placement {
164 Placement::Top | Placement::Left => {
165 self.insert_panel_before(panel, ix, size, dock_area, window, cx)
166 }
167 Placement::Right | Placement::Bottom => {
168 self.insert_panel_after(panel, ix, size, dock_area, window, cx)
169 }
170 }
171 }
172
173 pub fn insert_panel_before(
177 &mut self,
178 panel: Arc<dyn PanelView>,
179 ix: usize,
180 size: Option<Pixels>,
181 dock_area: WeakEntity<DockArea>,
182 window: &mut Window,
183 cx: &mut Context<Self>,
184 ) {
185 self.insert_panel(panel, ix, size, dock_area, window, cx);
186 }
187
188 pub fn insert_panel_after(
192 &mut self,
193 panel: Arc<dyn PanelView>,
194 ix: usize,
195 size: Option<Pixels>,
196 dock_area: WeakEntity<DockArea>,
197 window: &mut Window,
198 cx: &mut Context<Self>,
199 ) {
200 self.insert_panel(panel, ix + 1, size, dock_area, window, cx);
201 }
202
203 fn insert_panel(
204 &mut self,
205 panel: Arc<dyn PanelView>,
206 ix: usize,
207 size: Option<Pixels>,
208 dock_area: WeakEntity<DockArea>,
209 window: &mut Window,
210 cx: &mut Context<Self>,
211 ) {
212 self.assert_panel_is_valid(&panel);
213
214 if let Some(_) = self.index_of_panel(panel.clone()) {
216 return;
217 }
218
219 let view = cx.entity().clone();
220 window.defer(cx, {
221 let panel = panel.clone();
222
223 move |window, cx| {
224 if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
226 tab_panel.update(cx, |tab_panel, _| tab_panel.set_parent(view.downgrade()));
227 } else if let Ok(stack_panel) = panel.view().downcast::<Self>() {
228 stack_panel.update(cx, |stack_panel, _| {
229 stack_panel.parent = Some(view.downgrade())
230 });
231 }
232
233 _ = dock_area.update(cx, |this, cx| {
235 if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
236 this.subscribe_panel(&tab_panel, window, cx);
237 } else if let Ok(stack_panel) = panel.view().downcast::<Self>() {
238 this.subscribe_panel(&stack_panel, window, cx);
239 }
240 });
241 }
242 });
243
244 let ix = if ix > self.panels.len() {
245 self.panels.len()
246 } else {
247 ix
248 };
249
250 let size = match size {
252 Some(size) => size,
253 None => {
254 let state = self.state.read(cx);
255 (state.total_size() / (state.sizes().len() + 1) as f32).max(PANEL_MIN_SIZE)
256 }
257 };
258
259 self.panels.insert(ix, panel.clone());
260 self.state.update(cx, |state, cx| {
261 state.insert_panel(Some(size), Some(ix), cx);
262 });
263 cx.emit(PanelEvent::LayoutChanged);
264 cx.notify();
265 }
266
267 pub fn remove_panel(
271 &mut self,
272 panel: Arc<dyn PanelView>,
273 window: &mut Window,
274 cx: &mut Context<Self>,
275 ) {
276 let Some(ix) = self.index_of_panel(panel.clone()) else {
277 return;
278 };
279
280 self.panels.remove(ix);
281 self.state.update(cx, |state, cx| {
282 state.remove_panel(ix, cx);
283 });
284
285 cx.emit(PanelEvent::LayoutChanged);
286 self.remove_self_if_empty(window, cx);
287 }
288
289 pub(super) fn replace_panel(
291 &mut self,
292 old_panel: Arc<dyn PanelView>,
293 new_panel: Entity<StackPanel>,
294 _: &mut Window,
295 cx: &mut Context<Self>,
296 ) {
297 if let Some(ix) = self.index_of_panel(old_panel.clone()) {
298 self.panels[ix] = Arc::new(new_panel.clone());
299
300 let panel_state = ResizablePanelState::default();
301 self.state.update(cx, |state, cx| {
302 state.replace_panel(ix, panel_state, cx);
303 });
304 cx.emit(PanelEvent::LayoutChanged);
305 }
306 }
307
308 pub(crate) fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context<Self>) {
310 if self.is_root() {
311 return;
312 }
313
314 if !self.panels.is_empty() {
315 return;
316 }
317
318 let view = cx.entity().clone();
319 if let Some(parent) = self.parent.as_ref() {
320 _ = parent.update(cx, |parent, cx| {
321 parent.remove_panel(Arc::new(view.clone()), window, cx);
322 });
323 }
324
325 cx.emit(PanelEvent::LayoutChanged);
326 cx.notify();
327 }
328
329 pub(super) fn left_top_tab_panel(
331 &self,
332 check_parent: bool,
333 cx: &App,
334 ) -> Option<Entity<TabPanel>> {
335 if check_parent {
336 if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
337 if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) {
338 return Some(panel);
339 }
340 }
341 }
342
343 let first_panel = self.panels.first();
344 if let Some(view) = first_panel {
345 if let Ok(tab_panel) = view.view().downcast::<TabPanel>() {
346 Some(tab_panel)
347 } else if let Ok(stack_panel) = view.view().downcast::<StackPanel>() {
348 stack_panel.read(cx).left_top_tab_panel(false, cx)
349 } else {
350 None
351 }
352 } else {
353 None
354 }
355 }
356
357 pub(super) fn right_top_tab_panel(
359 &self,
360 check_parent: bool,
361 cx: &App,
362 ) -> Option<Entity<TabPanel>> {
363 if check_parent {
364 if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
365 if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) {
366 return Some(panel);
367 }
368 }
369 }
370
371 let panel = if self.axis.is_vertical() {
372 self.panels.first()
373 } else {
374 self.panels.last()
375 };
376
377 if let Some(view) = panel {
378 if let Ok(tab_panel) = view.view().downcast::<TabPanel>() {
379 Some(tab_panel)
380 } else if let Ok(stack_panel) = view.view().downcast::<StackPanel>() {
381 stack_panel.read(cx).right_top_tab_panel(false, cx)
382 } else {
383 None
384 }
385 } else {
386 None
387 }
388 }
389
390 pub(super) fn remove_all_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {
392 self.panels.clear();
393 self.state.update(cx, |state, cx| {
394 state.clear();
395 cx.notify();
396 });
397 }
398
399 pub(super) fn set_axis(&mut self, axis: Axis, _: &mut Window, cx: &mut Context<Self>) {
401 self.axis = axis;
402 cx.notify();
403 }
404}
405
406impl Focusable for StackPanel {
407 fn focus_handle(&self, _cx: &App) -> FocusHandle {
408 self.focus_handle.clone()
409 }
410}
411impl EventEmitter<PanelEvent> for StackPanel {}
412impl EventEmitter<DismissEvent> for StackPanel {}
413impl Render for StackPanel {
414 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
415 h_flex()
416 .size_full()
417 .overflow_hidden()
418 .bg(cx.theme().tab_bar)
419 .child(
420 ResizablePanelGroup::new("stack-panel-group", self.state.clone())
421 .axis(self.axis)
422 .children(self.panels.clone().into_iter().map(|panel| {
423 resizable_panel()
424 .child(panel.view())
425 .visible(panel.visible(cx))
426 })),
427 )
428 }
429}