dock: Add id to DockArea and improve DockArea init. (#204)

- Export add_panel_at method for TabPanel to split_panel.
- Fix resizeable_panel init size support.
- Fix panel resize to let panel that have initial size will not auto resize on window resize.
- Fix resize_handle sometimes will invisibly bug.
- Update TabPanel new to support give parent StackPanel.
This commit is contained in:
Jason Lee 2024-09-02 21:27:19 +08:00 committed by GitHub
parent f611b599aa
commit 8ece86cd0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 263 additions and 85 deletions

View file

@ -18,7 +18,7 @@ use ui::{
modal::Modal, modal::Modal,
popup_menu::PopupMenuExt, popup_menu::PopupMenuExt,
theme::{ActiveTheme, Colorize as _, Theme}, theme::{ActiveTheme, Colorize as _, Theme},
ContextModal, IconName, Root, Sizable, ContextModal, IconName, Placement, Root, Sizable,
}; };
use crate::app_state::AppState; use crate::app_state::AppState;
@ -52,35 +52,49 @@ impl StoryWorkspace {
.detach(); .detach();
let stack_panel = cx.new_view(|cx| StackPanel::new(Axis::Horizontal, cx)); let stack_panel = cx.new_view(|cx| StackPanel::new(Axis::Horizontal, cx));
let dock_area = cx.new_view(|cx| DockArea::new(stack_panel.clone(), cx)); let dock_area = cx.new_view(|cx| DockArea::new("main-dock", stack_panel.clone(), cx));
let weak_dock_area = dock_area.downgrade(); let weak_dock_area = dock_area.downgrade();
let tab_panel = cx.new_view(|cx| TabPanel::new(weak_dock_area.clone(), cx)); let center_tab_panel =
let right_tab_panel = cx.new_view(|cx| TabPanel::new(weak_dock_area.clone(), cx)); cx.new_view(|cx| TabPanel::new(Some(stack_panel.clone()), weak_dock_area.clone(), cx));
let right_tab_panel1 = cx.new_view(|cx| TabPanel::new(weak_dock_area.clone(), cx)); let left_tab_panel =
cx.new_view(|cx| TabPanel::new(Some(stack_panel.clone()), weak_dock_area.clone(), cx));
let right_tab_panel =
cx.new_view(|cx| TabPanel::new(Some(stack_panel.clone()), weak_dock_area.clone(), cx));
stack_panel.update(cx, |view, cx| { stack_panel.update(cx, |view, cx| {
view.add_panel(tab_panel.clone(), None, weak_dock_area.clone(), cx); let left_stack_panel = cx.new_view(|cx| StackPanel::new(Axis::Vertical, cx));
left_stack_panel.update(cx, |view, cx| {
let stock_panel1 = cx.new_view(|cx| StackPanel::new(Axis::Vertical, cx)); view.add_panel(left_tab_panel.clone(), None, weak_dock_area.clone(), cx);
});
view.add_panel( view.add_panel(
stock_panel1.clone(), left_stack_panel.clone(),
Some(px(380.)), Some(px(300.)),
weak_dock_area.clone(), weak_dock_area.clone(),
cx, cx,
); );
stock_panel1.update(cx, |view, cx| { view.add_panel(center_tab_panel.clone(), None, weak_dock_area.clone(), cx);
let right_stack_panel = cx.new_view(|cx| StackPanel::new(Axis::Vertical, cx));
right_stack_panel.update(cx, |view, cx| {
view.add_panel(right_tab_panel.clone(), None, weak_dock_area.clone(), cx); view.add_panel(right_tab_panel.clone(), None, weak_dock_area.clone(), cx);
view.add_panel(right_tab_panel1.clone(), None, weak_dock_area.clone(), cx); });
}) view.add_panel(
right_stack_panel.clone(),
Some(px(340.)),
weak_dock_area.clone(),
cx,
);
}); });
StoryContainer::add_panel( StoryContainer::add_panel(
"Buttons", "Buttons",
"Displays a button or a component that looks like a button.", "Displays a button or a component that looks like a button.",
ButtonStory::view(cx).into(), ButtonStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
None,
None,
false, false,
cx, cx,
) )
@ -90,7 +104,9 @@ impl StoryWorkspace {
"Input", "Input",
"A control that allows the user to input text.", "A control that allows the user to input text.",
InputStory::view(cx).into(), InputStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
None,
None,
false, false,
cx, cx,
) )
@ -100,7 +116,9 @@ impl StoryWorkspace {
"Text", "Text",
"Links, paragraphs, checkboxes, and more.", "Links, paragraphs, checkboxes, and more.",
TextStory::view(cx).into(), TextStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
Some(Placement::Bottom),
Some(px(200.)),
true, true,
cx, cx,
) )
@ -110,7 +128,9 @@ impl StoryWorkspace {
"Switch", "Switch",
"A control that allows the user to toggle between two states.", "A control that allows the user to toggle between two states.",
SwitchStory::view(cx).into(), SwitchStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )
@ -120,7 +140,9 @@ impl StoryWorkspace {
"Dropdowns", "Dropdowns",
"Displays a list of options for the user to pick from—triggered by a button.", "Displays a list of options for the user to pick from—triggered by a button.",
DropdownStory::new(cx).into(), DropdownStory::new(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )
@ -130,7 +152,9 @@ impl StoryWorkspace {
"Modal", "Modal",
"Modal & Drawer use examples", "Modal & Drawer use examples",
ModalStory::view(cx).into(), ModalStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )
@ -140,7 +164,9 @@ impl StoryWorkspace {
"Popup", "Popup",
"A popup displays content on top of the main page.", "A popup displays content on top of the main page.",
PopupStory::view(cx).into(), PopupStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )
@ -150,7 +176,9 @@ impl StoryWorkspace {
"Tooltip", "Tooltip",
"Displays a short message when users hover over an element.", "Displays a short message when users hover over an element.",
TooltipStory::view(cx).into(), TooltipStory::view(cx).into(),
tab_panel.clone(), right_tab_panel.clone(),
Some(Placement::Top),
None,
true, true,
cx, cx,
) )
@ -160,7 +188,9 @@ impl StoryWorkspace {
"List", "List",
"A list displays a series of items.", "A list displays a series of items.",
ListStory::view(cx).into(), ListStory::view(cx).into(),
tab_panel.clone(), left_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )
@ -170,7 +200,9 @@ impl StoryWorkspace {
"Icon", "Icon",
"Icon use examples", "Icon use examples",
IconStory::view(cx).into(), IconStory::view(cx).into(),
tab_panel.clone(), left_tab_panel.clone(),
Some(Placement::Bottom),
Some(px(200.)),
true, true,
cx, cx,
) )
@ -180,7 +212,9 @@ impl StoryWorkspace {
"Image", "Image",
"Render SVG image and Chart", "Render SVG image and Chart",
ImageStory::view(cx).into(), ImageStory::view(cx).into(),
right_tab_panel1.clone(), right_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )
@ -198,7 +232,9 @@ impl StoryWorkspace {
"Table", "Table",
"Powerful table and datagrids built.", "Powerful table and datagrids built.",
TableStory::view(cx).into(), TableStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )
@ -208,8 +244,10 @@ impl StoryWorkspace {
"Progress", "Progress",
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.", "Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
ProgressStory::view(cx).into(), ProgressStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
true, Some(Placement::Bottom),
Some(px(200.)),
true,
cx, cx,
) )
.detach(); .detach();
@ -218,7 +256,9 @@ impl StoryWorkspace {
"Resizable", "Resizable",
"Accessible resizable panel groups and layouts with keyboard support.", "Accessible resizable panel groups and layouts with keyboard support.",
ResizableStory::view(cx).into(), ResizableStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )
@ -228,7 +268,9 @@ impl StoryWorkspace {
"Scrollable", "Scrollable",
"A scrollable area with scroll bar.", "A scrollable area with scroll bar.",
ScrollableStory::view(cx).into(), ScrollableStory::view(cx).into(),
tab_panel.clone(), center_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )
@ -238,7 +280,9 @@ impl StoryWorkspace {
"Calendar", "Calendar",
"A calendar component.", "A calendar component.",
CalendarStory::view(cx).into(), CalendarStory::view(cx).into(),
right_tab_panel.clone(), center_tab_panel.clone(),
None,
None,
true, true,
cx, cx,
) )

