dock: Add popup_menu delegate to custom panel menu. (#202)
This commit is contained in:
parent
f5cae46e42
commit
af09525c1e
4 changed files with 105 additions and 34 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,8 +189,9 @@ 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)
|
||||||
|
.menu(
|
||||||
if is_zoomed {
|
if is_zoomed {
|
||||||
t!("Dock.Zoom Out")
|
t!("Dock.Zoom Out")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -195,9 +199,11 @@ impl TabPanel {
|
||||||
},
|
},
|
||||||
Box::new(ToggleZoom),
|
Box::new(ToggleZoom),
|
||||||
)
|
)
|
||||||
.separator()
|
.when(closeable, |this| {
|
||||||
|
this.separator()
|
||||||
.menu(t!("Dock.Close"), Box::new(ClosePanel))
|
.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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue