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:
parent
f611b599aa
commit
8ece86cd0c
6 changed files with 263 additions and 85 deletions
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue