use std::sync::Arc; use crate::{ resizable::{h_resizable, resizable_panel, v_resizable, ResizablePanel, ResizablePanelGroup}, theme::ActiveTheme, Placement, }; use super::{DockArea, Panel, PanelEvent, PanelView, TabPanel}; use gpui::{ div, prelude::FluentBuilder as _, px, Axis, DismissEvent, Entity, EventEmitter, FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, View, ViewContext, VisualContext, WeakView, }; use smallvec::SmallVec; pub struct StackPanel { pub(super) parent: Option>, pub(super) axis: Axis, focus_handle: FocusHandle, panels: SmallVec<[Arc; 2]>, panel_group: View, } impl Panel for StackPanel {} impl StackPanel { pub fn new(axis: Axis, cx: &mut ViewContext) -> Self { Self { axis, parent: None, focus_handle: cx.focus_handle(), panels: SmallVec::new(), panel_group: cx.new_view(|_| { if axis == Axis::Horizontal { h_resizable() } else { v_resizable() } }), } } /// The first level of the stack panel is root, will not have a parent. fn is_root(&self) -> bool { self.parent.is_none() } pub(super) fn panels_len(&self) -> usize { self.panels.len() } /// Return the index of the panel. pub fn index_of_panel

(&self, panel: View

) -> Option where P: Panel, { let entity_id = panel.entity_id(); self.panels .iter() .position(|p| p.view().entity_id() == entity_id) } /// Add a panel at the end of the stack. pub fn add_panel

( &mut self, panel: View

, size: Option, dock_area: WeakView, cx: &mut ViewContext, ) where P: Panel, { self.insert_panel(panel, self.panels.len(), size, dock_area, cx); } pub fn add_panel_at

( &mut self, panel: View

, ix: usize, placement: Placement, dock_area: WeakView, cx: &mut ViewContext, ) where P: Panel, { match placement { Placement::Top | Placement::Left => self.insert_panel_before(panel, ix, dock_area, cx), Placement::Right | Placement::Bottom => { self.insert_panel_after(panel, ix, dock_area, cx) } } } /// Insert a panel at the index. pub fn insert_panel_before

( &mut self, panel: View

, ix: usize, dock_area: WeakView, cx: &mut ViewContext, ) where P: Panel, { self.insert_panel(panel, ix, None, dock_area, cx); } /// Insert a panel after the index. pub fn insert_panel_after

( &mut self, panel: View

, ix: usize, dock_area: WeakView, cx: &mut ViewContext, ) where P: Panel, { self.insert_panel(panel, ix + 1, None, dock_area, cx); } fn new_resizable_panel

(panel: View

, size: Option) -> ResizablePanel where P: Panel, { resizable_panel() .content_view(panel.view()) .min_size(px(100.)) .when_some(size, |this, size| this.size(size)) } fn insert_panel

( &mut self, panel: View

, ix: usize, size: Option, dock_area: WeakView, cx: &mut ViewContext, ) where P: Panel, { // If the panel is already in the stack, return. if let Some(_) = self.index_of_panel(panel.clone()) { return; } let dock_area = dock_area.clone(); cx.subscribe(&panel, move |_, panel, event, cx| match event { PanelEvent::ZoomIn | PanelEvent::ZoomOut => { if let Some(dock) = dock_area.upgrade() { dock.update(cx, |dock, cx| { dock.toggle_zoom(panel.clone(), cx); }); } } }) .detach(); cx.spawn(|view, mut cx| { let panel = panel.clone(); async move { if let Some(view) = view.upgrade() { cx.update(|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.clone())); } else if let Ok(stack_panel) = panel.view().downcast::() { stack_panel.update(cx, |stack_panel, _| { stack_panel.parent = Some(view.clone()) }); } }) } else { Ok(()) } } }) .detach(); self.panels.insert(ix, Arc::new(panel.clone())); self.panel_group.update(cx, |view, cx| { view.insert_child(Self::new_resizable_panel(panel, size), ix, cx) }); cx.notify(); } /// Remove panel from the stack. pub fn remove_panel

(&mut self, panel: View

, cx: &mut ViewContext) where P: Panel, { if let Some(ix) = self.index_of_panel(panel) { self.panels.remove(ix); self.panel_group.update(cx, |view, cx| { view.remove_child(ix, cx); }); self.remove_self_if_empty(cx); } else { println!("Panel not found in stack panel."); } } /// Replace the old panel with the new panel at same index. pub(super) fn replace_panel

( &mut self, old_panel: View

, new_panel: View, cx: &mut ViewContext, ) where P: Panel, { if let Some(ix) = self.index_of_panel(old_panel) { self.panels[ix] = Arc::new(new_panel.clone()); self.panel_group.update(cx, |view, cx| { view.replace_child(Self::new_resizable_panel(new_panel.clone(), None), ix, cx); }); } } /// If children is empty, remove self from parent view. pub(crate) fn remove_self_if_empty(&mut self, cx: &mut ViewContext) { if self.is_root() { return; } if !self.panels.is_empty() { return; } let view = cx.view().clone(); if let Some(parent) = self.parent.as_ref() { parent.update(cx, |parent, cx| { parent.remove_panel(view, cx); }); } cx.notify(); } /// Remove all panels from the stack. pub(super) fn remove_all_panels(&mut self, cx: &mut ViewContext) { self.panels.clear(); self.panel_group .update(cx, |view, cx| view.remove_all_children(cx)); } /// Change the axis of the stack panel. pub(super) fn set_axis(&mut self, axis: Axis, cx: &mut ViewContext) { self.axis = axis; self.panel_group .update(cx, |view, cx| view.set_axis(axis, cx)); cx.notify(); } } impl FocusableView for StackPanel { fn focus_handle(&self, _cx: &gpui::AppContext) -> FocusHandle { self.focus_handle.clone() } } impl EventEmitter for StackPanel {} impl EventEmitter for StackPanel {} impl Render for StackPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { div() .size_full() .flex_grow() .flex_shrink() .overflow_hidden() .bg(cx.theme().tab_bar) .child(self.panel_group.clone()) } }