dock: Reduce dock item member (#223)

This commit is contained in:
Jason Lee 2024-09-06 17:51:21 +08:00 committed by GitHub
parent 83aecf1293
commit 9baada4fb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 346 additions and 241 deletions

View file

@ -59,18 +59,8 @@ impl StoryWorkspace {
DockItem::split(
Axis::Vertical,
vec![
DockItem::panel(StoryContainer::panel(
"Icon",
"Icon use examples",
IconStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Calendar",
"A calendar component.",
CalendarStory::view(cx).into(),
cx,
)),
DockItem::tab(StoryContainer::panel::<IconStory>(cx), &dock_area, cx),
DockItem::tab(StoryContainer::panel::<CalendarStory>(cx), &dock_area, cx),
],
&dock_area,
cx,
@ -80,83 +70,18 @@ impl StoryWorkspace {
vec![
DockItem::tabs(
vec![
DockItem::panel(StoryContainer::panel(
"Button",
"Displays a button or a component that looks like a button.",
ButtonStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Input",
"A control that allows the user to input text.",
InputStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Dropdown",
"Displays a list of options for the user to pick from—triggered by a button.",
DropdownStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Modal",
"Modal & Drawer use examples",
ModalStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Popup",
"A popup displays content on top of the main page.",
PopupStory::view(cx).into(),
cx,
)),
DockItem::tabs(
vec![DockItem::panel(StoryContainer::panel(
"List",
"A list displays a series of items.",
ListStory::view(cx).into(),
cx,
))],
None,
&dock_area,
cx,
),
DockItem::panel(StoryContainer::panel(
"Switch",
"A control that allows the user to toggle between two states.",
SwitchStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Progress",
"Progress use examples",
ProgressStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Table",
"Table use examples",
TableStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Image",
"Image use examples",
ImageStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Resizable",
"Resizable use examples",
ResizableStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Scrollable",
"Scrollable use examples",
ScrollableStory::view(cx).into(),
cx,
)),
StoryContainer::panel::<ButtonStory>(cx),
StoryContainer::panel::<InputStory>(cx),
StoryContainer::panel::<DropdownStory>(cx),
StoryContainer::panel::<ModalStory>(cx),
StoryContainer::panel::<PopupStory>(cx),
StoryContainer::panel::<ListStory>(cx),
StoryContainer::panel::<SwitchStory>(cx),
StoryContainer::panel::<ProgressStory>(cx),
StoryContainer::panel::<TableStory>(cx),
StoryContainer::panel::<ImageStory>(cx),
StoryContainer::panel::<ResizableStory>(cx),
StoryContainer::panel::<ScrollableStory>(cx),
],
None,
&dock_area,
@ -164,48 +89,29 @@ impl StoryWorkspace {
),
DockItem::tabs(
vec![
DockItem::panel(StoryContainer::panel(
"Progress",
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
ProgressStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Text",
"Links, paragraphs, checkboxes, and more.",
TextStory::view(cx).into(),
cx,
))],
StoryContainer::panel::<ProgressStory>(cx),
StoryContainer::panel::<TextStory>(cx),
],
None,
&dock_area,
cx,
),
],
vec![None, Some(px(300.))],
vec![None, None, Some(px(300.))],
&dock_area,
cx,
),
DockItem::split_with_sizes(
Axis::Vertical,
vec![
DockItem::tab(StoryContainer::panel::<TooltipStory>(cx), &dock_area, cx),
DockItem::tab(StoryContainer::panel::<CalendarStory>(cx), &dock_area, cx),
DockItem::tab(StoryContainer::panel::<ImageStory>(cx), &dock_area, cx),
],
vec![None, None, Some(px(300.))],
&dock_area,
cx,
),
DockItem::split_with_sizes(Axis::Vertical, vec![
DockItem::panel(StoryContainer::panel(
"Tooltip",
"Displays a short message when users hover over an element.",
TooltipStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Calendar",
"A calendar component.",
CalendarStory::view(cx).into(),
cx,
)),
DockItem::panel(StoryContainer::panel(
"Image",
"Render SVG image and Chart",
ImageStory::view(cx).into(),
cx,
)),
], vec![None, None, Some(px(300.))], &dock_area, cx),
],
vec![Some(px(300.)), None, Some(px(350.))],
&dock_area,

View file

@ -37,6 +37,24 @@ impl ButtonStory {
}
}
impl super::Story for ButtonStory {
fn title() -> &'static str {
"Button"
}
fn description() -> &'static str {
"Displays a button or a component that looks like a button."
}
fn closeable() -> bool {
false
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl Render for ButtonStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let disabled = self.disabled;

View file

@ -17,6 +17,20 @@ pub struct CalendarStory {
default_range_mode_picker: View<DatePicker>,
}
impl super::Story for CalendarStory {
fn title() -> &'static str {
"Calendar"
}
fn description() -> &'static str {
"A date picker and calendar component."
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl CalendarStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::new)

