274 lines
7.8 KiB
Rust
274 lines
7.8 KiB
Rust
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<View<StackPanel>>,
|
|
pub(super) axis: Axis,
|
|
focus_handle: FocusHandle,
|
|
panels: SmallVec<[Arc<dyn PanelView>; 2]>,
|
|
panel_group: View<ResizablePanelGroup>,
|
|
}
|
|
|
|
impl Panel for StackPanel {}
|
|
|
|
impl StackPanel {
|
|
pub fn new(axis: Axis, cx: &mut ViewContext<Self>) -> 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<P>(&self, panel: View<P>) -> Option<usize>
|
|
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<P>(
|
|
&mut self,
|
|
panel: View<P>,
|
|
size: Option<Pixels>,
|
|
dock_area: WeakView<DockArea>,
|
|
cx: &mut ViewContext<Self>,
|
|
) where
|
|
P: Panel,
|
|
{
|
|
self.insert_panel(panel, self.panels.len(), size, dock_area, cx);
|
|
}
|
|
|
|
pub fn add_panel_at<P>(
|
|
&mut self,
|
|
panel: View<P>,
|
|
ix: usize,
|
|
placement: Placement,
|
|
dock_area: WeakView<DockArea>,
|
|
cx: &mut ViewContext<Self>,
|
|
) 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<P>(
|
|
&mut self,
|
|
panel: View<P>,
|
|
ix: usize,
|
|
dock_area: WeakView<DockArea>,
|
|
cx: &mut ViewContext<Self>,
|
|
) where
|
|
P: Panel,
|
|
{
|
|
self.insert_panel(panel, ix, None, dock_area, cx);
|
|
}
|
|
|
|
/// Insert a panel after the index.
|
|
pub fn insert_panel_after<P>(
|
|
&mut self,
|
|
panel: View<P>,
|
|
ix: usize,
|
|
dock_area: WeakView<DockArea>,
|
|
cx: &mut ViewContext<Self>,
|
|
) where
|
|
P: Panel,
|
|
{
|
|
self.insert_panel(panel, ix + 1, None, dock_area, cx);
|
|
}
|
|
|
|
fn new_resizable_panel<P>(panel: View<P>, size: Option<Pixels>) -> 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<P>(
|
|
&mut self,
|
|
panel: View<P>,
|
|
ix: usize,
|
|
size: Option<Pixels>,
|
|
dock_area: WeakView<DockArea>,
|
|
cx: &mut ViewContext<Self>,
|
|
) 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::<TabPanel>() {
|
|
tab_panel.update(cx, |tab_panel, _| tab_panel.set_parent(view.clone()));
|
|
} else if let Ok(stack_panel) = panel.view().downcast::<Self>() {
|
|
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<P>(&mut self, panel: View<P>, cx: &mut ViewContext<Self>)
|
|
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<P>(
|
|
&mut self,
|
|
old_panel: View<P>,
|
|
new_panel: View<StackPanel>,
|
|
cx: &mut ViewContext<Self>,
|
|
) 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<Self>) {
|
|
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>) {
|
|
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>) {
|
|
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<PanelEvent> for StackPanel {}
|
|
impl EventEmitter<DismissEvent> for StackPanel {}
|
|
impl Render for StackPanel {
|
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
div()
|
|
.size_full()
|
|
.flex_grow()
|
|
.flex_shrink()
|
|
.overflow_hidden()
|
|
.bg(cx.theme().tab_bar)
|
|
.child(self.panel_group.clone())
|
|
}
|
|
}
|