diff --git a/crates/app/src/story_workspace.rs b/crates/app/src/story_workspace.rs index 50908243..6de14bd8 100644 --- a/crates/app/src/story_workspace.rs +++ b/crates/app/src/story_workspace.rs @@ -76,101 +76,112 @@ impl StoryWorkspace { }) }); - StoryContainer::add_pane( + StoryContainer::add_panel( "Buttons", "Displays a button or a component that looks like a button.", ButtonStory::view(cx).into(), tab_panel.clone(), + false, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Input", "A control that allows the user to input text.", InputStory::view(cx).into(), tab_panel.clone(), + false, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Text", "Links, paragraphs, checkboxes, and more.", TextStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Switch", "A control that allows the user to toggle between two states.", SwitchStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Dropdowns", "Displays a list of options for the user to pick from—triggered by a button.", DropdownStory::new(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Modal", "Modal & Drawer use examples", ModalStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Popup", "A popup displays content on top of the main page.", PopupStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Tooltip", "Displays a short message when users hover over an element.", TooltipStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "List", "A list displays a series of items.", ListStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Icon", "Icon use examples", IconStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Image", "Render SVG image and Chart", ImageStory::view(cx).into(), right_tab_panel1.clone(), + true, cx, ) .detach(); @@ -183,47 +194,52 @@ impl StoryWorkspace { // cx, // ); - StoryContainer::add_pane( + StoryContainer::add_panel( "Table", "Powerful table and datagrids built.", TableStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Progress", "Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.", ProgressStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Resizable", "Accessible resizable panel groups and layouts with keyboard support.", ResizableStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Scrollable", "A scrollable area with scroll bar.", ScrollableStory::view(cx).into(), tab_panel.clone(), + true, cx, ) .detach(); - StoryContainer::add_pane( + StoryContainer::add_panel( "Calendar", "A calendar component.", CalendarStory::view(cx).into(), right_tab_panel.clone(), + true, cx, ) .detach(); diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 3b1c557b..6edb4231 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -37,8 +37,8 @@ pub use tooltip_story::TooltipStory; pub use webview_story::WebViewStory; use gpui::{ - div, prelude::FluentBuilder as _, px, AnyView, AppContext, Div, EventEmitter, FocusableView, - InteractiveElement, IntoElement, ParentElement, Render, SharedString, + actions, div, prelude::FluentBuilder as _, px, AnyView, AppContext, Div, EventEmitter, + FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled as _, Task, View, ViewContext, VisualContext, WindowContext, }; @@ -48,6 +48,7 @@ use ui::{ dock::{Panel, PanelEvent, TabPanel}, h_flex, label::Label, + popup_menu::PopupMenu, v_flex, }; @@ -57,6 +58,8 @@ pub fn init(cx: &mut AppContext) { popup_story::init(cx); } +actions!(story, [PanelInfo]); + pub fn section(title: impl IntoElement, cx: &WindowContext) -> Div { use ui::theme::ActiveTheme; let theme = cx.theme(); @@ -81,6 +84,7 @@ pub struct StoryContainer { width: Option, height: Option, story: Option, + closeable: bool, } impl FocusableView for StoryContainer { @@ -100,6 +104,7 @@ impl StoryContainer { pub fn new( name: impl Into, description: impl Into, + closeable: bool, cx: &mut WindowContext, ) -> Self { let focus_handle = cx.focus_handle(); @@ -111,14 +116,16 @@ impl StoryContainer { width: None, height: None, story: None, + closeable, } } - pub fn add_pane( + pub fn add_panel( name: impl Into, description: impl Into, story: AnyView, tab_panel: View, + closeable: bool, cx: &mut WindowContext, ) -> Task>> { let name = name.into(); @@ -126,7 +133,8 @@ impl StoryContainer { cx.spawn(|mut cx| async move { tab_panel.update(&mut cx, |panel, cx| { - let view = cx.new_view(|cx| Self::new(name, description, cx).story(story)); + let view = + cx.new_view(|cx| Self::new(name, description, closeable, cx).story(story)); panel.add_panel(Arc::new(view.clone()), cx); view }) @@ -153,6 +161,14 @@ impl Panel for StoryContainer { fn title(&self, _cx: &WindowContext) -> SharedString { self.name.clone() } + + fn closeable(&self, _cx: &WindowContext) -> bool { + self.closeable + } + + fn popup_menu(&self, menu: PopupMenu, _cx: &WindowContext) -> PopupMenu { + menu.menu("Panel Info", Box::new(PanelInfo)) + } } impl EventEmitter for StoryContainer {} diff --git a/crates/ui/src/dock/panel.rs b/crates/ui/src/dock/panel.rs index 95ea1669..727e01ab 100644 --- a/crates/ui/src/dock/panel.rs +++ b/crates/ui/src/dock/panel.rs @@ -1,6 +1,8 @@ use gpui::{AnyView, EventEmitter, FocusableView, SharedString, View, WindowContext}; use rust_i18n::t; +use crate::popup_menu::PopupMenu; + use super::PanelEvent; pub trait Panel: EventEmitter + FocusableView { @@ -13,6 +15,11 @@ pub trait Panel: EventEmitter + FocusableView { fn closeable(&self, _cx: &WindowContext) -> bool { true } + + /// The addition popup menu of the panel, default is `None`. + fn popup_menu(&self, this: PopupMenu, _cx: &WindowContext) -> PopupMenu { + this + } } pub trait PanelView: 'static + Send + Sync { @@ -21,6 +28,10 @@ pub trait PanelView: 'static + Send + Sync { t!("Dock.Unnamed").into() } + fn closeable(&self, cx: &WindowContext) -> bool; + + fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu; + fn view(&self) -> AnyView; } @@ -29,6 +40,14 @@ impl PanelView for View { self.read(cx).title(cx) } + fn closeable(&self, cx: &WindowContext) -> bool { + self.read(cx).closeable(cx) + } + + fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu { + self.read(cx).popup_menu(menu, cx) + } + fn view(&self) -> AnyView { self.clone().into() } diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index bc38e968..f80ba292 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -4,14 +4,14 @@ use gpui::{ div, prelude::FluentBuilder, rems, AnchorCorner, AppContext, DefiniteLength, DismissEvent, DragMoveEvent, Empty, EventEmitter, FocusHandle, FocusableView, InteractiveElement as _, IntoElement, ParentElement, Render, ScrollHandle, StatefulInteractiveElement, Styled, View, - ViewContext, VisualContext as _, WeakView, + ViewContext, VisualContext as _, WeakView, WindowContext, }; use rust_i18n::t; use crate::{ button::Button, h_flex, - popup_menu::PopupMenuExt, + popup_menu::{PopupMenu, PopupMenuExt}, tab::{Tab, TabBar}, theme::ActiveTheme, tooltip::Tooltip, @@ -164,6 +164,9 @@ impl TabPanel { fn render_menu_button(&self, cx: &mut ViewContext) -> impl IntoElement { let is_zoomed = self.is_zoomed; + let closeable = self.closeable(cx); + let view = cx.view().clone(); + let build_popup_menu = move |this, cx: &WindowContext| view.read(cx).popup_menu(this, cx); h_flex() .gap_2() @@ -186,17 +189,20 @@ impl TabPanel { .icon(IconName::Ellipsis) .xsmall() .ghost() - .popup_menu(move |this, _| { - this.menu( - if is_zoomed { - t!("Dock.Zoom Out") - } else { - t!("Dock.Zoom In") - }, - Box::new(ToggleZoom), - ) - .separator() - .menu(t!("Dock.Close"), Box::new(ClosePanel)) + .popup_menu(move |this, cx| { + build_popup_menu(this, cx) + .menu( + if is_zoomed { + t!("Dock.Zoom Out") + } else { + t!("Dock.Zoom In") + }, + Box::new(ToggleZoom), + ) + .when(closeable, |this| { + this.separator() + .menu(t!("Dock.Close"), Box::new(ClosePanel)) + }) }) .anchor(AnchorCorner::TopRight), ) @@ -498,7 +504,21 @@ impl TabPanel { } } -impl Panel for TabPanel {} +impl Panel for TabPanel { + fn closeable(&self, cx: &WindowContext) -> bool { + self.active_panel() + .map(|panel| panel.closeable(cx)) + .unwrap_or(false) + } + + fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu { + if let Some(panel) = self.active_panel() { + panel.popup_menu(menu, cx) + } else { + menu + } + } +} impl FocusableView for TabPanel { fn focus_handle(&self, _cx: &AppContext) -> gpui::FocusHandle { // FIXME: Delegate to the active panel