View file

@ -38,7 +38,7 @@ pub use webview_story::WebViewStory;
use gpui::{ use gpui::{
actions, div, prelude::FluentBuilder as _, px, AnyView, AppContext, Div, EventEmitter, actions, div, prelude::FluentBuilder as _, px, AnyView, AppContext, Div, EventEmitter,
FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString, FocusableView, InteractiveElement, IntoElement, ParentElement, Pixels, Render, SharedString,
StatefulInteractiveElement, Styled as _, Task, View, ViewContext, VisualContext, WindowContext, StatefulInteractiveElement, Styled as _, Task, View, ViewContext, VisualContext, WindowContext,
}; };
@ -49,7 +49,7 @@ use ui::{
h_flex, h_flex,
label::Label, label::Label,
popup_menu::PopupMenu, popup_menu::PopupMenu,
v_flex, v_flex, Placement,
}; };
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
@ -120,11 +120,14 @@ impl StoryContainer {
} }
} }
#[allow(clippy::too_many_arguments)]
pub fn add_panel( pub fn add_panel(
name: impl Into<SharedString>, name: impl Into<SharedString>,
description: impl Into<SharedString>, description: impl Into<SharedString>,
story: AnyView, story: AnyView,
tab_panel: View<TabPanel>, tab_panel: View<TabPanel>,
placement: Option<Placement>,
size: Option<Pixels>,
closeable: bool, closeable: bool,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Task<Result<View<Self>>> { ) -> Task<Result<View<Self>>> {
@ -135,7 +138,11 @@ impl StoryContainer {
tab_panel.update(&mut cx, |panel, cx| { tab_panel.update(&mut cx, |panel, cx| {
let view = let view =
cx.new_view(|cx| Self::new(name, description, closeable, cx).story(story)); cx.new_view(|cx| Self::new(name, description, closeable, cx).story(story));
panel.add_panel(Arc::new(view.clone()), cx); if let Some(placement) = placement {
panel.add_panel_at(Arc::new(view.clone()), placement, size, cx);
} else {
panel.add_panel(Arc::new(view.clone()), cx);
}
view view
}) })
}) })

