diff --git a/crates/app/src/story_workspace.rs b/crates/app/src/story_workspace.rs index caad337b..e3b1557e 100644 --- a/crates/app/src/story_workspace.rs +++ b/crates/app/src/story_workspace.rs @@ -21,6 +21,11 @@ use workspace::TitleBar; use crate::app_state::AppState; +const MAIN_DOCK_AREA: DockAreaTab = DockAreaTab { + id: "main-dock", + version: 3, +}; + #[derive(Clone, PartialEq, Eq, Deserialize)] struct SelectLocale(SharedString); @@ -43,6 +48,11 @@ pub struct StoryWorkspace { _save_layout_task: Option>, } +struct DockAreaTab { + id: &'static str, + version: usize, +} + impl StoryWorkspace { pub fn new(_app_state: Arc, cx: &mut ViewContext) -> Self { cx.observe_window_appearance(|_, cx| { @@ -50,7 +60,8 @@ impl StoryWorkspace { }) .detach(); - let dock_area = cx.new_view(|cx| DockArea::new("main-dock", cx)); + let dock_area = + cx.new_view(|cx| DockArea::new(MAIN_DOCK_AREA.id, Some(MAIN_DOCK_AREA.version), cx)); let weak_dock_area = dock_area.downgrade(); match Self::load_layout(dock_area.clone(), cx) { @@ -59,25 +70,7 @@ impl StoryWorkspace { } Err(err) => { eprintln!("load layout error: {:?}", err); - let dock_item = Self::init_default_layout(&weak_dock_area, cx); - - let left_panels: Vec> = - vec![Arc::new(StoryContainer::panel::(cx))]; - - let bottom_panels: Vec> = vec![ - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - ]; - - let right_panels: Vec> = - vec![Arc::new(StoryContainer::panel::(cx))]; - - _ = dock_area.update(cx, |view, cx| { - view.set_root(dock_item, cx); - view.set_left_dock(left_panels, Some(px(350.)), cx); - view.set_bottom_dock(bottom_panels, Some(px(200.)), cx); - view.set_right_dock(right_panels, Some(px(320.)), cx); - }); + Self::reset_default_layout(weak_dock_area, cx); } }; @@ -166,6 +159,23 @@ impl StoryWorkspace { let json = std::fs::read_to_string(fname)?; let state = serde_json::from_str::(&json)?; + // Check if the saved layout version is different from the current version + // Notify the user and ask if they want to reset the layout to default. + if state.version != Some(MAIN_DOCK_AREA.version) { + let answer = cx.prompt(PromptLevel::Info, "The default main layout has been updated.\nDo you want to reset the layout to default?", None, + &["Yes", "No"]); + + let weak_dock_area = dock_area.downgrade(); + cx.spawn(|mut cx| async move { + if answer.await == Ok(0) { + _ = cx.update(|cx| { + Self::reset_default_layout(weak_dock_area, cx); + }); + } + }) + .detach(); + } + dock_area.update(cx, |dock_area, cx| { dock_area.load(state, cx).context("load layout")?; @@ -173,6 +183,30 @@ impl StoryWorkspace { }) } + fn reset_default_layout(dock_area: WeakView, cx: &mut WindowContext) { + let dock_item = Self::init_default_layout(&dock_area, cx); + let left_panels: Vec> = + vec![Arc::new(StoryContainer::panel::(cx))]; + + let bottom_panels: Vec> = vec![ + Arc::new(StoryContainer::panel::(cx)), + Arc::new(StoryContainer::panel::(cx)), + ]; + + let right_panels: Vec> = + vec![Arc::new(StoryContainer::panel::(cx))]; + + _ = dock_area.update(cx, |view, cx| { + view.set_version(MAIN_DOCK_AREA.version, cx); + view.set_root(dock_item, cx); + view.set_left_dock(left_panels, Some(px(350.)), cx); + view.set_bottom_dock(bottom_panels, Some(px(200.)), cx); + view.set_right_dock(right_panels, Some(px(320.)), cx); + + Self::save_state(&view.dump(cx)).unwrap(); + }); + } + fn init_default_layout(dock_area: &WeakView, cx: &mut WindowContext) -> DockItem { DockItem::split_with_sizes( Axis::Vertical, @@ -180,8 +214,8 @@ impl StoryWorkspace { vec![ Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), + Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), @@ -191,10 +225,10 @@ impl StoryWorkspace { Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), + Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), ], None, &dock_area, diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index edf99d77..0253f695 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -52,14 +52,16 @@ use ui::{ v_flex, ContextModal, }; +const PANEL_NAME: &str = "StoryContainer"; + pub fn init(cx: &mut AppContext) { input_story::init(cx); dropdown_story::init(cx); popup_story::init(cx); - register_panel(cx, "StoryContainer", |_, info, cx| { + register_panel(cx, PANEL_NAME, |_, _, info, cx| { let story_state = match info { - DockItemInfo::Panel(value) => StoryState::from_value(value), + DockItemInfo::Panel(value) => StoryState::from_value(value.clone()), _ => { unreachable!("Invalid DockItemInfo: {:?}", info) } @@ -292,7 +294,7 @@ impl Panel for StoryContainer { } fn dump(&self, _cx: &AppContext) -> DockItemState { - let mut state = DockItemState::new(self.panel_name()); + let mut state = DockItemState::new(self); let story_state = StoryState { story_klass: self.story_klass.clone().unwrap(), }; diff --git a/crates/ui/src/dock/invalid_panel.rs b/crates/ui/src/dock/invalid_panel.rs index 0997dd13..2c922a2c 100644 --- a/crates/ui/src/dock/invalid_panel.rs +++ b/crates/ui/src/dock/invalid_panel.rs @@ -28,7 +28,7 @@ impl Panel for InvalidPanel { } fn dump(&self, _cx: &AppContext) -> super::DockItemState { - let mut state = DockItemState::new(&self.name); + let mut state = DockItemState::new(self); state.info = self.info.clone(); state } diff --git a/crates/ui/src/dock/mod.rs b/crates/ui/src/dock/mod.rs index 7d374543..5f378026 100644 --- a/crates/ui/src/dock/mod.rs +++ b/crates/ui/src/dock/mod.rs @@ -35,6 +35,8 @@ pub enum DockEvent { /// The main area of the dock. pub struct DockArea { id: SharedString, + /// The version is used to special the default layout, this is like the `panel_version` in `trait Panel`. + version: Option, pub(crate) bounds: Bounds, /// The center view of the dockarea. @@ -181,7 +183,11 @@ impl DockItem { } impl DockArea { - pub fn new(id: impl Into, cx: &mut ViewContext) -> Self { + pub fn new( + id: impl Into, + version: Option, + cx: &mut ViewContext, + ) -> Self { let stack_panel = cx.new_view(|cx| StackPanel::new(Axis::Horizontal, cx)); let dock_item = DockItem::Split { axis: Axis::Horizontal, @@ -192,6 +198,7 @@ impl DockArea { Self { id: id.into(), + version, bounds: Bounds::default(), items: dock_item, zoom_view: None, @@ -201,6 +208,12 @@ impl DockArea { } } + /// Set version of the dock area. + pub fn set_version(&mut self, version: usize, cx: &mut ViewContext) { + self.version = Some(version); + cx.notify(); + } + /// The the DockItem as the root of the dock area. /// /// This is used to render at the Center of the DockArea. @@ -299,6 +312,7 @@ impl DockArea { /// /// See also [DockeArea::dump]. pub fn load(&mut self, state: DockAreaState, cx: &mut ViewContext) -> Result<()> { + self.version = state.version; let weak_self = cx.view().downgrade(); if let Some(left_dock) = state.left_dock { @@ -348,6 +362,7 @@ impl DockArea { .map(|dock| DockState::new(dock.clone(), cx)); DockAreaState { + version: self.version, center, left_dock, right_dock, diff --git a/crates/ui/src/dock/panel.rs b/crates/ui/src/dock/panel.rs index 64fc6671..73a2e12c 100644 --- a/crates/ui/src/dock/panel.rs +++ b/crates/ui/src/dock/panel.rs @@ -60,26 +60,20 @@ pub trait Panel: EventEmitter + FocusableView { /// Dump the panel, used to serialize the panel. fn dump(&self, _cx: &AppContext) -> DockItemState { - DockItemState::new(self.panel_name()) + DockItemState::new(self) } } pub trait PanelView: 'static + Send + Sync { fn panel_name(&self, _cx: &WindowContext) -> &'static str; fn title(&self, _cx: &WindowContext) -> AnyElement; - fn title_style(&self, _cx: &WindowContext) -> Option; - fn closeable(&self, cx: &WindowContext) -> bool; fn zoomable(&self, cx: &WindowContext) -> bool; fn collapsible(&self, cx: &WindowContext) -> bool; - fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu; - fn view(&self) -> AnyView; - fn focus_handle(&self, cx: &AppContext) -> FocusHandle; - fn dump(&self, cx: &AppContext) -> DockItemState; } @@ -87,6 +81,7 @@ impl PanelView for View { fn panel_name(&self, cx: &WindowContext) -> &'static str { self.read(cx).panel_name() } + fn title(&self, cx: &WindowContext) -> AnyElement { self.read(cx).title(cx) } @@ -145,7 +140,14 @@ impl PartialEq for dyn PanelView { pub struct PanelRegistry { pub(super) items: HashMap< String, - Arc, DockItemInfo, &mut WindowContext) -> Box>, + Arc< + dyn Fn( + WeakView, + &DockItemState, + &DockItemInfo, + &mut WindowContext, + ) -> Box, + >, >, } impl PanelRegistry { @@ -160,7 +162,13 @@ 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: F) where - F: Fn(WeakView, DockItemInfo, &mut WindowContext) -> Box + 'static, + F: Fn( + WeakView, + &DockItemState, + &DockItemInfo, + &mut WindowContext, + ) -> Box + + 'static, { if let None = cx.try_global::() { cx.set_global(PanelRegistry::new()); diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index d5ca4159..2f39086a 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -38,7 +38,7 @@ impl Panel for StackPanel { fn dump(&self, cx: &AppContext) -> DockItemState { let sizes = self.panel_group.read(cx).sizes(); - let mut state = DockItemState::new(self.panel_name()); + let mut state = DockItemState::new(self); for panel in &self.panels { state.add_child(panel.dump(cx)); state.info = DockItemInfo::stack(sizes.clone(), self.axis); diff --git a/crates/ui/src/dock/state.rs b/crates/ui/src/dock/state.rs index 88970983..5f217ac7 100644 --- a/crates/ui/src/dock/state.rs +++ b/crates/ui/src/dock/state.rs @@ -4,13 +4,18 @@ use itertools::Itertools as _; use serde::{Deserialize, Serialize}; use super::{ - invalid_panel::InvalidPanel, Dock, DockArea, DockItem, DockPlacement, PanelRegistry, PanelView, - TabPanel, + invalid_panel::InvalidPanel, Dock, DockArea, DockItem, DockPlacement, Panel, PanelRegistry, + PanelView, TabPanel, }; /// Used to serialize and deserialize the DockArea #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct DockAreaState { + /// The version is used to mark this persisted state is compatible with the current version + /// For example, some times we many totally changed the structure of the Panel, + /// then we can compare the version to decide whether we can use the state or ignore. + #[serde(default)] + pub version: Option, pub center: DockItemState, pub left_dock: Option, pub right_dock: Option, @@ -138,9 +143,9 @@ impl Default for DockItemState { } impl DockItemState { - pub fn new(panel_name: &str) -> Self { + pub fn new(panel: &P) -> Self { Self { - panel_name: panel_name.to_string(), + panel_name: panel.panel_name().to_string(), ..Default::default() } } @@ -192,7 +197,7 @@ impl DockItemState { .get(&self.panel_name) .cloned() { - f(dock_area.clone(), info.clone(), cx) + f(dock_area.clone(), self, &info, cx) } else { // Show an invalid panel if the panel is not registered. Box::new( @@ -215,6 +220,7 @@ mod tests { fn test_deserialize_item_state() { let json = include_str!("../../tests/fixtures/layout.json"); let state: DockAreaState = serde_json::from_str(json).unwrap(); + assert_eq!(state.version, None); assert_eq!(state.center.panel_name, "StackPanel"); assert_eq!(state.center.children.len(), 2); assert_eq!(state.center.children[0].panel_name, "TabPanel"); diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index 39961c6c..4a9d2778 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -122,7 +122,7 @@ impl Panel for TabPanel { } fn dump(&self, cx: &AppContext) -> DockItemState { - let mut state = DockItemState::new(self.panel_name()); + let mut state = DockItemState::new(self); for panel in self.panels.iter() { state.add_child(panel.dump(cx)); state.info = DockItemInfo::tabs(self.active_ix);