use std::sync::Arc; use gpui::prelude::FluentBuilder; use gpui::{ App, AppContext, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window, }; use smallvec::SmallVec; use super::{DockArea, PanelEvent}; use crate::dock_area::panel::{Panel, PanelView}; use crate::dock_area::tab_panel::TabPanel; use crate::resizable::{ h_resizable, resizable_panel, v_resizable, ResizablePanel, ResizablePanelEvent, ResizablePanelGroup, }; use crate::{h_flex, AxisExt as _, Placement}; pub struct StackPanel { pub(super) parent: Option>, pub(super) axis: Axis, focus_handle: FocusHandle, pub(crate) panels: SmallVec<[Arc; 2]>, panel_group: Entity, #[allow(dead_code)] subscriptions: Vec, } impl Panel for StackPanel { fn panel_id(&self) -> SharedString { "StackPanel".into() } fn title(&self, _cx: &App) -> gpui::AnyElement { "StackPanel".into_any_element() } } impl StackPanel { pub fn new(axis: Axis, window: &mut Window, cx: &mut Context) -> Self { let panel_group = cx.new(|cx| { if axis == Axis::Horizontal { h_resizable(window, cx) } else { v_resizable(window, cx) } }); // Bubble up the resize event. let subscriptions = vec![cx.subscribe_in( &panel_group, window, |_, _, _: &ResizablePanelEvent, _, cx| cx.emit(PanelEvent::LayoutChanged), )]; Self { axis, parent: None, focus_handle: cx.focus_handle(), panels: SmallVec::new(), panel_group, subscriptions, } } /// The first level of the stack panel is root, will not have a parent. fn is_root(&self) -> bool { self.parent.is_none() } /// Return true if self or parent only have last panel. pub(super) fn is_last_panel(&self, cx: &App) -> bool { if self.panels.len() > 1 { return false; } if let Some(parent) = &self.parent { if let Some(parent) = parent.upgrade() { return parent.read(cx).is_last_panel(cx); } } true } pub(super) fn panels_len(&self) -> usize { self.panels.len() } /// Return the index of the panel. pub(crate) fn index_of_panel(&self, panel: Arc) -> Option { self.panels.iter().position(|p| p == &panel) } /// Add a panel at the end of the stack. pub fn add_panel( &mut self, panel: Arc, size: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { self.insert_panel(panel, self.panels.len(), size, dock_area, window, cx); } pub fn add_panel_at( &mut self, panel: Arc, placement: Placement, size: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { self.insert_panel_at( panel, self.panels_len(), placement, size, dock_area, window, cx, ); } #[allow(clippy::too_many_arguments)] pub fn insert_panel_at( &mut self, panel: Arc, ix: usize, placement: Placement, size: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { match placement { Placement::Top | Placement::Left => { self.insert_panel_before(panel, ix, size, dock_area, window, cx) } Placement::Right | Placement::Bottom => { self.insert_panel_after(panel, ix, size, dock_area, window, cx) } } } /// Insert a panel at the index. pub fn insert_panel_before( &mut self, panel: Arc, ix: usize, size: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { self.insert_panel(panel, ix, size, dock_area, window, cx); } /// Insert a panel after the index. pub fn insert_panel_after( &mut self, panel: Arc, ix: usize, size: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { self.insert_panel(panel, ix + 1, size, dock_area, window, cx); } fn new_resizable_panel(panel: Arc, size: Option) -> ResizablePanel { resizable_panel() .content_view(panel.view()) .content_visible(move |cx| panel.visible(cx)) .when_some(size, |this, size| this.size(size)) } fn insert_panel( &mut self, panel: Arc, ix: usize, size: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { // If the panel is already in the stack, return. if self.index_of_panel(panel.clone()).is_some() { return; } let view = cx.entity().clone(); window.defer(cx, { let panel = panel.clone(); move |window, cx| { // If the panel is a TabPanel, set its parent to this. if let Ok(tab_panel) = panel.view().downcast::() { tab_panel.update(cx, |tab_panel, _| tab_panel.set_parent(view.downgrade())); } else if let Ok(stack_panel) = panel.view().downcast::() { stack_panel.update(cx, |stack_panel, _| { stack_panel.parent = Some(view.downgrade()) }); } // Subscribe to the panel's layout change event. _ = dock_area.update(cx, |this, cx| { if let Ok(tab_panel) = panel.view().downcast::() { this.subscribe_panel(&tab_panel, window, cx); } else if let Ok(stack_panel) = panel.view().downcast::() { this.subscribe_panel(&stack_panel, window, cx); } }); } }); let ix = if ix > self.panels.len() { self.panels.len() } else { ix }; self.panels.insert(ix, panel.clone()); self.panel_group.update(cx, |view, cx| { view.insert_child( Self::new_resizable_panel(panel.clone(), size), ix, window, cx, ) }); cx.emit(PanelEvent::LayoutChanged); cx.notify(); } /// Remove panel from the stack. pub fn remove_panel( &mut self, panel: Arc, window: &mut Window, cx: &mut Context, ) { if let Some(ix) = self.index_of_panel(panel.clone()) { self.panels.remove(ix); self.panel_group.update(cx, |view, cx| { view.remove_child(ix, window, cx); }); cx.emit(PanelEvent::LayoutChanged); self.remove_self_if_empty(window, cx); } } /// Replace the old panel with the new panel at same index. pub(super) fn replace_panel( &mut self, old_panel: Arc, new_panel: Entity, window: &mut Window, cx: &mut Context, ) { if let Some(ix) = self.index_of_panel(old_panel.clone()) { self.panels[ix] = Arc::new(new_panel.clone()); self.panel_group.update(cx, |view, cx| { view.replace_child( Self::new_resizable_panel(Arc::new(new_panel.clone()), None), ix, window, cx, ); }); cx.emit(PanelEvent::LayoutChanged); } } /// If children is empty, remove self from parent view. pub(crate) fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context) { if self.is_root() { return; } if !self.panels.is_empty() { return; } let view = cx.entity().clone(); if let Some(parent) = self.parent.as_ref() { _ = parent.update(cx, |parent, cx| { parent.remove_panel(Arc::new(view.clone()), window, cx); }); } cx.emit(PanelEvent::LayoutChanged); cx.notify(); } /// Find the first top left in the stack. pub(super) fn left_top_tab_panel( &self, check_parent: bool, cx: &App, ) -> Option> { if check_parent { if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) { if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) { return Some(panel); } } } let first_panel = self.panels.first(); if let Some(view) = first_panel { if let Ok(tab_panel) = view.view().downcast::() { Some(tab_panel) } else if let Ok(stack_panel) = view.view().downcast::() { stack_panel.read(cx).left_top_tab_panel(false, cx) } else { None } } else { None } } /// Find the first top right in the stack. pub(super) fn right_top_tab_panel( &self, check_parent: bool, cx: &App, ) -> Option> { if check_parent { if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) { if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) { return Some(panel); } } } let panel = if self.axis.is_vertical() { self.panels.first() } else { self.panels.last() }; if let Some(view) = panel { if let Ok(tab_panel) = view.view().downcast::() { Some(tab_panel) } else if let Ok(stack_panel) = view.view().downcast::() { stack_panel.read(cx).right_top_tab_panel(false, cx) } else { None } } else { None } } /// Remove all panels from the stack. pub(super) fn remove_all_panels(&mut self, window: &mut Window, cx: &mut Context) { self.panels.clear(); self.panel_group .update(cx, |view, cx| view.remove_all_children(window, cx)); } /// Change the axis of the stack panel. pub(super) fn set_axis(&mut self, axis: Axis, window: &mut Window, cx: &mut Context) { self.axis = axis; self.panel_group .update(cx, |view, cx| view.set_axis(axis, window, cx)); cx.notify(); } } impl Focusable for StackPanel { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.focus_handle.clone() } } impl EventEmitter for StackPanel {} impl EventEmitter for StackPanel {} impl Render for StackPanel { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { h_flex() .size_full() .overflow_hidden() .child(self.panel_group.clone()) } }