View file

@ -55,6 +55,20 @@ pub struct DropdownStory {
disabled_dropdown: View<Dropdown<Vec<SharedString>>>,
}
impl super::Story for DropdownStory {
fn title() -> &'static str {
"Dropdown"
}
fn description() -> &'static str {
"Displays a list of options for the user to pick from—triggered by a button."
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl DropdownStory {
fn new(cx: &mut WindowContext) -> View<Self> {
let countries = vec![

View file

@ -18,6 +18,20 @@ impl IconStory {
}
}
impl super::Story for IconStory {
fn title() -> &'static str {
"Icon"
}
fn description() -> &'static str {
"Icon use examples"
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl Render for IconStory {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
v_flex().gap_3().child(

View file

@ -10,6 +10,16 @@ pub struct ImageStory {
inbox_img: SvgImg,
}
impl super::Story for ImageStory {
fn title() -> &'static str {
"Image"
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl ImageStory {
pub fn new(_: &WindowContext) -> Self {
let chart = charts_rs::PieChart::from_json(PIE_JSON).unwrap();

View file

@ -1,4 +1,8 @@
use gpui::{actions, div, px, AppContext, FocusHandle, InteractiveElement, IntoElement, KeyBinding, ParentElement as _, Render, SharedString, Styled, View, ViewContext, VisualContext, WindowContext};
use gpui::{
actions, div, px, AppContext, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
ParentElement as _, Render, SharedString, Styled, View, ViewContext, VisualContext,
WindowContext,
};
use crate::section;
use ui::{
@ -8,11 +12,7 @@ use ui::{
input::{InputEvent, OtpInput, TextInput},
prelude::FluentBuilder as _,
scroll::ScrollbarAxis,
v_flex,
FocusableCycle,
IconName,
Sizable,
StyledExt,
v_flex, FocusableCycle, IconName, Sizable, StyledExt,
};
actions!(input_story, [Tab, TabPrev]);
@ -44,6 +44,24 @@ pub struct InputStory {
opt_input_sized: View<OtpInput>,
}
impl super::Story for InputStory {
fn title() -> &'static str {
"Input"
}
fn description() -> &'static str {
"A control that allows the user to input text."
}
fn closeable() -> bool {
false
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl InputStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::new)

View file

@ -16,8 +16,6 @@ mod text_story;
mod tooltip_story;
mod webview_story;
use std::sync::Arc;
pub use button_story::ButtonStory;
pub use calendar_story::CalendarStory;
pub use dropdown_story::DropdownStory;
@ -38,20 +36,19 @@ pub use webview_story::WebViewStory;
use gpui::{
actions, div, prelude::FluentBuilder as _, px, AnyView, AppContext, Div, EventEmitter,
FocusableView, Hsla, InteractiveElement, IntoElement, ParentElement, Pixels, Render,
SharedString, StatefulInteractiveElement, Styled as _, View, ViewContext, VisualContext,
WindowContext,
FocusableView, Hsla, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled as _, View, ViewContext, VisualContext, WindowContext,
};
use ui::{
divider::Divider,
dock::{Panel, PanelEvent, TabPanel, TitleStyle},
dock::{Panel, PanelEvent, TitleStyle},
h_flex,
label::Label,
notification::Notification,
popup_menu::PopupMenu,
theme::ActiveTheme,
v_flex, ContextModal, Placement,
v_flex, ContextModal,
};
pub fn init(cx: &mut AppContext) {
@ -87,6 +84,7 @@ pub struct StoryContainer {
width: Option<gpui::Pixels>,
height: Option<gpui::Pixels>,
story: Option<AnyView>,
story_klass: Option<SharedString>,
closeable: bool,
}
@ -95,66 +93,59 @@ pub enum ContainerEvent {
Close,
}
pub trait Story {
fn klass() -> &'static str {
std::any::type_name::<Self>()
}
fn title() -> &'static str;
fn description() -> &'static str {
""
}
fn closeable() -> bool {
true
}
fn title_bg() -> Option<Hsla> {
None
}
fn new_view(cx: &mut WindowContext) -> AnyView;
}
impl EventEmitter<ContainerEvent> for StoryContainer {}
impl StoryContainer {
pub fn new(
name: impl Into<SharedString>,
description: impl Into<SharedString>,
closeable: bool,
cx: &mut WindowContext,
) -> Self {
pub fn new(closeable: bool, cx: &mut WindowContext) -> Self {
let focus_handle = cx.focus_handle();
Self {
focus_handle,
name: name.into(),
name: "".into(),
title_bg: None,
description: description.into(),
description: "".into(),
width: None,
height: None,
story: None,
story_klass: None,
closeable,
}
}
pub fn panel(
name: impl Into<SharedString>,
description: impl Into<SharedString>,
story: AnyView,
cx: &mut WindowContext,
) -> View<Self> {
cx.new_view(|cx| Self::new(name, description, true, cx).story(story))
}
#[allow(clippy::too_many_arguments)]
pub fn add_panel(
name: impl Into<SharedString>,
description: impl Into<SharedString>,
story: AnyView,
tab_panel: View<TabPanel>,
placement: Option<Placement>,
size: Option<Pixels>,
closeable: bool,
title_bg: Option<Hsla>,
cx: &mut WindowContext,
) {
let name = name.into();
let description = description.into();
pub fn panel<S: Story>(cx: &mut WindowContext) -> View<Self> {
let name = S::title();
let description = S::description();
let story = S::new_view(cx);
let story_klass = S::klass();
tab_panel.update(cx, |panel, cx| {
let view = cx.new_view(|cx| {
Self::new(name, description, closeable, cx)
.story(story)
.title_bg(title_bg)
});
if let Some(placement) = placement {
panel.add_panel_at(Arc::new(view.clone()), placement, size, cx);
} else {
panel.add_panel(Arc::new(view.clone()), cx);
}
view
let view = cx.new_view(|cx| {
let mut story = Self::new(S::closeable(), cx).story(story, story_klass);
story.name = name.into();
story.description = description.into();
story.title_bg = S::title_bg();
story
});
view
}
pub fn width(mut self, width: gpui::Pixels) -> Self {
@ -167,13 +158,9 @@ impl StoryContainer {
self
}
pub fn story(mut self, story: AnyView) -> Self {
pub fn story(mut self, story: AnyView, story_klass: impl Into<SharedString>) -> Self {
self.story = Some(story);
self
}
pub fn title_bg(mut self, title_bg: Option<Hsla>) -> Self {
self.title_bg = title_bg;
self.story_klass = Some(story_klass.into());
self
}

View file

@ -196,6 +196,20 @@ pub struct ListStory {
selected_company: Option<Company>,
}
impl super::Story for ListStory {
fn title() -> &'static str {
"List"
}
fn description() -> &'static str {
"A list displays a series of items."
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl ListStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::new)

View file

@ -151,6 +151,20 @@ pub struct ModalStory {
model_padding: bool,
}
impl super::Story for ModalStory {
fn title() -> &'static str {
"Modal"
}
fn description() -> &'static str {
"Modal & Drawer use examples"
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl ModalStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::new)

View file

@ -79,6 +79,20 @@ pub struct PopupStory {
window_mode: bool,
}
impl super::Story for PopupStory {
fn title() -> &'static str {
"Popup"
}
fn description() -> &'static str {
"A popup displays content on top of the main page."
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl PopupStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::new)
@ -199,9 +213,10 @@ impl Render for PopupStory {
.small(),
)
.into_any()
}).max_w(px(600.))
})
.max_w(px(600.))
})
})
}),
),
)
.child(
@ -209,7 +224,7 @@ impl Render for PopupStory {
.anchor(AnchorCorner::TopRight)
.trigger(Button::new("info-top-right", cx).label("Top Right"))
.content(|cx| {
cx.new_view(|cx|
cx.new_view(|cx| {
PopoverContent::new(cx, |cx| {
v_flex()
.gap_4()
@ -223,7 +238,8 @@ impl Render for PopupStory {
.small(),
)
.into_any()
}))
})
})
}),
),
)
@ -284,11 +300,13 @@ impl Render for PopupStory {
.w(px(300.)),
)
.content(|cx| {
cx.new_view(|cx|
cx.new_view(|cx| {
PopoverContent::new(cx, |cx| {
v_flex()
.gap_4()
.child("Hello, this is a Popover on the Bottom Right.")
.child(
"Hello, this is a Popover on the Bottom Right.",
)
.child(Divider::horizontal())
.child(
Button::new("info1", cx)
@ -297,7 +315,8 @@ impl Render for PopupStory {
.small(),
)
.into_any()
}))
})
})
}),
),
),

