dock: Add visible method to Panel trait to control panel visible. (#503)

Closes #502 #499 

## Added

- Add `visible` method to `Panel` trait, if return false this panel will
disappear.

## Break changes

- Renamed `closeable` to `closable`.
- The `title_style`, `closable`, `zoomable` in `Panel` trait have been
change `cx: &WindowContext` to `cx: &AppContext`.
This commit is contained in:
Jason Lee 2024-12-20 10:21:21 +08:00 committed by GitHub
parent 4b211bfe21
commit 1a4ee348f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 256 additions and 87 deletions

View file

@ -50,7 +50,7 @@ impl super::Story for ButtonStory {
"Displays a button or a component that looks like a button." "Displays a button or a component that looks like a button."
} }
fn closeable() -> bool { fn closable() -> bool {
false false
} }

View file

@ -55,7 +55,7 @@ impl super::Story for InputStory {
"Input" "Input"
} }
fn closeable() -> bool { fn closable() -> bool {
false false
} }

View file

@ -43,9 +43,10 @@ pub use tooltip_story::TooltipStory;
pub use webview_story::WebViewStory; pub use webview_story::WebViewStory;
use gpui::{ use gpui::{
actions, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, actions, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Context as _,
EventEmitter, FocusableView, Hsla, InteractiveElement, IntoElement, ParentElement, Render, Div, EventEmitter, FocusableView, Global, Hsla, InteractiveElement, IntoElement, Model,
SharedString, Styled as _, View, ViewContext, VisualContext, WindowContext, ParentElement, Render, SharedString, Styled as _, View, ViewContext, VisualContext,
WindowContext,
}; };
use ui::{ use ui::{
@ -62,7 +63,30 @@ use ui::{
const PANEL_NAME: &str = "StoryContainer"; const PANEL_NAME: &str = "StoryContainer";
pub struct AppState {
pub invisible_panels: Model<Vec<SharedString>>,
}
impl AppState {
fn init(cx: &mut AppContext) {
let state = Self {
invisible_panels: cx.new_model(|_| Vec::new()),
};
cx.set_global::<AppState>(state);
}
pub fn global(cx: &AppContext) -> &Self {
cx.global::<Self>()
}
pub fn global_mut(cx: &mut AppContext) -> &mut Self {
cx.global_mut::<Self>()
}
}
impl Global for AppState {}
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
AppState::init(cx);
input_story::init(cx); input_story::init(cx);
dropdown_story::init(cx); dropdown_story::init(cx);
popup_story::init(cx); popup_story::init(cx);
@ -76,7 +100,7 @@ pub fn init(cx: &mut AppContext) {
}; };
let view = cx.new_view(|cx| { let view = cx.new_view(|cx| {
let (title, description, closeable, zoomable, story) = story_state.to_story(cx); let (title, description, closable, zoomable, story) = story_state.to_story(cx);
let mut container = StoryContainer::new(cx).story(story, story_state.story_klass); let mut container = StoryContainer::new(cx).story(story, story_state.story_klass);
cx.on_focus_in(&container.focus_handle, |this: &mut StoryContainer, _| { cx.on_focus_in(&container.focus_handle, |this: &mut StoryContainer, _| {
@ -86,7 +110,7 @@ pub fn init(cx: &mut AppContext) {
container.name = title.into(); container.name = title.into();
container.description = description.into(); container.description = description.into();
container.closeable = closeable; container.closable = closable;
container.zoomable = zoomable; container.zoomable = zoomable;
container container
}); });
@ -122,7 +146,7 @@ pub struct StoryContainer {
height: Option<gpui::Pixels>, height: Option<gpui::Pixels>,
story: Option<AnyView>, story: Option<AnyView>,
story_klass: Option<SharedString>, story_klass: Option<SharedString>,
closeable: bool, closable: bool,
zoomable: bool, zoomable: bool,
} }
@ -140,7 +164,7 @@ pub trait Story: FocusableView {
fn description() -> &'static str { fn description() -> &'static str {
"" ""
} }
fn closeable() -> bool { fn closable() -> bool {
true true
} }
fn zoomable() -> bool { fn zoomable() -> bool {
@ -167,7 +191,7 @@ impl StoryContainer {
height: None, height: None,
story: None, story: None,
story_klass: None, story_klass: None,
closeable: true, closable: true,
zoomable: true, zoomable: true,
} }
} }
@ -182,7 +206,7 @@ impl StoryContainer {
let view = cx.new_view(|cx| { let view = cx.new_view(|cx| {
let mut story = Self::new(cx).story(story.into(), story_klass); let mut story = Self::new(cx).story(story.into(), story_klass);
story.focus_handle = focus_handle; story.focus_handle = focus_handle;
story.closeable = S::closeable(); story.closable = S::closable();
story.zoomable = S::zoomable(); story.zoomable = S::zoomable();
story.name = name.into(); story.name = name.into();
story.description = description.into(); story.description = description.into();
@ -242,7 +266,7 @@ impl StoryState {
( (
$klass::title(), $klass::title(),
$klass::description(), $klass::description(),
$klass::closeable(), $klass::closable(),
$klass::zoomable(), $klass::zoomable(),
$klass::view(cx).into(), $klass::view(cx).into(),
) )
@ -285,7 +309,7 @@ impl Panel for StoryContainer {
self.name.clone().into_any_element() self.name.clone().into_any_element()
} }
fn title_style(&self, cx: &WindowContext) -> Option<TitleStyle> { fn title_style(&self, cx: &AppContext) -> Option<TitleStyle> {
if let Some(bg) = self.title_bg { if let Some(bg) = self.title_bg {
Some(TitleStyle { Some(TitleStyle {
background: bg, background: bg,
@ -296,14 +320,21 @@ impl Panel for StoryContainer {
} }
} }
fn closeable(&self, _cx: &WindowContext) -> bool { fn closable(&self, _cx: &AppContext) -> bool {
self.closeable self.closable
} }
fn zoomable(&self, _cx: &WindowContext) -> bool { fn zoomable(&self, _cx: &AppContext) -> bool {
self.zoomable self.zoomable
} }
fn visible(&self, cx: &AppContext) -> bool {
!AppState::global(cx)
.invisible_panels
.read(cx)
.contains(&self.name)
}
fn set_zoomed(&self, zoomed: bool, _cx: &ViewContext<Self>) { fn set_zoomed(&self, zoomed: bool, _cx: &ViewContext<Self>) {
println!("panel: {} zoomed: {}", self.name, zoomed); println!("panel: {} zoomed: {}", self.name, zoomed);
} }

View file

@ -4,9 +4,10 @@ use prelude::FluentBuilder as _;
use serde::Deserialize; use serde::Deserialize;
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use story::{ use story::{
AccordionStory, Assets, ButtonStory, CalendarStory, DropdownStory, IconStory, ImageStory, AccordionStory, AppState, Assets, ButtonStory, CalendarStory, DropdownStory, IconStory,
InputStory, ListStory, ModalStory, PopupStory, ProgressStory, ResizableStory, ScrollableStory, ImageStory, InputStory, ListStory, ModalStory, PopupStory, ProgressStory, ResizableStory,
SidebarStory, StoryContainer, SwitchStory, TableStory, TextStory, TooltipStory, ScrollableStory, SidebarStory, StoryContainer, SwitchStory, TableStory, TextStory,
TooltipStory,
}; };
use ui::{ use ui::{
button::{Button, ButtonVariants as _}, button::{Button, ButtonVariants as _},
@ -36,9 +37,18 @@ struct SelectFont(usize);
#[derive(Clone, PartialEq, Eq, Deserialize)] #[derive(Clone, PartialEq, Eq, Deserialize)]
struct AddPanel(DockPlacement); struct AddPanel(DockPlacement);
#[derive(Clone, PartialEq, Eq, Deserialize)]
struct TogglePanelVisible(SharedString);
impl_actions!( impl_actions!(
story, story,
[SelectLocale, SelectFont, AddPanel, SelectScrollbarShow] [
SelectLocale,
SelectFont,
AddPanel,
SelectScrollbarShow,
TogglePanelVisible
]
); );
actions!(main_menu, [Quit]); actions!(main_menu, [Quit]);
@ -395,6 +405,24 @@ impl StoryWorkspace {
dock_area.add_panel(panel, action.0, cx); dock_area.add_panel(panel, action.0, cx);
}); });
} }
fn on_action_toggle_panel_visible(
&mut self,
action: &TogglePanelVisible,
cx: &mut ViewContext<Self>,
) {
let panel_name = action.0.clone();
let invisible_panels = AppState::global(cx).invisible_panels.clone();
invisible_panels.update(cx, |names, cx| {
if names.contains(&panel_name) {
names.retain(|id| id != &panel_name);
} else {
names.push(panel_name);
}
cx.notify();
});
cx.notify();
}
} }
pub fn open_new( pub fn open_new(
@ -417,10 +445,12 @@ impl Render for StoryWorkspace {
let modal_layer = Root::render_modal_layer(cx); let modal_layer = Root::render_modal_layer(cx);
let notification_layer = Root::render_notification_layer(cx); let notification_layer = Root::render_notification_layer(cx);
let notifications_count = cx.notifications().len(); let notifications_count = cx.notifications().len();
let invisible_panels = AppState::global(cx).invisible_panels.clone();
div() div()
.id("story-workspace") .id("story-workspace")
.on_action(cx.listener(Self::on_action_add_panel)) .on_action(cx.listener(Self::on_action_add_panel))
.on_action(cx.listener(Self::on_action_toggle_panel_visible))
.relative() .relative()
.size_full() .size_full()
.flex() .flex()
@ -442,7 +472,7 @@ impl Render for StoryWorkspace {
.icon(IconName::LayoutDashboard) .icon(IconName::LayoutDashboard)
.small() .small()
.ghost() .ghost()
.popup_menu(|menu, _| { .popup_menu(move |menu, cx| {
menu.menu( menu.menu(
"Add Panel to Center", "Add Panel to Center",
Box::new(AddPanel(DockPlacement::Center)), Box::new(AddPanel(DockPlacement::Center)),
@ -460,6 +490,43 @@ impl Render for StoryWorkspace {
"Add Panel to Bottom", "Add Panel to Bottom",
Box::new(AddPanel(DockPlacement::Bottom)), Box::new(AddPanel(DockPlacement::Bottom)),
) )
.separator()
.menu_with_check(
"Sidebar",
!invisible_panels
.read(cx)
.contains(&SharedString::from("Sidebar")),
Box::new(TogglePanelVisible(SharedString::from(
"Sidebar",
))),
)
.menu_with_check(
"Modal",
!invisible_panels
.read(cx)
.contains(&SharedString::from("SidebModalar")),
Box::new(TogglePanelVisible(SharedString::from(
"Modal",
))),
)
.menu_with_check(
"Accordion",
!invisible_panels
.read(cx)
.contains(&SharedString::from("Accordion")),
Box::new(TogglePanelVisible(SharedString::from(
"Accordion",
))),
)
.menu_with_check(
"List",
!invisible_panels
.read(cx)
.contains(&SharedString::from("List")),
Box::new(TogglePanelVisible(SharedString::from(
"List",
))),
)
}) })
.anchor(Corner::TopRight), .anchor(Corner::TopRight),
) )

View file

@ -505,7 +505,7 @@ impl super::Story for TableStory {
Self::view(cx) Self::view(cx)
} }
fn closeable() -> bool { fn closable() -> bool {
false false
} }
} }

View file

@ -81,7 +81,7 @@ impl Dock {
) -> Self { ) -> Self {
let panel = cx.new_view(|cx| { let panel = cx.new_view(|cx| {
let mut tab = TabPanel::new(None, dock_area.clone(), cx); let mut tab = TabPanel::new(None, dock_area.clone(), cx);
tab.closeable = false; tab.closable = false;
tab tab
}); });

View file

@ -45,17 +45,28 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
} }
/// The theme of the panel title, default is `None`. /// The theme of the panel title, default is `None`.
fn title_style(&self, cx: &WindowContext) -> Option<TitleStyle> { fn title_style(&self, cx: &AppContext) -> Option<TitleStyle> {
None None
} }
/// Whether the panel can be closed, default is `true`. /// Whether the panel can be closed, default is `true`.
fn closeable(&self, cx: &WindowContext) -> bool { ///
/// This method called in Panel render, we should make sure it is fast.
fn closable(&self, cx: &AppContext) -> bool {
true true
} }
/// Return true if the panel is zoomable, default is `false`. /// Return true if the panel is zoomable, default is `false`.
fn zoomable(&self, cx: &WindowContext) -> bool { ///
/// This method called in Panel render, we should make sure it is fast.
fn zoomable(&self, cx: &AppContext) -> bool {
true
}
/// Return false to hide panel, true to show panel, default is `true`.
///
/// This method called in Panel render, we should make sure it is fast.
fn visible(&self, cx: &AppContext) -> bool {
true true
} }
@ -95,9 +106,10 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
pub trait PanelView: 'static + Send + Sync { pub trait PanelView: 'static + Send + Sync {
fn panel_name(&self, cx: &AppContext) -> &'static str; fn panel_name(&self, cx: &AppContext) -> &'static str;
fn title(&self, cx: &WindowContext) -> AnyElement; fn title(&self, cx: &WindowContext) -> AnyElement;
fn title_style(&self, cx: &WindowContext) -> Option<TitleStyle>; fn title_style(&self, cx: &AppContext) -> Option<TitleStyle>;
fn closeable(&self, cx: &WindowContext) -> bool; fn closable(&self, cx: &AppContext) -> bool;
fn zoomable(&self, cx: &WindowContext) -> bool; fn zoomable(&self, cx: &AppContext) -> bool;
fn visible(&self, cx: &AppContext) -> bool;
fn set_active(&self, active: bool, cx: &mut WindowContext); fn set_active(&self, active: bool, cx: &mut WindowContext);
fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu; fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu;
@ -116,18 +128,22 @@ impl<T: Panel> PanelView for View<T> {
self.read(cx).title(cx) self.read(cx).title(cx)
} }
fn title_style(&self, cx: &WindowContext) -> Option<TitleStyle> { fn title_style(&self, cx: &AppContext) -> Option<TitleStyle> {
self.read(cx).title_style(cx) self.read(cx).title_style(cx)
} }
fn closeable(&self, cx: &WindowContext) -> bool { fn closable(&self, cx: &AppContext) -> bool {
self.read(cx).closeable(cx) self.read(cx).closable(cx)
} }
fn zoomable(&self, cx: &WindowContext) -> bool { fn zoomable(&self, cx: &AppContext) -> bool {
self.read(cx).zoomable(cx) self.read(cx).zoomable(cx)
} }
fn visible(&self, cx: &AppContext) -> bool {
self.read(cx).visible(cx)
}
fn set_active(&self, active: bool, cx: &mut WindowContext) { fn set_active(&self, active: bool, cx: &mut WindowContext) {
self.update(cx, |this, cx| { self.update(cx, |this, cx| {
this.set_active(active, cx); this.set_active(active, cx);

View file

@ -172,6 +172,7 @@ impl StackPanel {
fn new_resizable_panel(panel: Arc<dyn PanelView>, size: Option<Pixels>) -> ResizablePanel { fn new_resizable_panel(panel: Arc<dyn PanelView>, size: Option<Pixels>) -> ResizablePanel {
resizable_panel() resizable_panel()
.content_view(panel.view()) .content_view(panel.view())
.content_visible(move |cx| panel.visible(cx))
.when_some(size, |this, size| this.size(size)) .when_some(size, |this, size| this.size(size))
} }

View file

@ -26,7 +26,7 @@ use super::{
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct TabState { struct TabState {
closeable: bool, closable: bool,
zoomable: bool, zoomable: bool,
draggable: bool, draggable: bool,
droppable: bool, droppable: bool,
@ -71,9 +71,9 @@ pub struct TabPanel {
stack_panel: Option<WeakView<StackPanel>>, stack_panel: Option<WeakView<StackPanel>>,
pub(crate) panels: Vec<Arc<dyn PanelView>>, pub(crate) panels: Vec<Arc<dyn PanelView>>,
pub(crate) active_ix: usize, pub(crate) active_ix: usize,
/// If this is true, the Panel closeable will follow the active panel's closeable, /// If this is true, the Panel closable will follow the active panel's closable,
/// otherwise this TabPanel will not able to close /// otherwise this TabPanel will not able to close
pub(crate) closeable: bool, pub(crate) closable: bool,
tab_bar_scroll_handle: ScrollHandle, tab_bar_scroll_handle: ScrollHandle,
is_zoomed: bool, is_zoomed: bool,
@ -88,29 +88,33 @@ impl Panel for TabPanel {
} }
fn title(&self, cx: &WindowContext) -> gpui::AnyElement { fn title(&self, cx: &WindowContext) -> gpui::AnyElement {
self.active_panel() self.active_panel(cx)
.map(|panel| panel.title(cx)) .map(|panel| panel.title(cx))
.unwrap_or("Empty Tab".into_any_element()) .unwrap_or("Empty Tab".into_any_element())
} }
fn closeable(&self, cx: &WindowContext) -> bool { fn closable(&self, cx: &AppContext) -> bool {
if !self.closeable { if !self.closable {
return false; return false;
} }
self.active_panel() self.active_panel(cx)
.map(|panel| panel.closeable(cx)) .map(|panel| panel.closable(cx))
.unwrap_or(false) .unwrap_or(false)
} }
fn zoomable(&self, cx: &WindowContext) -> bool { fn zoomable(&self, cx: &AppContext) -> bool {
self.active_panel() self.active_panel(cx)
.map(|panel| panel.zoomable(cx)) .map(|panel| panel.zoomable(cx))
.unwrap_or(false) .unwrap_or(false)
} }
fn visible(&self, cx: &AppContext) -> bool {
self.visible_panels(cx).next().is_some()
}
fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu { fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu {
if let Some(panel) = self.active_panel() { if let Some(panel) = self.active_panel(cx) {
panel.popup_menu(menu, cx) panel.popup_menu(menu, cx)
} else { } else {
menu menu
@ -118,7 +122,7 @@ impl Panel for TabPanel {
} }
fn toolbar_buttons(&self, cx: &WindowContext) -> Vec<Button> { fn toolbar_buttons(&self, cx: &WindowContext) -> Vec<Button> {
if let Some(panel) = self.active_panel() { if let Some(panel) = self.active_panel(cx) {
panel.toolbar_buttons(cx) panel.toolbar_buttons(cx)
} else { } else {
vec![] vec![]
@ -151,7 +155,7 @@ impl TabPanel {
will_split_placement: None, will_split_placement: None,
is_zoomed: false, is_zoomed: false,
is_collapsed: false, is_collapsed: false,
closeable: true, closable: true,
} }
} }
@ -160,8 +164,19 @@ impl TabPanel {
} }
/// Return current active_panel View /// Return current active_panel View
pub fn active_panel(&self) -> Option<Arc<dyn PanelView>> { pub fn active_panel(&self, cx: &AppContext) -> Option<Arc<dyn PanelView>> {
self.panels.get(self.active_ix).cloned() let panel = self.panels.get(self.active_ix);
if let Some(panel) = panel {
if panel.visible(cx) {
Some(panel.clone())
} else {
// Return the first visible panel
self.visible_panels(cx).next()
}
} else {
None
}
} }
fn set_active_ix(&mut self, ix: usize, cx: &mut ViewContext<Self>) { fn set_active_ix(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
@ -335,6 +350,20 @@ impl TabPanel {
self.panels.len() <= 1 self.panels.len() <= 1
} }
/// Return all visible panels
fn visible_panels<'a>(
&'a self,
cx: &'a AppContext,
) -> impl Iterator<Item = Arc<dyn PanelView>> + 'a {
self.panels.iter().filter_map(|panel| {
if panel.visible(cx) {
Some(panel.clone())
} else {
None
}
})
}
/// Return true if the tab panel is draggable. /// Return true if the tab panel is draggable.
/// ///
/// E.g. if the parent and self only have one panel, it is not draggable. /// E.g. if the parent and self only have one panel, it is not draggable.
@ -392,7 +421,7 @@ impl TabPanel {
}; };
this.separator().menu(name, Box::new(ToggleZoom)) this.separator().menu(name, Box::new(ToggleZoom))
}) })
.when(state.closeable, |this| { .when(state.closable, |this| {
this.separator() this.separator()
.menu(t!("Dock.Close"), Box::new(ClosePanel)) .menu(t!("Dock.Close"), Box::new(ClosePanel))
}) })
@ -496,6 +525,11 @@ impl TabPanel {
if self.panels.len() == 1 && panel_style == PanelStyle::Default { if self.panels.len() == 1 && panel_style == PanelStyle::Default {
let panel = self.panels.get(0).unwrap(); let panel = self.panels.get(0).unwrap();
if !panel.visible(cx) {
return div().into_any_element();
}
let title_style = panel.title_style(cx); let title_style = panel.title_style(cx);
return h_flex() return h_flex()
@ -580,47 +614,53 @@ impl TabPanel {
) )
}, },
) )
.children(self.panels.iter().enumerate().map(|(ix, panel)| { .children(self.panels.iter().enumerate().filter_map(|(ix, panel)| {
let mut active = ix == self.active_ix; let mut active = ix == self.active_ix;
let disabled = self.is_collapsed; let disabled = self.is_collapsed;
if !panel.visible(cx) {
return None;
}
// Always not show active tab style, if the panel is collapsed // Always not show active tab style, if the panel is collapsed
if self.is_collapsed { if self.is_collapsed {
active = false; active = false;
} }
Tab::new(("tab", ix), panel.title(cx)) Some(
.py_2() Tab::new(("tab", ix), panel.title(cx))
.selected(active) .py_2()
.disabled(disabled) .selected(active)
.when(!disabled, |this| { .disabled(disabled)
this.on_click(cx.listener(move |view, _, cx| { .when(!disabled, |this| {
view.set_active_ix(ix, cx); this.on_click(cx.listener(move |view, _, cx| {
})) view.set_active_ix(ix, cx);
.when(state.draggable, |this| { }))
this.on_drag( .when(state.draggable, |this| {
DragPanel::new(panel.clone(), view.clone()), this.on_drag(
|drag, _, cx| { DragPanel::new(panel.clone(), view.clone()),
cx.stop_propagation(); |drag, _, cx| {
cx.new_view(|_| drag.clone()) cx.stop_propagation();
}, cx.new_view(|_| drag.clone())
) },
}) )
.when(state.droppable, |this| {
this.drag_over::<DragPanel>(|this, _, cx| {
this.rounded_l_none()
.border_l_2()
.border_r_0()
.border_color(cx.theme().drag_border)
}) })
.on_drop(cx.listener( .when(state.droppable, |this| {
move |this, drag: &DragPanel, cx| { this.drag_over::<DragPanel>(|this, _, cx| {
this.will_split_placement = None; this.rounded_l_none()
this.on_drop(drag, Some(ix), true, cx) .border_l_2()
}, .border_r_0()
)) .border_color(cx.theme().drag_border)
}) })
}) .on_drop(cx.listener(
move |this, drag: &DragPanel, cx| {
this.will_split_placement = None;
this.on_drop(drag, Some(ix), true, cx)
},
))
})
}),
)
})) }))
.child( .child(
// empty space to allow move to last tab right // empty space to allow move to last tab right
@ -667,7 +707,7 @@ impl TabPanel {
return Empty {}.into_any_element(); return Empty {}.into_any_element();
} }
self.active_panel() self.active_panel(cx)
.map(|panel| { .map(|panel| {
div() div()
.id("tab-content") .id("tab-content")
@ -885,7 +925,7 @@ impl TabPanel {
} }
fn focus_active_panel(&self, cx: &mut ViewContext<Self>) { fn focus_active_panel(&self, cx: &mut ViewContext<Self>) {
if let Some(active_panel) = self.active_panel() { if let Some(active_panel) = self.active_panel(cx) {
active_panel.focus_handle(cx).focus(cx); active_panel.focus_handle(cx).focus(cx);
} }
} }
@ -916,7 +956,7 @@ impl TabPanel {
} }
fn on_action_close_panel(&mut self, _: &ClosePanel, cx: &mut ViewContext<Self>) { fn on_action_close_panel(&mut self, _: &ClosePanel, cx: &mut ViewContext<Self>) {
if let Some(panel) = self.active_panel() { if let Some(panel) = self.active_panel(cx) {
self.remove_panel(panel, cx); self.remove_panel(panel, cx);
} }
} }
@ -924,7 +964,7 @@ impl TabPanel {
impl FocusableView for TabPanel { impl FocusableView for TabPanel {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
if let Some(active_panel) = self.active_panel() { if let Some(active_panel) = self.active_panel(cx) {
active_panel.focus_handle(cx) active_panel.focus_handle(cx)
} else { } else {
self.focus_handle.clone() self.focus_handle.clone()
@ -937,13 +977,13 @@ impl Render for TabPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement {
let focus_handle = self.focus_handle(cx); let focus_handle = self.focus_handle(cx);
let mut state = TabState { let mut state = TabState {
closeable: self.closeable(cx), closable: self.closable(cx),
draggable: self.draggable(cx), draggable: self.draggable(cx),
droppable: self.droppable(cx), droppable: self.droppable(cx),
zoomable: self.zoomable(cx), zoomable: self.zoomable(cx),
}; };
if !state.draggable { if !state.draggable {
state.closeable = false; state.closable = false;
} }
v_flex() v_flex()

View file

@ -285,6 +285,7 @@ pub struct ResizablePanel {
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>,
content_visible: Rc<Box<dyn Fn(&WindowContext) -> bool>>,
/// The bounds of the resizable panel, when render the bounds will be updated. /// The bounds of the resizable panel, when render the bounds will be updated.
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
resize_handle: Option<AnyElement>, resize_handle: Option<AnyElement>,
@ -300,6 +301,7 @@ impl ResizablePanel {
axis: Axis::Horizontal, axis: Axis::Horizontal,
content_builder: None, content_builder: None,
content_view: None, content_view: None,
content_visible: Rc::new(Box::new(|_| true)),
bounds: Bounds::default(), bounds: Bounds::default(),
resize_handle: None, resize_handle: None,
} }
@ -313,6 +315,14 @@ impl ResizablePanel {
self self
} }
pub(crate) fn content_visible<F>(mut self, content_visible: F) -> Self
where
F: Fn(&WindowContext) -> bool + 'static,
{
self.content_visible = Rc::new(Box::new(content_visible));
self
}
pub fn content_view(mut self, content: AnyView) -> Self { pub fn content_view(mut self, content: AnyView) -> Self {
self.content_view = Some(content); self.content_view = Some(content);
self self
@ -350,6 +360,10 @@ 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 {
if !(self.content_visible)(cx) {
return div();
}
let view = cx.view().clone(); let view = cx.view().clone();
let total_size = self let total_size = self
.group .group