dock: Add add_panel method to add panel to DockArea. (#440)

Ref #438 

https://github.com/user-attachments/assets/93b40aed-39f3-4d16-a442-13aa7a931895
This commit is contained in:
Jason Lee 2024-11-27 11:27:04 +08:00 committed by GitHub
parent 0119c80120
commit 142d092867
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 205 additions and 8 deletions

1
Cargo.lock generated
View file

@ -2355,6 +2355,7 @@ dependencies = [
"anyhow",
"gpui",
"log",
"rand 0.8.5",
"rust-embed",
"serde",
"serde_json",

View file

@ -0,0 +1,24 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-layout-dashboard"
><rect width="7" height="9" x="3" y="3" rx="1" /><rect
width="7"
height="5"
x="14"
y="3"
rx="1"
/><rect width="7" height="9" x="14" y="12" rx="1" /><rect
width="7"
height="5"
x="3"
y="16"
rx="1"
/></svg>

After

Width:  |  Height:  |  Size: 552 B

View file

@ -12,6 +12,7 @@ ui.workspace = true
story.workspace = true
serde.workspace = true
serde_json.workspace = true
rand = "0.8"
[lints]
workspace = true

View file

@ -11,7 +11,7 @@ use story::{
use ui::{
button::{Button, ButtonStyled as _},
color_picker::{ColorPicker, ColorPickerEvent},
dock::{DockArea, DockAreaState, DockEvent, DockItem},
dock::{DockArea, DockAreaState, DockEvent, DockItem, DockPlacement},
h_flex,
popup_menu::PopupMenuExt,
theme::{ActiveTheme, Theme},
@ -31,7 +31,10 @@ struct SelectLocale(SharedString);
#[derive(Clone, PartialEq, Eq, Deserialize)]
struct SelectFont(usize);
impl_actions!(story, [SelectLocale, SelectFont]);
#[derive(Clone, PartialEq, Eq, Deserialize)]
struct AddPanel(DockPlacement);
impl_actions!(story, [SelectLocale, SelectFont, AddPanel]);
actions!(workspace, [Open, CloseWindow]);
@ -352,6 +355,35 @@ impl StoryWorkspace {
Ok(window)
})
}
fn on_action_add_panel(&mut self, action: &AddPanel, cx: &mut ViewContext<Self>) {
// Random pick up a panel to add
let panel = match rand::random::<usize>() % 18 {
0 => Arc::new(StoryContainer::panel::<ButtonStory>(cx)),
1 => Arc::new(StoryContainer::panel::<InputStory>(cx)),
2 => Arc::new(StoryContainer::panel::<DropdownStory>(cx)),
3 => Arc::new(StoryContainer::panel::<TextStory>(cx)),
4 => Arc::new(StoryContainer::panel::<ModalStory>(cx)),
5 => Arc::new(StoryContainer::panel::<PopupStory>(cx)),
6 => Arc::new(StoryContainer::panel::<SwitchStory>(cx)),
7 => Arc::new(StoryContainer::panel::<ProgressStory>(cx)),
8 => Arc::new(StoryContainer::panel::<TableStory>(cx)),
9 => Arc::new(StoryContainer::panel::<ImageStory>(cx)),
10 => Arc::new(StoryContainer::panel::<IconStory>(cx)),
11 => Arc::new(StoryContainer::panel::<TooltipStory>(cx)),
12 => Arc::new(StoryContainer::panel::<ProgressStory>(cx)),
13 => Arc::new(StoryContainer::panel::<CalendarStory>(cx)),
14 => Arc::new(StoryContainer::panel::<ResizableStory>(cx)),
15 => Arc::new(StoryContainer::panel::<ScrollableStory>(cx)),
16 => Arc::new(StoryContainer::panel::<AccordionStory>(cx)),
// 17 => Arc::new(StoryContainer::panel::<WebViewStory>(cx)),
_ => Arc::new(StoryContainer::panel::<ButtonStory>(cx)),
};
self.dock_area.update(cx, |dock_area, cx| {
dock_area.add_panel(panel, action.0, cx);
});
}
}
pub fn open_new(
@ -377,6 +409,8 @@ impl Render for StoryWorkspace {
let notifications_count = cx.notifications().len();
div()
.id("story-workspace")
.on_action(cx.listener(Self::on_action_add_panel))
.relative()
.size_full()
.flex()
@ -393,6 +427,32 @@ impl Render for StoryWorkspace {
.px_2()
.gap_2()
.child(self.theme_color_picker.clone())
.child(
Button::new("add-panel")
.icon(IconName::LayoutDashboard)
.small()
.ghost()
.popup_menu(|menu, _| {
menu.menu(
"Add Panel to Center",
Box::new(AddPanel(DockPlacement::Center)),
)
.separator()
.menu(
"Add Panel to Left",
Box::new(AddPanel(DockPlacement::Left)),
)
.menu(
"Add Panel to Right",
Box::new(AddPanel(DockPlacement::Right)),
)
.menu(
"Add Panel to Bottom",
Box::new(AddPanel(DockPlacement::Bottom)),
)
})
.anchor(AnchorCorner::TopRight),
)
.child(
Button::new("theme-mode")
.map(|this| {
@ -414,7 +474,9 @@ impl Render for StoryWorkspace {
.small()
.ghost()
.on_click(|_, cx| {
cx.open_url("https://github.com/huacnlee/gpui-component")
cx.open_url(
"https://github.com/longbridgeapp/gpui-component",
)
}),
)
.child(

View file

@ -291,7 +291,7 @@ impl Render for PopupStory {
menu.link_with_icon(
"GitHub Repository",
IconName::GitHub,
"https://github.com/huacnlee/gpui-component",
"https://github.com/longbridgeapp/gpui-component",
)
.separator()
.link("GPUI", "https://gpui.rs")

View file

@ -105,7 +105,7 @@ impl Render for TextStory {
.child(Link::new("link3").child(h_flex().gap_1().child(IconName::GitHub).child("GitHub")).on_click(cx.listener(|_, _, cx| {
cx.open_url("https://google.com")
})))
.child(div().w(px(250.)).child(Link::new("link4").child("https://github.com/huacnlee/gpui-component").href("https://github.com/huacnlee/gpui-component")))
.child(div().w(px(250.)).child(Link::new("link4").child("https://github.com/longbridgeapp/gpui-component").href("https://github.com/longbridgeapp/gpui-component")))
)
)
.child(

View file

@ -1,5 +1,7 @@
//! Dock is a fixed container that places at left, bottom, right of the Windows.
use std::sync::Arc;
use gpui::{
div, prelude::FluentBuilder as _, px, Axis, Element, InteractiveElement as _, IntoElement,
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render,
@ -14,13 +16,15 @@ use crate::{
AxisExt as _, StyledExt,
};
use super::{DockArea, DockItem, TabPanel};
use super::{DockArea, DockItem, PanelView, TabPanel};
#[derive(Clone, Render)]
struct ResizePanel;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum DockPlacement {
#[serde(rename = "center")]
Center,
#[serde(rename = "left")]
Left,
#[serde(rename = "bottom")]
@ -34,6 +38,7 @@ impl DockPlacement {
match self {
Self::Left | Self::Right => Axis::Horizontal,
Self::Bottom => Axis::Vertical,
Self::Center => unreachable!(),
}
}
@ -204,6 +209,12 @@ impl Dock {
cx.notify();
}
/// Add item to the Dock.
pub fn add_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) {
self.panel.add_panel(panel, &self.dock_area, cx);
cx.notify();
}
fn render_resize_handle(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let axis = self.placement.axis();
let neg_offset = -HANDLE_PADDING;
@ -270,6 +281,7 @@ impl Dock {
DockPlacement::Left => mouse_position.x - area_bounds.left(),
DockPlacement::Right => area_bounds.right() - mouse_position.x,
DockPlacement::Bottom => area_bounds.bottom() - mouse_position.y,
DockPlacement::Center => unreachable!(),
};
self.size = size.max(PANEL_MIN_SIZE);
@ -293,6 +305,7 @@ impl Render for Dock {
.map(|this| match self.placement {
DockPlacement::Left | DockPlacement::Right => this.h_flex().h_full().w(self.size),
DockPlacement::Bottom => this.w_full().h(self.size),
DockPlacement::Center => unreachable!(),
})
// Bottom Dock should keep the title bar, then user can click the Toggle button
.when(!self.open && self.placement.is_bottom(), |this| {

View file

@ -202,6 +202,41 @@ impl DockItem {
}
}
/// Add a panel to the dock item.
pub fn add_panel(
&mut self,
panel: Arc<dyn PanelView>,
dock_area: &WeakView<DockArea>,
cx: &mut WindowContext,
) {
match self {
Self::Tabs { view, items, .. } => {
items.push(panel.clone());
view.update(cx, |tab_panel, cx| {
tab_panel.add_panel(panel, cx);
});
}
Self::Split { view, items, .. } => {
// Iter items to add panel to the first tabs
for item in items.into_iter() {
if let DockItem::Tabs { view, .. } = item {
view.update(cx, |tab_panel, cx| {
tab_panel.add_panel(panel.clone(), cx);
});
return;
}
}
// Unable to find tabs, create new tabs
let new_item = Self::tabs(vec![panel.clone()], None, dock_area, cx);
items.push(new_item.clone());
view.update(cx, |stack_panel, cx| {
stack_panel.add_panel(new_item.view(), None, dock_area.clone(), cx);
});
}
}
}
pub fn set_collapsed(&self, collapsed: bool, cx: &mut WindowContext) {
match self {
DockItem::Tabs { view, .. } => {
@ -364,6 +399,7 @@ impl DockArea {
DockPlacement::Left => self.left_dock.is_some(),
DockPlacement::Bottom => self.bottom_dock.is_some(),
DockPlacement::Right => self.right_dock.is_some(),
DockPlacement::Center => false,
}
}
@ -385,6 +421,7 @@ impl DockArea {
.as_ref()
.map(|dock| dock.read(cx).is_open())
.unwrap_or(false),
DockPlacement::Center => false,
}
}
@ -393,7 +430,9 @@ impl DockArea {
DockPlacement::Left => &self.left_dock,
DockPlacement::Bottom => &self.bottom_dock,
DockPlacement::Right => &self.right_dock,
DockPlacement::Center => return,
};
if let Some(dock) = dock {
dock.update(cx, |view, cx| {
view.toggle_open(cx);
@ -401,6 +440,59 @@ impl DockArea {
}
}
/// Add a panel item to the dock area at the given placement.
///
/// If the left, bottom, right dock is not present, it will set the dock at the placement.
pub fn add_panel(
&mut self,
panel: Arc<dyn PanelView>,
placement: DockPlacement,
cx: &mut ViewContext<Self>,
) {
let weak_self = cx.view().downgrade();
match placement {
DockPlacement::Left => {
if let Some(dock) = self.left_dock.as_ref() {
dock.update(cx, |dock, cx| dock.add_panel(panel, cx))
} else {
self.set_left_dock(
DockItem::tabs(vec![panel], None, &weak_self, cx),
None,
true,
cx,
);
}
}
DockPlacement::Bottom => {
if let Some(dock) = self.bottom_dock.as_ref() {
dock.update(cx, |dock, cx| dock.add_panel(panel, cx))
} else {
self.set_bottom_dock(
DockItem::tabs(vec![panel], None, &weak_self, cx),
None,
true,
cx,
);
}
}
DockPlacement::Right => {
if let Some(dock) = self.right_dock.as_ref() {
dock.update(cx, |dock, cx| dock.add_panel(panel, cx))
} else {
self.set_right_dock(
DockItem::tabs(vec![panel], None, &weak_self, cx),
None,
true,
cx,
);
}
}
DockPlacement::Center => {
self.items.add_panel(panel, &cx.view().downgrade(), cx);
}
}
}
/// Load the state of the DockArea from the DockAreaState.
///
/// See also [DockeArea::dump].

View file

@ -400,6 +400,7 @@ impl TabPanel {
dock_area.bottom_dock.is_some()
&& toggle_button_panels.bottom == Some(view_entity_id)
}
DockPlacement::Center => unreachable!(),
} {
return None;
}
@ -428,6 +429,7 @@ impl TabPanel {
IconName::PanelBottomOpen
}
}
DockPlacement::Center => unreachable!(),
};
Some(

View file

@ -36,6 +36,7 @@ pub enum IconName {
HeartOff,
Inbox,
Info,
LayoutDashboard,
Loader,
LoaderCircle,
Maximize,
@ -60,10 +61,10 @@ pub enum IconName {
ThumbsDown,
ThumbsUp,
TriangleAlert,
WindowClose,
WindowMaximize,
WindowMinimize,
WindowRestore,
WindowClose,
}
impl IconName {
@ -99,6 +100,7 @@ impl IconName {
IconName::HeartOff => "icons/heart-off.svg",
IconName::Inbox => "icons/inbox.svg",
IconName::Info => "icons/info.svg",
IconName::LayoutDashboard => "icons/layout-dashboard.svg",
IconName::Loader => "icons/loader.svg",
IconName::LoaderCircle => "icons/loader-circle.svg",
IconName::Maximize => "icons/maximize.svg",
@ -123,10 +125,10 @@ impl IconName {
IconName::ThumbsDown => "icons/thumbs-down.svg",
IconName::ThumbsUp => "icons/thumbs-up.svg",
IconName::TriangleAlert => "icons/triangle-alert.svg",
IconName::WindowClose => "icons/window-close.svg",
IconName::WindowMaximize => "icons/window-maximize.svg",
IconName::WindowMinimize => "icons/window-minimize.svg",
IconName::WindowRestore => "icons/window-restore.svg",
IconName::WindowClose => "icons/window-close.svg",
}
.into()
}