Update register_panel to use trait object Fn. (#229)

This commit is contained in:
Jason Lee 2024-09-09 19:19:27 +08:00 committed by GitHub
parent d6d00f59a2
commit 840a54850a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 272 additions and 52 deletions

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
/target
.DS_Store
layout.json
/layout.json

View file

@ -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(())
}

View file

@ -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
}
}

View 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()
))
}
}

View file

@ -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]);

View file

@ -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");
}
}

View file

@ -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,

View file

@ -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
View 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 } }
}