Update register_panel to use trait object Fn. (#229)
This commit is contained in:
parent
d6d00f59a2
commit
840a54850a
9 changed files with 272 additions and 52 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,3 @@
|
||||||
/target
|
/target
|
||||||
.DS_Store
|
.DS_Store
|
||||||
layout.json
|
/layout.json
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ impl StoryWorkspace {
|
||||||
|
|
||||||
fn save_state(state: &DockItemState) -> Result<()> {
|
fn save_state(state: &DockItemState) -> Result<()> {
|
||||||
println!("Save layout...");
|
println!("Save layout...");
|
||||||
let json = serde_json::to_string(state)?;
|
let json = serde_json::to_string_pretty(state)?;
|
||||||
std::fs::write("layout.json", json)?;
|
std::fs::write("layout.json", json)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
|
|
||||||
register_panel(cx, "StoryContainer", |_, info, cx| {
|
register_panel(cx, "StoryContainer", |_, info, cx| {
|
||||||
let story_state = match info {
|
let story_state = match info {
|
||||||
DockItemInfo::Custom(value) => StoryState::from_value(value),
|
DockItemInfo::Panel(value) => StoryState::from_value(value),
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!("Invalid DockItemInfo: {:?}", info)
|
unreachable!("Invalid DockItemInfo: {:?}", info)
|
||||||
}
|
}
|
||||||
|
|
@ -278,7 +278,7 @@ impl Panel for StoryContainer {
|
||||||
let story_state = StoryState {
|
let story_state = StoryState {
|
||||||
story_klass: self.story_klass.clone().unwrap(),
|
story_klass: self.story_klass.clone().unwrap(),
|
||||||
};
|
};
|
||||||
state.info = DockItemInfo::custom(story_state.to_value());
|
state.info = DockItemInfo::panel(story_state.to_value());
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
crates/ui/src/dock/invalid_panel.rs
Normal file
49
crates/ui/src/dock/invalid_panel.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use gpui::{
|
||||||
|
AppContext, EventEmitter, FocusHandle, FocusableView, ParentElement as _, Render, SharedString,
|
||||||
|
Styled as _, WindowContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::theme::ActiveTheme as _;
|
||||||
|
|
||||||
|
use super::{Panel, PanelEvent};
|
||||||
|
|
||||||
|
pub(crate) struct InvalidPanel {
|
||||||
|
name: SharedString,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InvalidPanel {
|
||||||
|
pub(crate) fn new(name: &str, cx: &mut WindowContext) -> Self {
|
||||||
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
name: SharedString::from(name.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Panel for InvalidPanel {
|
||||||
|
fn panel_name(&self) -> &'static str {
|
||||||
|
"InvalidPanel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl EventEmitter<PanelEvent> for InvalidPanel {}
|
||||||
|
impl FocusableView for InvalidPanel {
|
||||||
|
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Render for InvalidPanel {
|
||||||
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||||
|
gpui::div()
|
||||||
|
.size_full()
|
||||||
|
.my_6()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.text_color(cx.theme().muted_foreground)
|
||||||
|
.child(format!(
|
||||||
|
"The `{}` panel type is not registed in PanelRegistry.",
|
||||||
|
self.name.clone()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod invalid_panel;
|
||||||
mod panel;
|
mod panel;
|
||||||
mod stack_panel;
|
mod stack_panel;
|
||||||
mod tab_panel;
|
mod tab_panel;
|
||||||
|
|
@ -14,8 +15,7 @@ pub use stack_panel::*;
|
||||||
pub use tab_panel::*;
|
pub use tab_panel::*;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
stack_panel::init(cx);
|
cx.set_global(PanelRegistry::new());
|
||||||
tab_panel::init(cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(dock, [ToggleZoom, ClosePanel]);
|
actions!(dock, [ToggleZoom, ClosePanel]);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::popup_menu::PopupMenu;
|
use crate::popup_menu::PopupMenu;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyView, AppContext, Axis, EventEmitter, FocusableView, Global, Hsla, Pixels, SharedString,
|
AnyView, AppContext, Axis, EventEmitter, FocusableView, Global, Hsla, Pixels, SharedString,
|
||||||
View, WeakView, WindowContext,
|
View, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{DockArea, DockItem};
|
use super::{invalid_panel::InvalidPanel, DockArea, DockItem};
|
||||||
|
|
||||||
pub enum PanelEvent {
|
pub enum PanelEvent {
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
|
|
@ -130,8 +130,8 @@ pub enum DockItemInfo {
|
||||||
},
|
},
|
||||||
#[serde(rename = "tabs")]
|
#[serde(rename = "tabs")]
|
||||||
Tabs { active_index: usize },
|
Tabs { active_index: usize },
|
||||||
#[serde(rename = "custom")]
|
#[serde(rename = "panel")]
|
||||||
Custom(serde_json::Value),
|
Panel(serde_json::Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DockItemInfo {
|
impl DockItemInfo {
|
||||||
|
|
@ -146,8 +146,8 @@ impl DockItemInfo {
|
||||||
Self::Tabs { active_index }
|
Self::Tabs { active_index }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn custom(value: serde_json::Value) -> Self {
|
pub fn panel(value: serde_json::Value) -> Self {
|
||||||
Self::Custom(value)
|
Self::Panel(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn axis(&self) -> Option<Axis> {
|
pub fn axis(&self) -> Option<Axis> {
|
||||||
|
|
@ -176,12 +176,21 @@ impl DockItemInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for DockItemState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
panel_name: "".to_string(),
|
||||||
|
children: Vec::new(),
|
||||||
|
info: DockItemInfo::Panel(serde_json::Value::Null),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DockItemState {
|
impl DockItemState {
|
||||||
pub fn new(panel_name: &str) -> Self {
|
pub fn new(panel_name: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
panel_name: panel_name.to_string(),
|
panel_name: panel_name.to_string(),
|
||||||
children: Vec::new(),
|
..Default::default()
|
||||||
info: DockItemInfo::Tabs { active_index: 0 },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,16 +202,6 @@ impl DockItemState {
|
||||||
// TODO: Use the empty panel if the panel is not registered, for the compatibility.
|
// TODO: Use the empty panel if the panel is not registered, for the compatibility.
|
||||||
|
|
||||||
let info = self.info.clone();
|
let info = self.info.clone();
|
||||||
let f = *cx
|
|
||||||
.global::<PanelRegistry>()
|
|
||||||
.items
|
|
||||||
.get(&self.panel_name)
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
panic!(
|
|
||||||
"The {} panel type is not registed in PanelRegistry.",
|
|
||||||
self.panel_name
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let items: Vec<DockItem> = self
|
let items: Vec<DockItem> = self
|
||||||
.children
|
.children
|
||||||
|
|
@ -237,8 +236,19 @@ impl DockItemState {
|
||||||
|
|
||||||
DockItem::tabs(items, Some(active_index), &dock_area, cx)
|
DockItem::tabs(items, Some(active_index), &dock_area, cx)
|
||||||
}
|
}
|
||||||
DockItemInfo::Custom(_) => {
|
DockItemInfo::Panel(_) => {
|
||||||
let view = f(dock_area.clone(), info.clone(), cx);
|
let view = if let Some(f) = cx
|
||||||
|
.global::<PanelRegistry>()
|
||||||
|
.items
|
||||||
|
.get(&self.panel_name)
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
f(dock_area.clone(), info.clone(), cx)
|
||||||
|
} else {
|
||||||
|
// Show an invalid panel if the panel is not registered.
|
||||||
|
Box::new(cx.new_view(|cx| InvalidPanel::new(&self.panel_name, cx)))
|
||||||
|
};
|
||||||
|
|
||||||
DockItem::tabs(vec![view.into()], None, &dock_area, cx)
|
DockItem::tabs(vec![view.into()], None, &dock_area, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -248,7 +258,7 @@ impl DockItemState {
|
||||||
pub struct PanelRegistry {
|
pub struct PanelRegistry {
|
||||||
items: HashMap<
|
items: HashMap<
|
||||||
String,
|
String,
|
||||||
fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView>,
|
Arc<dyn Fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView>>,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
impl PanelRegistry {
|
impl PanelRegistry {
|
||||||
|
|
@ -261,16 +271,32 @@ impl PanelRegistry {
|
||||||
impl Global for PanelRegistry {}
|
impl Global for PanelRegistry {}
|
||||||
|
|
||||||
/// Register the Panel init by panel_name to global registry.
|
/// Register the Panel init by panel_name to global registry.
|
||||||
pub fn register_panel(
|
pub fn register_panel<F>(cx: &mut AppContext, panel_name: &str, deserialize: F)
|
||||||
cx: &mut AppContext,
|
where
|
||||||
panel_name: &str,
|
F: Fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView> + 'static,
|
||||||
deserialize: fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView>,
|
{
|
||||||
) {
|
|
||||||
if let None = cx.try_global::<PanelRegistry>() {
|
if let None = cx.try_global::<PanelRegistry>() {
|
||||||
cx.set_global(PanelRegistry::new());
|
cx.set_global(PanelRegistry::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.global_mut::<PanelRegistry>()
|
cx.global_mut::<PanelRegistry>()
|
||||||
.items
|
.items
|
||||||
.insert(panel_name.to_string(), deserialize);
|
.insert(panel_name.to_string(), Arc::new(deserialize));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_item_state() {
|
||||||
|
let json = include_str!("../../tests/fixtures/layout.json");
|
||||||
|
let state: DockItemState = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(state.panel_name, "StackPanel");
|
||||||
|
assert_eq!(state.children.len(), 3);
|
||||||
|
assert_eq!(state.children[0].panel_name, "StackPanel");
|
||||||
|
assert_eq!(state.children[1].children.len(), 2);
|
||||||
|
assert_eq!(state.children[1].children[0].panel_name, "TabPanel");
|
||||||
|
assert_eq!(state.children[1].panel_name, "StackPanel");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
Placement,
|
Placement,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{register_panel, DockArea, DockItemState, Panel, PanelEvent, PanelView, TabPanel};
|
use super::{DockArea, DockItemState, Panel, PanelEvent, PanelView, TabPanel};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
prelude::FluentBuilder as _, AppContext, Axis, DismissEvent, EventEmitter, FocusHandle,
|
prelude::FluentBuilder as _, AppContext, Axis, DismissEvent, EventEmitter, FocusHandle,
|
||||||
FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, View, ViewContext,
|
FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, View, ViewContext,
|
||||||
|
|
@ -19,14 +19,6 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
|
||||||
register_panel(cx, "StackPanel", |_, info, cx| {
|
|
||||||
let axis = info.axis().unwrap_or(Axis::Horizontal);
|
|
||||||
let view = cx.new_view(|cx| StackPanel::new(axis, cx));
|
|
||||||
Box::new(view)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct StackPanel {
|
pub struct StackPanel {
|
||||||
pub(super) parent: Option<View<StackPanel>>,
|
pub(super) parent: Option<View<StackPanel>>,
|
||||||
pub(super) axis: Axis,
|
pub(super) axis: Axis,
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
register_panel, ClosePanel, DockArea, DockItemState, Panel, PanelEvent, PanelView, StackPanel,
|
ClosePanel, DockArea, DockItemState, Panel, PanelEvent, PanelView, StackPanel, ToggleZoom,
|
||||||
ToggleZoom,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
|
||||||
register_panel(cx, "TabPanel", |dock_area, _, cx| {
|
|
||||||
let view = cx.new_view(|cx| TabPanel::new(None, dock_area, cx));
|
|
||||||
Box::new(view)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct DragPanel {
|
pub(crate) struct DragPanel {
|
||||||
pub(crate) panel: Arc<dyn PanelView>,
|
pub(crate) panel: Arc<dyn PanelView>,
|
||||||
|
|
|
||||||
161
crates/ui/tests/fixtures/layout.json
vendored
Normal file
161
crates/ui/tests/fixtures/layout.json
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
{
|
||||||
|
"panel_name": "StackPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StackPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "IconStory" } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "tabs": { "active_index": 0 } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "CalendarStory" } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "tabs": { "active_index": 0 } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "stack": { "sizes": [584.0, 583.0], "axis": 1 } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StackPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "ButtonStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "InputStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "DropdownStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "ModalStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "PopupStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "ListStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "SwitchStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "ProgressStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "TableStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "ImageStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "ResizableStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "ScrollableStory" } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "tabs": { "active_index": 0 } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "ProgressStory" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "TextStory" } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "tabs": { "active_index": 0 } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "stack": { "sizes": [584.0, 583.0], "axis": 1 } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StackPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "TooltipStory" } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "tabs": { "active_index": 0 } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "CalendarStory" } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "tabs": { "active_index": 0 } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": { "panel": { "story_klass": "ImageStory" } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "tabs": { "active_index": 0 } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "stack": { "sizes": [434.0, 433.0, 300.0], "axis": 1 } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": { "stack": { "sizes": [300.0, 950.0, 350.0], "axis": 0 } }
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue