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