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