diff --git a/crates/story/src/button_story.rs b/crates/story/src/button_story.rs index fdbaa98d..d2d57b40 100644 --- a/crates/story/src/button_story.rs +++ b/crates/story/src/button_story.rs @@ -50,7 +50,7 @@ impl super::Story for ButtonStory { "Displays a button or a component that looks like a button." } - fn closeable() -> bool { + fn closable() -> bool { false } diff --git a/crates/story/src/input_story.rs b/crates/story/src/input_story.rs index 9d793cfb..8ee53352 100644 --- a/crates/story/src/input_story.rs +++ b/crates/story/src/input_story.rs @@ -55,7 +55,7 @@ impl super::Story for InputStory { "Input" } - fn closeable() -> bool { + fn closable() -> bool { false } diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 015aad95..d5804017 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -43,9 +43,10 @@ pub use tooltip_story::TooltipStory; pub use webview_story::WebViewStory; use gpui::{ - actions, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, - EventEmitter, FocusableView, Hsla, InteractiveElement, IntoElement, ParentElement, Render, - SharedString, Styled as _, View, ViewContext, VisualContext, WindowContext, + actions, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Context as _, + Div, EventEmitter, FocusableView, Global, Hsla, InteractiveElement, IntoElement, Model, + ParentElement, Render, SharedString, Styled as _, View, ViewContext, VisualContext, + WindowContext, }; use ui::{ @@ -62,7 +63,30 @@ use ui::{ const PANEL_NAME: &str = "StoryContainer"; +pub struct AppState { + pub invisible_panels: Model>, +} +impl AppState { + fn init(cx: &mut AppContext) { + let state = Self { + invisible_panels: cx.new_model(|_| Vec::new()), + }; + cx.set_global::(state); + } + + pub fn global(cx: &AppContext) -> &Self { + cx.global::() + } + + pub fn global_mut(cx: &mut AppContext) -> &mut Self { + cx.global_mut::() + } +} + +impl Global for AppState {} + pub fn init(cx: &mut AppContext) { + AppState::init(cx); input_story::init(cx); dropdown_story::init(cx); popup_story::init(cx); @@ -76,7 +100,7 @@ pub fn init(cx: &mut AppContext) { }; 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); 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.description = description.into(); - container.closeable = closeable; + container.closable = closable; container.zoomable = zoomable; container }); @@ -122,7 +146,7 @@ pub struct StoryContainer { height: Option, story: Option, story_klass: Option, - closeable: bool, + closable: bool, zoomable: bool, } @@ -140,7 +164,7 @@ pub trait Story: FocusableView { fn description() -> &'static str { "" } - fn closeable() -> bool { + fn closable() -> bool { true } fn zoomable() -> bool { @@ -167,7 +191,7 @@ impl StoryContainer { height: None, story: None, story_klass: None, - closeable: true, + closable: true, zoomable: true, } } @@ -182,7 +206,7 @@ impl StoryContainer { let view = cx.new_view(|cx| { let mut story = Self::new(cx).story(story.into(), story_klass); story.focus_handle = focus_handle; - story.closeable = S::closeable(); + story.closable = S::closable(); story.zoomable = S::zoomable(); story.name = name.into(); story.description = description.into(); @@ -242,7 +266,7 @@ impl StoryState { ( $klass::title(), $klass::description(), - $klass::closeable(), + $klass::closable(), $klass::zoomable(), $klass::view(cx).into(), ) @@ -285,7 +309,7 @@ impl Panel for StoryContainer { self.name.clone().into_any_element() } - fn title_style(&self, cx: &WindowContext) -> Option { + fn title_style(&self, cx: &AppContext) -> Option { if let Some(bg) = self.title_bg { Some(TitleStyle { background: bg, @@ -296,14 +320,21 @@ impl Panel for StoryContainer { } } - fn closeable(&self, _cx: &WindowContext) -> bool { - self.closeable + fn closable(&self, _cx: &AppContext) -> bool { + self.closable } - fn zoomable(&self, _cx: &WindowContext) -> bool { + fn zoomable(&self, _cx: &AppContext) -> bool { 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) { println!("panel: {} zoomed: {}", self.name, zoomed); } diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index 37d2a1e7..dce6c999 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -4,9 +4,10 @@ use prelude::FluentBuilder as _; use serde::Deserialize; use std::{sync::Arc, time::Duration}; use story::{ - AccordionStory, Assets, ButtonStory, CalendarStory, DropdownStory, IconStory, ImageStory, - InputStory, ListStory, ModalStory, PopupStory, ProgressStory, ResizableStory, ScrollableStory, - SidebarStory, StoryContainer, SwitchStory, TableStory, TextStory, TooltipStory, + AccordionStory, AppState, Assets, ButtonStory, CalendarStory, DropdownStory, IconStory, + ImageStory, InputStory, ListStory, ModalStory, PopupStory, ProgressStory, ResizableStory, + ScrollableStory, SidebarStory, StoryContainer, SwitchStory, TableStory, TextStory, + TooltipStory, }; use ui::{ button::{Button, ButtonVariants as _}, @@ -36,9 +37,18 @@ struct SelectFont(usize); #[derive(Clone, PartialEq, Eq, Deserialize)] struct AddPanel(DockPlacement); +#[derive(Clone, PartialEq, Eq, Deserialize)] +struct TogglePanelVisible(SharedString); + impl_actions!( story, - [SelectLocale, SelectFont, AddPanel, SelectScrollbarShow] + [ + SelectLocale, + SelectFont, + AddPanel, + SelectScrollbarShow, + TogglePanelVisible + ] ); actions!(main_menu, [Quit]); @@ -395,6 +405,24 @@ impl StoryWorkspace { dock_area.add_panel(panel, action.0, cx); }); } + + fn on_action_toggle_panel_visible( + &mut self, + action: &TogglePanelVisible, + cx: &mut ViewContext, + ) { + 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( @@ -417,10 +445,12 @@ impl Render for StoryWorkspace { let modal_layer = Root::render_modal_layer(cx); let notification_layer = Root::render_notification_layer(cx); let notifications_count = cx.notifications().len(); + let invisible_panels = AppState::global(cx).invisible_panels.clone(); div() .id("story-workspace") .on_action(cx.listener(Self::on_action_add_panel)) + .on_action(cx.listener(Self::on_action_toggle_panel_visible)) .relative() .size_full() .flex() @@ -442,7 +472,7 @@ impl Render for StoryWorkspace { .icon(IconName::LayoutDashboard) .small() .ghost() - .popup_menu(|menu, _| { + .popup_menu(move |menu, cx| { menu.menu( "Add Panel to Center", Box::new(AddPanel(DockPlacement::Center)), @@ -460,6 +490,43 @@ impl Render for StoryWorkspace { "Add Panel to 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), ) diff --git a/crates/story/src/table_story.rs b/crates/story/src/table_story.rs index b5c68b83..df3ed703 100644 --- a/crates/story/src/table_story.rs +++ b/crates/story/src/table_story.rs @@ -505,7 +505,7 @@ impl super::Story for TableStory { Self::view(cx) } - fn closeable() -> bool { + fn closable() -> bool { false } } diff --git a/crates/ui/src/dock/dock.rs b/crates/ui/src/dock/dock.rs index 62f66682..b0127c39 100644 --- a/crates/ui/src/dock/dock.rs +++ b/crates/ui/src/dock/dock.rs @@ -81,7 +81,7 @@ impl Dock { ) -> Self { let panel = cx.new_view(|cx| { let mut tab = TabPanel::new(None, dock_area.clone(), cx); - tab.closeable = false; + tab.closable = false; tab }); diff --git a/crates/ui/src/dock/panel.rs b/crates/ui/src/dock/panel.rs index 5b880642..c7f9425c 100644 --- a/crates/ui/src/dock/panel.rs +++ b/crates/ui/src/dock/panel.rs @@ -45,17 +45,28 @@ pub trait Panel: EventEmitter + FocusableView { } /// The theme of the panel title, default is `None`. - fn title_style(&self, cx: &WindowContext) -> Option { + fn title_style(&self, cx: &AppContext) -> Option { None } /// 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 } /// 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 } @@ -95,9 +106,10 @@ pub trait Panel: EventEmitter + FocusableView { pub trait PanelView: 'static + Send + Sync { fn panel_name(&self, cx: &AppContext) -> &'static str; fn title(&self, cx: &WindowContext) -> AnyElement; - fn title_style(&self, cx: &WindowContext) -> Option; - fn closeable(&self, cx: &WindowContext) -> bool; - fn zoomable(&self, cx: &WindowContext) -> bool; + fn title_style(&self, cx: &AppContext) -> Option; + fn closable(&self, cx: &AppContext) -> bool; + fn zoomable(&self, cx: &AppContext) -> bool; + fn visible(&self, cx: &AppContext) -> bool; fn set_active(&self, active: bool, cx: &mut WindowContext); fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu; @@ -116,18 +128,22 @@ impl PanelView for View { self.read(cx).title(cx) } - fn title_style(&self, cx: &WindowContext) -> Option { + fn title_style(&self, cx: &AppContext) -> Option { self.read(cx).title_style(cx) } - fn closeable(&self, cx: &WindowContext) -> bool { - self.read(cx).closeable(cx) + fn closable(&self, cx: &AppContext) -> bool { + self.read(cx).closable(cx) } - fn zoomable(&self, cx: &WindowContext) -> bool { + fn zoomable(&self, cx: &AppContext) -> bool { 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) { self.update(cx, |this, cx| { this.set_active(active, cx); diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index 2ee4cf87..95243e98 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -172,6 +172,7 @@ impl StackPanel { fn new_resizable_panel(panel: Arc, size: Option) -> ResizablePanel { resizable_panel() .content_view(panel.view()) + .content_visible(move |cx| panel.visible(cx)) .when_some(size, |this, size| this.size(size)) } diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index 746be62b..507a1a34 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -26,7 +26,7 @@ use super::{ #[derive(Clone, Copy)] struct TabState { - closeable: bool, + closable: bool, zoomable: bool, draggable: bool, droppable: bool, @@ -71,9 +71,9 @@ pub struct TabPanel { stack_panel: Option>, pub(crate) panels: Vec>, 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 - pub(crate) closeable: bool, + pub(crate) closable: bool, tab_bar_scroll_handle: ScrollHandle, is_zoomed: bool, @@ -88,29 +88,33 @@ impl Panel for TabPanel { } fn title(&self, cx: &WindowContext) -> gpui::AnyElement { - self.active_panel() + self.active_panel(cx) .map(|panel| panel.title(cx)) .unwrap_or("Empty Tab".into_any_element()) } - fn closeable(&self, cx: &WindowContext) -> bool { - if !self.closeable { + fn closable(&self, cx: &AppContext) -> bool { + if !self.closable { return false; } - self.active_panel() - .map(|panel| panel.closeable(cx)) + self.active_panel(cx) + .map(|panel| panel.closable(cx)) .unwrap_or(false) } - fn zoomable(&self, cx: &WindowContext) -> bool { - self.active_panel() + fn zoomable(&self, cx: &AppContext) -> bool { + self.active_panel(cx) .map(|panel| panel.zoomable(cx)) .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 { - if let Some(panel) = self.active_panel() { + if let Some(panel) = self.active_panel(cx) { panel.popup_menu(menu, cx) } else { menu @@ -118,7 +122,7 @@ impl Panel for TabPanel { } fn toolbar_buttons(&self, cx: &WindowContext) -> Vec