View file

@ -4,7 +4,7 @@ mod tab_panel;
use gpui::{ use gpui::{
actions, div, prelude::FluentBuilder, AnyWeakView, InteractiveElement as _, IntoElement, actions, div, prelude::FluentBuilder, AnyWeakView, InteractiveElement as _, IntoElement,
ParentElement as _, Render, Styled, View, ViewContext, ParentElement as _, Render, SharedString, Styled, View, ViewContext,
}; };
pub use panel::*; pub use panel::*;
pub use stack_panel::*; pub use stack_panel::*;
@ -14,18 +14,29 @@ actions!(dock, [ToggleZoom, ClosePanel]);
/// The main area of the dock. /// The main area of the dock.
pub struct DockArea { pub struct DockArea {
id: SharedString,
root: View<StackPanel>, root: View<StackPanel>,
zoom_view: Option<AnyWeakView>, zoom_view: Option<AnyWeakView>,
} }
impl DockArea { impl DockArea {
pub fn new(root: View<StackPanel>, _cx: &mut ViewContext<Self>) -> Self { pub fn new(
id: impl Into<SharedString>,
root: View<StackPanel>,
_cx: &mut ViewContext<Self>,
) -> Self {
Self { Self {
id: id.into(),
root, root,
zoom_view: None, zoom_view: None,
} }
} }
/// Returns the ID of the dock area.
pub fn id(&self) -> SharedString {
self.id.clone()
}
/// Toggles the zoom view. /// Toggles the zoom view.
pub fn toggle_zoom<P: Panel>(&mut self, panel: View<P>, cx: &mut ViewContext<Self>) { pub fn toggle_zoom<P: Panel>(&mut self, panel: View<P>, cx: &mut ViewContext<Self>) {
if self.zoom_view.is_some() { if self.zoom_view.is_some() {
@ -35,6 +46,25 @@ impl DockArea {
} }
cx.notify(); cx.notify();
} }
/// Returns the root stack panel.
pub fn root(&self) -> View<StackPanel> {
self.root.clone()
}
/// Return the index of the panel.
pub fn index_of_panel<P: Panel>(
&self,
panel: View<P>,
cx: &mut ViewContext<Self>,
) -> Option<usize> {
self.root.read(cx).index_of_panel(panel)
}
/// Return the existing panel by type.
pub fn panel<P: Panel>(&self, cx: &mut ViewContext<Self>) -> Option<View<P>> {
self.root.read(cx).panel::<P>(cx)
}
} }
impl Render for DockArea { impl Render for DockArea {

View file

@ -11,7 +11,7 @@ use super::{DockArea, Panel, PanelEvent, PanelView, TabPanel};
use gpui::{ use gpui::{
prelude::FluentBuilder as _, Axis, DismissEvent, Entity, EventEmitter, FocusHandle, prelude::FluentBuilder as _, Axis, DismissEvent, Entity, EventEmitter, FocusHandle,
FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, View, ViewContext, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, View, ViewContext,
VisualContext, WeakView, VisualContext, WeakView, WindowContext,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
@ -62,6 +62,24 @@ impl StackPanel {
.position(|p| p.view().entity_id() == entity_id) .position(|p| p.view().entity_id() == entity_id)
} }
/// Return the existing panel view by type.
pub fn panel<P>(&self, cx: &WindowContext) -> Option<View<P>>
where
P: Panel,
{
self.panels.iter().find_map(|p| {
if let Ok(p) = p.view().downcast::<P>() {
Some(p)
} else {
if let Ok(tab) = p.view().downcast::<TabPanel>() {
tab.read(cx).panel(cx)
} else {
None
}
}
})
}
/// Add a panel at the end of the stack. /// Add a panel at the end of the stack.
pub fn add_panel<P>( pub fn add_panel<P>(
&mut self, &mut self,
@ -76,19 +94,35 @@ impl StackPanel {
} }
pub fn add_panel_at<P>( pub fn add_panel_at<P>(
&mut self,
panel: View<P>,
placement: Placement,
size: Option<Pixels>,
dock_area: WeakView<DockArea>,
cx: &mut ViewContext<Self>,
) where
P: Panel,
{
self.insert_panel_at(panel, self.panels_len(), placement, size, dock_area, cx);
}
pub fn insert_panel_at<P>(
&mut self, &mut self,
panel: View<P>, panel: View<P>,
ix: usize, ix: usize,
placement: Placement, placement: Placement,
size: Option<Pixels>,
dock_area: WeakView<DockArea>, dock_area: WeakView<DockArea>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) where ) where
P: Panel, P: Panel,
{ {
match placement { match placement {
Placement::Top | Placement::Left => self.insert_panel_before(panel, ix, dock_area, cx), Placement::Top | Placement::Left => {
self.insert_panel_before(panel, ix, size, dock_area, cx)
}
Placement::Right | Placement::Bottom => { Placement::Right | Placement::Bottom => {
self.insert_panel_after(panel, ix, dock_area, cx) self.insert_panel_after(panel, ix, size, dock_area, cx)
} }
} }
} }
@ -98,12 +132,13 @@ impl StackPanel {
&mut self, &mut self,
panel: View<P>, panel: View<P>,
ix: usize, ix: usize,
size: Option<Pixels>,
dock_area: WeakView<DockArea>, dock_area: WeakView<DockArea>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) where ) where
P: Panel, P: Panel,
{ {
self.insert_panel(panel, ix, None, dock_area, cx); self.insert_panel(panel, ix, size, dock_area, cx);
} }
/// Insert a panel after the index. /// Insert a panel after the index.
@ -111,12 +146,13 @@ impl StackPanel {
&mut self, &mut self,
panel: View<P>, panel: View<P>,
ix: usize, ix: usize,
size: Option<Pixels>,
dock_area: WeakView<DockArea>, dock_area: WeakView<DockArea>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) where ) where
P: Panel, P: Panel,
{ {
self.insert_panel(panel, ix + 1, None, dock_area, cx); self.insert_panel(panel, ix + 1, size, dock_area, cx);
} }
fn new_resizable_panel<P>(panel: View<P>, size: Option<Pixels>) -> ResizablePanel fn new_resizable_panel<P>(panel: View<P>, size: Option<Pixels>) -> ResizablePanel
@ -176,6 +212,12 @@ impl StackPanel {
}) })
.detach(); .detach();
let ix = if ix > self.panels.len() {
self.panels.len()
} else {
ix
};
self.panels.insert(ix, Arc::new(panel.clone())); self.panels.insert(ix, Arc::new(panel.clone()));
self.panel_group.update(cx, |view, cx| { self.panel_group.update(cx, |view, cx| {
view.insert_child(Self::new_resizable_panel(panel, size), ix, cx) view.insert_child(Self::new_resizable_panel(panel, size), ix, cx)

View file

@ -3,8 +3,8 @@ use std::sync::Arc;
use gpui::{ use gpui::{
div, prelude::FluentBuilder, rems, AnchorCorner, AppContext, DefiniteLength, DismissEvent, div, prelude::FluentBuilder, rems, AnchorCorner, AppContext, DefiniteLength, DismissEvent,
DragMoveEvent, Empty, EventEmitter, FocusHandle, FocusableView, InteractiveElement as _, DragMoveEvent, Empty, EventEmitter, FocusHandle, FocusableView, InteractiveElement as _,
IntoElement, ParentElement, Render, ScrollHandle, StatefulInteractiveElement, Styled, View, IntoElement, ParentElement, Pixels, Render, ScrollHandle, StatefulInteractiveElement, Styled,
ViewContext, VisualContext as _, WeakView, WindowContext, View, ViewContext, VisualContext as _, WeakView, WindowContext,
}; };
use rust_i18n::t; use rust_i18n::t;
@ -71,11 +71,15 @@ pub struct TabPanel {
} }
impl TabPanel { impl TabPanel {
pub fn new(dock_area: WeakView<DockArea>, cx: &mut ViewContext<Self>) -> Self { pub fn new(
stack_panel: Option<View<StackPanel>>,
dock_area: WeakView<DockArea>,
cx: &mut ViewContext<Self>,
) -> Self {
Self { Self {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
dock_area, dock_area,
stack_panel: None, stack_panel,
panels: Vec::new(), panels: Vec::new(),
active_ix: 0, active_ix: 0,
tab_bar_scroll_handle: ScrollHandle::new(), tab_bar_scroll_handle: ScrollHandle::new(),
@ -99,6 +103,21 @@ impl TabPanel {
cx.notify(); cx.notify();
} }
/// Return the existing panel view by type.
pub fn panel<P: Panel>(&self, cx: &WindowContext) -> Option<View<P>> {
self.panels.iter().find_map(|p| {
if let Ok(p) = p.view().downcast::<P>() {
Some(p)
} else {
if let Ok(stack) = p.view().downcast::<StackPanel>() {
stack.read(cx).panel::<P>(cx)
} else {
None
}
}
})
}
/// Add a panel to the end of the tabs /// Add a panel to the end of the tabs
pub fn add_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) { pub fn add_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) {
if self if self
@ -115,6 +134,19 @@ impl TabPanel {
cx.notify(); cx.notify();
} }
/// Add panel to try to split
pub fn add_panel_at(
&mut self,
panel: Arc<dyn PanelView>,
placement: Placement,
size: Option<Pixels>,
cx: &mut ViewContext<Self>,
) {
self.will_split_placement = Some(placement);
self.split_panel(panel, placement, size, cx);
cx.notify();
}
fn insert_panel_at( fn insert_panel_at(
&mut self, &mut self,
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
@ -401,7 +433,7 @@ impl TabPanel {
// Insert into new tabs // Insert into new tabs
if let Some(placement) = self.will_split_placement { if let Some(placement) = self.will_split_placement {
self.split_panel(panel, placement, cx); self.split_panel(panel, placement, None, cx);
} else { } else {
if let Some(ix) = ix { if let Some(ix) = ix {
self.insert_panel_at(panel, ix, cx) self.insert_panel_at(panel, ix, cx)
@ -418,11 +450,12 @@ impl TabPanel {
&self, &self,
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
placement: Placement, placement: Placement,
size: Option<Pixels>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let dock_area = self.dock_area.clone(); let dock_area = self.dock_area.clone();
// wrap the panel in a TabPanel // wrap the panel in a TabPanel
let new_tab_panel = cx.new_view(|cx| Self::new(dock_area.clone(), cx)); let new_tab_panel = cx.new_view(|cx| Self::new(None, dock_area.clone(), cx));
new_tab_panel.update(cx, |view, cx| { new_tab_panel.update(cx, |view, cx| {
view.add_panel(panel, cx); view.add_panel(panel, cx);
}); });
@ -436,11 +469,11 @@ impl TabPanel {
if parent_axis.is_vertical() && placement.is_vertical() { if parent_axis.is_vertical() && placement.is_vertical() {
stack_panel.update(cx, |view, cx| { stack_panel.update(cx, |view, cx| {
view.add_panel_at(new_tab_panel, ix, placement, dock_area.clone(), cx); view.insert_panel_at(new_tab_panel, ix, placement, size, dock_area.clone(), cx);
}); });
} else if parent_axis.is_horizontal() && placement.is_horizontal() { } else if parent_axis.is_horizontal() && placement.is_horizontal() {
stack_panel.update(cx, |view, cx| { stack_panel.update(cx, |view, cx| {
view.add_panel_at(new_tab_panel, ix, placement, dock_area.clone(), cx); view.insert_panel_at(new_tab_panel, ix, placement, size, dock_area.clone(), cx);
}); });
} else { } else {
// 1. Create new StackPanel with new axis // 1. Create new StackPanel with new axis
@ -466,12 +499,12 @@ impl TabPanel {
new_stack_panel.update(cx, |view, cx| match placement { new_stack_panel.update(cx, |view, cx| match placement {
Placement::Left | Placement::Top => { Placement::Left | Placement::Top => {
view.add_panel(new_tab_panel, None, dock_area.clone(), cx); view.add_panel(new_tab_panel, size, dock_area.clone(), cx);
view.add_panel(tab_panel.clone(), None, dock_area.clone(), cx); view.add_panel(tab_panel.clone(), None, dock_area.clone(), cx);
} }
Placement::Right | Placement::Bottom => { Placement::Right | Placement::Bottom => {
view.add_panel(tab_panel.clone(), None, dock_area.clone(), cx); view.add_panel(tab_panel.clone(), None, dock_area.clone(), cx);
view.add_panel(new_tab_panel, None, dock_area.clone(), cx); view.add_panel(new_tab_panel, size, dock_area.clone(), cx);
} }
}); });

View file

@ -2,8 +2,8 @@ use std::rc::Rc;
use gpui::{ use gpui::{
canvas, div, prelude::FluentBuilder, px, Along, AnyElement, AnyView, Axis, Bounds, Element, canvas, div, prelude::FluentBuilder, px, Along, AnyElement, AnyView, Axis, Bounds, Element,
EntityId, InteractiveElement as _, IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement, Entity, EntityId, InteractiveElement as _, IntoElement, MouseMoveEvent, MouseUpEvent,
Pixels, Render, StatefulInteractiveElement, Style, Styled, View, ViewContext, ParentElement, Pixels, Render, StatefulInteractiveElement, Style, Styled, View, ViewContext,
VisualContext as _, WindowContext, VisualContext as _, WindowContext,
}; };
@ -89,26 +89,19 @@ impl ResizablePanelGroup {
self self
} }
/// When add a child panel to the group, resize other panels each into half of the remaining space.
fn default_panel_size(&self) -> Pixels {
let container_size = self.bounds.size.along(self.axis);
let each_size = container_size / (self.panels.len() + 1) as f32;
each_size.round()
}
pub fn add_child(&mut self, panel: ResizablePanel, cx: &mut ViewContext<Self>) { pub fn add_child(&mut self, panel: ResizablePanel, cx: &mut ViewContext<Self>) {
let mut panel = panel; let mut panel = panel;
panel.axis = self.axis; panel.axis = self.axis;
panel.size = self.default_panel_size(); panel.group = Some(cx.view().clone());
self.sizes.push(panel.size); self.sizes.push(panel.size.unwrap_or_default());
self.panels.push(cx.new_view(|_| panel)); self.panels.push(cx.new_view(|_| panel));
} }
pub fn insert_child(&mut self, panel: ResizablePanel, ix: usize, cx: &mut ViewContext<Self>) { pub fn insert_child(&mut self, panel: ResizablePanel, ix: usize, cx: &mut ViewContext<Self>) {
let mut panel = panel; let mut panel = panel;
panel.axis = self.axis; panel.axis = self.axis;
panel.size = self.default_panel_size(); panel.group = Some(cx.view().clone());
self.sizes.insert(ix, panel.size); self.sizes.insert(ix, panel.size.unwrap_or_default());
self.panels.insert(ix, cx.new_view(|_| panel)); self.panels.insert(ix, cx.new_view(|_| panel));
cx.notify() cx.notify()
} }
@ -122,8 +115,8 @@ impl ResizablePanelGroup {
) { ) {
let mut panel = panel; let mut panel = panel;
panel.axis = self.axis; panel.axis = self.axis;
panel.size = self.default_panel_size(); panel.group = Some(cx.view().clone());
self.sizes[ix] = panel.size; self.sizes[ix] = panel.size.unwrap_or_default();
self.panels[ix] = cx.new_view(|_| panel); self.panels[ix] = cx.new_view(|_| panel);
cx.notify() cx.notify()
} }
@ -142,7 +135,7 @@ impl ResizablePanelGroup {
fn render_resize_handle(&self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_resize_handle(&self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
let axis = self.axis; let axis = self.axis;
let neg_offset = -HANDLE_PADDING + px(1.); let neg_offset = -HANDLE_PADDING;
let view = cx.view().clone(); let view = cx.view().clone();
div() div()
@ -153,14 +146,14 @@ impl ResizablePanelGroup {
.when(self.axis.is_horizontal(), |this| { .when(self.axis.is_horizontal(), |this| {
this.cursor_col_resize() this.cursor_col_resize()
.top_0() .top_0()
.right(neg_offset) .left(neg_offset)
.h_full() .h_full()
.w(px(1.)) .w(px(1.))
.px(HANDLE_PADDING) .px(HANDLE_PADDING)
}) })
.when(self.axis.is_vertical(), |this| { .when(self.axis.is_vertical(), |this| {
this.cursor_row_resize() this.cursor_row_resize()
.bottom(neg_offset) .top(neg_offset)
.left_0() .left_0()
.w_full() .w_full()
.h(px(1.)) .h(px(1.))
@ -250,7 +243,9 @@ impl ResizablePanelGroup {
self.sizes = new_sizes; self.sizes = new_sizes;
for (i, panel) in self.panels.iter().enumerate() { for (i, panel) in self.panels.iter().enumerate() {
let size = self.sizes[i]; let size = self.sizes[i];
panel.update(cx, |this, _| this.size = size); if size > px(0.) {
panel.update(cx, |this, _| this.changed_size = Some(size));
}
} }
} }
} }
@ -267,8 +262,8 @@ impl Render for ResizablePanelGroup {
container container
.size_full() .size_full()
.children(self.panels.iter().enumerate().map(|(ix, panel)| { .children(self.panels.iter().enumerate().map(|(ix, panel)| {
if ix < self.panels.len() - 1 { if ix > 0 {
let handle = self.render_resize_handle(ix, cx); let handle = self.render_resize_handle(ix - 1, cx);
panel.update(cx, |view, _| { panel.update(cx, |view, _| {
view.resize_handle = Some(handle.into_any_element()) view.resize_handle = Some(handle.into_any_element())
}); });
@ -292,7 +287,9 @@ impl Render for ResizablePanelGroup {
} }
pub struct ResizablePanel { pub struct ResizablePanel {
size: Pixels, group: Option<View<ResizablePanelGroup>>,
size: Option<Pixels>,
changed_size: Option<Pixels>,
axis: Axis, axis: Axis,
content_builder: Option<Rc<dyn Fn(&mut WindowContext) -> AnyElement>>, content_builder: Option<Rc<dyn Fn(&mut WindowContext) -> AnyElement>>,
content_view: Option<AnyView>, content_view: Option<AnyView>,
@ -304,7 +301,9 @@ pub struct ResizablePanel {
impl ResizablePanel { impl ResizablePanel {
pub(super) fn new() -> Self { pub(super) fn new() -> Self {
Self { Self {
size: PANEL_MIN_SIZE, group: None,
size: None,
changed_size: None,
axis: Axis::Horizontal, axis: Axis::Horizontal,
content_builder: None, content_builder: None,
content_view: None, content_view: None,
@ -327,9 +326,29 @@ impl ResizablePanel {
} }
pub fn size(mut self, size: Pixels) -> Self { pub fn size(mut self, size: Pixels) -> Self {
self.size = size; self.size = Some(size);
self self
} }
/// Save the real panel size, and update group sizes
fn update_size(&mut self, bounds: Bounds<Pixels>, cx: &mut ViewContext<Self>) {
let new_size = bounds.size.along(self.axis);
self.bounds = bounds;
let panel_view = cx.view().clone();
if let Some(group) = self.group.as_ref() {
group.update(cx, |view, _| {
if let Some(ix) = view
.panels
.iter()
.position(|v| v.entity_id() == panel_view.entity_id())
{
view.sizes[ix] = new_size;
}
})
}
cx.notify();
}
} }
impl FluentBuilder for ResizablePanel {} impl FluentBuilder for ResizablePanel {}
@ -337,24 +356,27 @@ impl FluentBuilder for ResizablePanel {}
impl Render for ResizablePanel { impl Render for ResizablePanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let view = cx.view().clone(); let view = cx.view().clone();
let axis = self.axis;
let size = self.size.max(PANEL_MIN_SIZE);
div() div()
.flex() .flex()
.flex_grow() .flex_grow()
.size_full()
.relative() .relative()
.overflow_hidden() .when(self.size.is_none(), |this| this.flex_shrink())
.when(self.axis.is_vertical(), |this| this.w_full().h(size)) .when(self.axis.is_vertical(), |this| this.min_h(PANEL_MIN_SIZE))
.when(self.axis.is_horizontal(), |this| this.h_full().w(size)) .when(self.axis.is_horizontal(), |this| this.min_w(PANEL_MIN_SIZE))
.when_some(self.size, |this, size| {
this.flex_shrink_0()
.when(self.axis.is_vertical(), |this| this.h(size))
.when(self.axis.is_horizontal(), |this| this.w(size))
})
.when_some(self.changed_size, |this, size| {
this.when(self.axis.is_vertical(), |this| this.h(size))
.when(self.axis.is_horizontal(), |this| this.w(size))
})
.child({ .child({
canvas( canvas(
move |bounds, cx| { move |bounds, cx| view.update(cx, |r, cx| r.update_size(bounds, cx)),
view.update(cx, |r, _| {
r.size = bounds.size.along(axis);
r.bounds = bounds;
})
},
|_, _, _| {}, |_, _, _| {},
) )
.absolute() .absolute()