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:
parent
4b211bfe21
commit
1a4ee348f8
10 changed files with 256 additions and 87 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ impl super::Story for InputStory {
|
||||||
"Input"
|
"Input"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn closeable() -> bool {
|
fn closable() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -505,7 +505,7 @@ impl super::Story for TableStory {
|
||||||
Self::view(cx)
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn closeable() -> bool {
|
fn closable() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue