dock: Add popup_menu delegate to custom panel menu. (#202)

This commit is contained in:
Jason Lee 2024-09-02 14:55:37 +08:00 committed by GitHub
parent f5cae46e42
commit af09525c1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 105 additions and 34 deletions

View file

@ -76,101 +76,112 @@ impl StoryWorkspace {
}) })
}); });
StoryContainer::add_pane( 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(), tab_panel.clone(),
false,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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(), tab_panel.clone(),
false,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"Text", "Text",
"Links, paragraphs, checkboxes, and more.", "Links, paragraphs, checkboxes, and more.",
TextStory::view(cx).into(), TextStory::view(cx).into(),
tab_panel.clone(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"Modal", "Modal",
"Modal & Drawer use examples", "Modal & Drawer use examples",
ModalStory::view(cx).into(), ModalStory::view(cx).into(),
tab_panel.clone(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"Icon", "Icon",
"Icon use examples", "Icon use examples",
IconStory::view(cx).into(), IconStory::view(cx).into(),
tab_panel.clone(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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_panel1.clone(),
true,
cx, cx,
) )
.detach(); .detach();
@ -183,47 +194,52 @@ impl StoryWorkspace {
// cx, // cx,
// ); // );
StoryContainer::add_pane( StoryContainer::add_panel(
"Table", "Table",
"Powerful table and datagrids built.", "Powerful table and datagrids built.",
TableStory::view(cx).into(), TableStory::view(cx).into(),
tab_panel.clone(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"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(), tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();
StoryContainer::add_pane( StoryContainer::add_panel(
"Calendar", "Calendar",
"A calendar component.", "A calendar component.",
CalendarStory::view(cx).into(), CalendarStory::view(cx).into(),
right_tab_panel.clone(), right_tab_panel.clone(),
true,
cx, cx,
) )
.detach(); .detach();

View file

@ -37,8 +37,8 @@ pub use tooltip_story::TooltipStory;
pub use webview_story::WebViewStory; pub use webview_story::WebViewStory;
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, AnyView, AppContext, Div, EventEmitter, FocusableView, actions, div, prelude::FluentBuilder as _, px, AnyView, AppContext, Div, EventEmitter,
InteractiveElement, IntoElement, ParentElement, Render, SharedString, FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled as _, Task, View, ViewContext, VisualContext, WindowContext, StatefulInteractiveElement, Styled as _, Task, View, ViewContext, VisualContext, WindowContext,
}; };
@ -48,6 +48,7 @@ use ui::{
dock::{Panel, PanelEvent, TabPanel}, dock::{Panel, PanelEvent, TabPanel},
h_flex, h_flex,
label::Label, label::Label,
popup_menu::PopupMenu,
v_flex, v_flex,
}; };
@ -57,6 +58,8 @@ pub fn init(cx: &mut AppContext) {
popup_story::init(cx); popup_story::init(cx);
} }
actions!(story, [PanelInfo]);
pub fn section(title: impl IntoElement, cx: &WindowContext) -> Div { pub fn section(title: impl IntoElement, cx: &WindowContext) -> Div {
use ui::theme::ActiveTheme; use ui::theme::ActiveTheme;
let theme = cx.theme(); let theme = cx.theme();
@ -81,6 +84,7 @@ pub struct StoryContainer {
width: Option<gpui::Pixels>, width: Option<gpui::Pixels>,
height: Option<gpui::Pixels>, height: Option<gpui::Pixels>,
story: Option<AnyView>, story: Option<AnyView>,
closeable: bool,
} }
impl FocusableView for StoryContainer { impl FocusableView for StoryContainer {
@ -100,6 +104,7 @@ impl StoryContainer {
pub fn new( pub fn new(
name: impl Into<SharedString>, name: impl Into<SharedString>,
description: impl Into<SharedString>, description: impl Into<SharedString>,
closeable: bool,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Self { ) -> Self {
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
@ -111,14 +116,16 @@ impl StoryContainer {
width: None, width: None,
height: None, height: None,
story: None, story: None,
closeable,
} }
} }
pub fn add_pane( 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>,
closeable: bool,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Task<Result<View<Self>>> { ) -> Task<Result<View<Self>>> {
let name = name.into(); let name = name.into();
@ -126,7 +133,8 @@ impl StoryContainer {
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
tab_panel.update(&mut cx, |panel, cx| { 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); panel.add_panel(Arc::new(view.clone()), cx);
view view
}) })
@ -153,6 +161,14 @@ impl Panel for StoryContainer {
fn title(&self, _cx: &WindowContext) -> SharedString { fn title(&self, _cx: &WindowContext) -> SharedString {
self.name.clone() 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<PanelEvent> for StoryContainer {} impl EventEmitter<PanelEvent> for StoryContainer {}

View file

@ -1,6 +1,8 @@
use gpui::{AnyView, EventEmitter, FocusableView, SharedString, View, WindowContext}; use gpui::{AnyView, EventEmitter, FocusableView, SharedString, View, WindowContext};
use rust_i18n::t; use rust_i18n::t;
use crate::popup_menu::PopupMenu;
use super::PanelEvent; use super::PanelEvent;
pub trait Panel: EventEmitter<PanelEvent> + FocusableView { pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
@ -13,6 +15,11 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
fn closeable(&self, _cx: &WindowContext) -> bool { fn closeable(&self, _cx: &WindowContext) -> bool {
true 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 { pub trait PanelView: 'static + Send + Sync {
@ -21,6 +28,10 @@ pub trait PanelView: 'static + Send + Sync {
t!("Dock.Unnamed").into() t!("Dock.Unnamed").into()
} }
fn closeable(&self, cx: &WindowContext) -> bool;
fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu;
fn view(&self) -> AnyView; fn view(&self) -> AnyView;
} }
@ -29,6 +40,14 @@ impl<T: Panel> PanelView for View<T> {
self.read(cx).title(cx) 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 { fn view(&self) -> AnyView {
self.clone().into() self.clone().into()
} }

View file

@ -4,14 +4,14 @@ 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, Render, ScrollHandle, StatefulInteractiveElement, Styled, View,
ViewContext, VisualContext as _, WeakView, ViewContext, VisualContext as _, WeakView, WindowContext,
}; };
use rust_i18n::t; use rust_i18n::t;
use crate::{ use crate::{
button::Button, button::Button,
h_flex, h_flex,
popup_menu::PopupMenuExt, popup_menu::{PopupMenu, PopupMenuExt},
tab::{Tab, TabBar}, tab::{Tab, TabBar},
theme::ActiveTheme, theme::ActiveTheme,
tooltip::Tooltip, tooltip::Tooltip,
@ -164,6 +164,9 @@ impl TabPanel {
fn render_menu_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_menu_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let is_zoomed = self.is_zoomed; 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() h_flex()
.gap_2() .gap_2()
@ -186,17 +189,20 @@ impl TabPanel {
.icon(IconName::Ellipsis) .icon(IconName::Ellipsis)
.xsmall() .xsmall()
.ghost() .ghost()
.popup_menu(move |this, _| { .popup_menu(move |this, cx| {
this.menu( build_popup_menu(this, cx)
if is_zoomed { .menu(
t!("Dock.Zoom Out") if is_zoomed {
} else { t!("Dock.Zoom Out")
t!("Dock.Zoom In") } else {
}, t!("Dock.Zoom In")
Box::new(ToggleZoom), },
) Box::new(ToggleZoom),
.separator() )
.menu(t!("Dock.Close"), Box::new(ClosePanel)) .when(closeable, |this| {
this.separator()
.menu(t!("Dock.Close"), Box::new(ClosePanel))
})
}) })
.anchor(AnchorCorner::TopRight), .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 { impl FocusableView for TabPanel {
fn focus_handle(&self, _cx: &AppContext) -> gpui::FocusHandle { fn focus_handle(&self, _cx: &AppContext) -> gpui::FocusHandle {
// FIXME: Delegate to the active panel // FIXME: Delegate to the active panel