View file

@ -21,6 +21,16 @@ pub struct ProgressStory {
slider2_value: f32,
}
impl super::Story for ProgressStory {
fn title() -> &'static str {
"Progress"
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl ProgressStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::new)

View file

@ -13,6 +13,20 @@ pub struct ResizableStory {
group2: View<ResizablePanelGroup>,
}
impl super::Story for ResizableStory {
fn title() -> &'static str {
"Resizable"
}
fn description() -> &'static str {
"The resizable panels."
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl ResizableStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(|cx| Self::new(cx))

View file

@ -60,6 +60,20 @@ impl ScrollableStory {
}
}
impl super::Story for ScrollableStory {
fn title() -> &'static str {
"Scrollable"
}
fn description() -> &'static str {
"Add vertical or horizontal, or both scrollbars to a container."
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl Render for ScrollableStory {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
let view = cx.view().clone();

View file

@ -18,6 +18,20 @@ pub struct SwitchStory {
switch3: bool,
}
impl super::Story for SwitchStory {
fn title() -> &'static str {
"Switch"
}
fn description() -> &'static str {
"A control that allows the user to toggle between two states."
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl SwitchStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(|cx| Self::new(cx))

View file

@ -362,6 +362,20 @@ pub struct TableStory {
table: View<Table<CustomerTableDelegate>>,
}
impl super::Story for TableStory {
fn title() -> &'static str {
"Table"
}
fn description() -> &'static str {
"A complex data table with selection, sorting, column moving, and loading more."
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl TableStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::new)

