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