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
|
||||
.DS_Store
|
||||
layout.json
|
||||
/layout.json
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ impl StoryWorkspace {
|
|||
|
||||
fn save_state(state: &DockItemState) -> Result<()> {
|
||||
println!("Save layout...");
|
||||
let json = serde_json::to_string(state)?;
|
||||
let json = serde_json::to_string_pretty(state)?;
|
||||
std::fs::write("layout.json", json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ pub fn init(cx: &mut AppContext) {
|
|||
|
||||
register_panel(cx, "StoryContainer", |_, info, cx| {
|
||||
let story_state = match info {
|
||||
DockItemInfo::Custom(value) => StoryState::from_value(value),
|
||||
DockItemInfo::Panel(value) => StoryState::from_value(value),
|
||||
_ => {
|
||||
unreachable!("Invalid DockItemInfo: {:?}", info)
|
||||
}
|
||||
|
|
@ -278,7 +278,7 @@ impl Panel for StoryContainer {
|
|||
let story_state = StoryState {
|
||||
story_klass: self.story_klass.clone().unwrap(),
|
||||
};
|
||||
state.info = DockItemInfo::custom(story_state.to_value());
|
||||
state.info = DockItemInfo::panel(story_state.to_value());
|
||||
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 stack_panel;
|
||||
mod tab_panel;
|
||||
|
|
@ -14,8 +15,7 @@ pub use stack_panel::*;
|
|||
pub use tab_panel::*;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
stack_panel::init(cx);
|
||||
tab_panel::init(cx);
|
||||
cx.set_global(PanelRegistry::new());
|
||||
}
|
||||
|
||||
actions!(dock, [ToggleZoom, ClosePanel]);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::popup_menu::PopupMenu;
|
||||
use gpui::{
|
||||
AnyView, AppContext, Axis, EventEmitter, FocusableView, Global, Hsla, Pixels, SharedString,
|
||||
View, WeakView, WindowContext,
|
||||
View, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rust_i18n::t;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{DockArea, DockItem};
|
||||
use super::{invalid_panel::InvalidPanel, DockArea, DockItem};
|
||||
|
||||
pub enum PanelEvent {
|
||||
ZoomIn,
|
||||
|
|
@ -130,8 +130,8 @@ pub enum DockItemInfo {
|
|||
},
|
||||
#[serde(rename = "tabs")]
|
||||
Tabs { active_index: usize },
|
||||
#[serde(rename = "custom")]
|
||||
Custom(serde_json::Value),
|
||||
#[serde(rename = "panel")]
|
||||
Panel(serde_json::Value),
|
||||
}
|
||||
|
||||
impl DockItemInfo {
|
||||
|
|
@ -146,8 +146,8 @@ impl DockItemInfo {
|
|||
Self::Tabs { active_index }
|
||||
}
|
||||
|
||||
pub fn custom(value: serde_json::Value) -> Self {
|
||||
Self::Custom(value)
|
||||
pub fn panel(value: serde_json::Value) -> Self {
|
||||
Self::Panel(value)
|
||||
}
|
||||
|
||||
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 {
|
||||
pub fn new(panel_name: &str) -> Self {
|
||||
Self {
|
||||
panel_name: panel_name.to_string(),
|
||||
children: Vec::new(),
|
||||
info: DockItemInfo::Tabs { active_index: 0 },
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,16 +202,6 @@ impl DockItemState {
|
|||
// TODO: Use the empty panel if the panel is not registered, for the compatibility.
|
||||
|
||||
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
|
||||
.children
|
||||
|
|
@ -237,8 +236,19 @@ impl DockItemState {
|
|||
|
||||
DockItem::tabs(items, Some(active_index), &dock_area, cx)
|
||||
}
|
||||
DockItemInfo::Custom(_) => {
|
||||
let view = f(dock_area.clone(), info.clone(), cx);
|
||||
DockItemInfo::Panel(_) => {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -248,7 +258,7 @@ impl DockItemState {
|
|||
pub struct PanelRegistry {
|
||||
items: HashMap<
|
||||
String,
|
||||
fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView>,
|
||||
Arc<dyn Fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView>>,
|
||||
>,
|
||||
}
|
||||
impl PanelRegistry {
|
||||
|
|
@ -261,16 +271,32 @@ impl PanelRegistry {
|
|||
impl Global for PanelRegistry {}
|
||||
|
||||
/// Register the Panel init by panel_name to global registry.
|
||||
pub fn register_panel(
|
||||
cx: &mut AppContext,
|
||||
panel_name: &str,
|
||||
deserialize: fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView>,
|
||||
) {
|
||||
pub fn register_panel<F>(cx: &mut AppContext, panel_name: &str, deserialize: F)
|
||||
where
|
||||
F: Fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView> + 'static,
|
||||
{
|
||||
if let None = cx.try_global::<PanelRegistry>() {
|
||||
cx.set_global(PanelRegistry::new());
|
||||
}
|
||||
|
||||
cx.global_mut::<PanelRegistry>()
|
||||
.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,
|
||||
};
|
||||
|
||||
use super::{register_panel, DockArea, DockItemState, Panel, PanelEvent, PanelView, TabPanel};
|
||||
use super::{DockArea, DockItemState, Panel, PanelEvent, PanelView, TabPanel};
|
||||
use gpui::{
|
||||
prelude::FluentBuilder as _, AppContext, Axis, DismissEvent, EventEmitter, FocusHandle,
|
||||
FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, View, ViewContext,
|
||||
|
|
@ -19,14 +19,6 @@ use gpui::{
|
|||
};
|
||||
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(super) parent: Option<View<StackPanel>>,
|
||||
pub(super) axis: Axis,
|
||||
|
|
|
|||
|
|
@ -20,17 +20,9 @@ use crate::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
register_panel, ClosePanel, DockArea, DockItemState, Panel, PanelEvent, PanelView, StackPanel,
|
||||
ToggleZoom,
|
||||
ClosePanel, DockArea, DockItemState, Panel, PanelEvent, PanelView, StackPanel, 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)]
|
||||
pub(crate) struct DragPanel {
|
||||
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