View file

@ -25,6 +25,20 @@ pub struct TextStory {
masked: bool,
}
impl super::Story for TextStory {
fn title() -> &'static str {
"Text"
}
fn description() -> &'static str {
"The text render testing and examples"
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl TextStory {
pub(crate) fn new(_cx: &mut WindowContext) -> Self {
Self {

View file

@ -24,6 +24,16 @@ impl TooltipStory {
}
}
impl super::Story for TooltipStory {
fn title() -> &'static str {
"Tooltip"
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl Render for TooltipStory {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
v_flex()

View file

@ -18,6 +18,16 @@ pub struct WebViewStory {
address_input: View<TextInput>,
}
impl super::Story for WebViewStory {
fn title() -> &'static str {
"WebView"
}
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
Self::view(cx).into()
}
}
impl WebViewStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
let focus_handle = cx.focus_handle();

View file

@ -32,13 +32,10 @@ pub enum DockItem {
view: View<StackPanel>,
},
Tabs {
items: Vec<DockItem>,
items: Vec<Arc<dyn PanelView>>,
active_ix: usize,
view: View<TabPanel>,
},
Panel {
view: Arc<dyn PanelView>,
},
}
impl DockItem {
@ -68,26 +65,12 @@ impl DockItem {
let stack_panel = cx.new_view(|cx| {
let mut stack_panel = StackPanel::new(axis, cx);
for (i, item) in items.iter_mut().enumerate() {
// Always convert DockItem::Panel to DockItem::Tabs in split layout.
// This makes they can subscribe the Zoom event.
match item {
DockItem::Panel { .. } => {
*item = DockItem::tabs(vec![item.clone()], None, &dock_area, cx);
}
_ => {}
}
let view = item.view();
let size = sizes.get(i).copied().flatten();
stack_panel.add_panel(view.clone(), size, dock_area.downgrade(), cx)
}
for (i, item) in items.iter().enumerate() {
let item = match item {
DockItem::Panel { .. } => Self::tabs(vec![item.clone()], None, &dock_area, cx),
_ => item.clone(),
};
let view = item.view();
let size = sizes.get(i).copied().flatten();
stack_panel.add_panel(view.clone(), size, dock_area.downgrade(), cx)
@ -106,8 +89,30 @@ impl DockItem {
/// Create DockItem with tabs layout, items are displayed as tabs.
///
/// The `active_ix` is the index of the active tab, if `None` the first tab is active.
pub fn tabs(
items: Vec<DockItem>,
pub fn tabs<P: Panel>(
items: Vec<View<P>>,
active_ix: Option<usize>,
dock_area: &View<DockArea>,
cx: &mut WindowContext,
) -> Self {
let mut new_items: Vec<Arc<dyn PanelView>> = vec![];
for item in items.into_iter() {
let item: Arc<dyn PanelView> = Arc::new(item);
new_items.push(item)
}
Self::new_tabs(new_items, active_ix, dock_area, cx)
}
pub fn tab<P: Panel>(
item: View<P>,
dock_area: &View<DockArea>,
cx: &mut WindowContext,
) -> Self {
Self::new_tabs(vec![Arc::new(item.clone())], None, dock_area, cx)
}
fn new_tabs(
items: Vec<Arc<dyn PanelView>>,
active_ix: Option<usize>,
dock_area: &View<DockArea>,
cx: &mut WindowContext,
@ -115,10 +120,8 @@ impl DockItem {
let active_ix = active_ix.unwrap_or(0);
let tab_panel = cx.new_view(|cx| {
let mut tab_panel = TabPanel::new(None, dock_area.downgrade(), cx);
for item in items.iter() {
let view = item.view();
tab_panel.add_panel(view, cx)
tab_panel.add_panel(item.clone(), cx)
}
tab_panel
@ -131,22 +134,11 @@ impl DockItem {
}
}
/// Create DockItem with a single panel, the `view` must implement `Panel`.
pub fn panel<P>(view: View<P>) -> Self
where
P: Panel,
{
Self::Panel {
view: Arc::new(view),
}
}
/// Returns the views of the dock item.
fn view(&self) -> Arc<dyn PanelView> {
match self {
Self::Split { view, .. } => Arc::new(view.clone()),
Self::Tabs { view, .. } => Arc::new(view.clone()),
Self::Panel { view } => view.clone(),
}
}
@ -156,16 +148,7 @@ impl DockItem {
Self::Split { items, .. } => {
items.iter().find_map(|item| item.find_panel(panel.clone()))
}
Self::Tabs { items, .. } => {
items.iter().find_map(|item| item.find_panel(panel.clone()))
}
Self::Panel { view } => {
if view == &panel {
Some(view.clone())
} else {
None
}
}
Self::Tabs { items, .. } => items.iter().find(|item| *item == &panel).cloned(),
}
}
}
@ -245,11 +228,6 @@ impl DockArea {
// Because we always wrap the DockItem::Panel in a DockItem::Tabs
subscribe_zoom(view, dock_area.clone(), cx);
}
DockItem::Panel { .. } => {
// The DockItem::Panel is not need to handle the zoom events
// Because the DockItem::Panel is always wrapped in a DockItem::Tabs
// So we only need to subscribe the zoom events on the TabPanel
}
}
}
@ -272,7 +250,6 @@ impl DockArea {
match &self.items {
DockItem::Split { view, .. } => view.clone().into_any_element(),
DockItem::Tabs { view, .. } => view.clone().into_any_element(),
DockItem::Panel { view } => view.view().into_any_element(),
}
}
}