Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dock: Add version to DockArea to allow us check the persisted state is need to update. #323

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 56 additions & 22 deletions crates/app/src/story_workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -43,14 +48,20 @@ pub struct StoryWorkspace {
_save_layout_task: Option<Task<()>>,
}

struct DockAreaTab {
id: &'static str,
version: usize,
}

impl StoryWorkspace {
pub fn new(_app_state: Arc<AppState>, cx: &mut ViewContext<Self>) -> Self {
cx.observe_window_appearance(|_, cx| {
Theme::sync_system_appearance(cx);
})
.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) {
Expand All @@ -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<Arc<dyn PanelView>> =
vec![Arc::new(StoryContainer::panel::<ListStory>(cx))];

let bottom_panels: Vec<Arc<dyn PanelView>> = vec![
Arc::new(StoryContainer::panel::<TooltipStory>(cx)),
Arc::new(StoryContainer::panel::<IconStory>(cx)),
];

let right_panels: Vec<Arc<dyn PanelView>> =
vec![Arc::new(StoryContainer::panel::<ImageStory>(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);
}
};

Expand Down Expand Up @@ -166,22 +159,63 @@ impl StoryWorkspace {
let json = std::fs::read_to_string(fname)?;
let state = serde_json::from_str::<DockAreaState>(&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")?;

Ok::<(), anyhow::Error>(())
})
}

fn reset_default_layout(dock_area: WeakView<DockArea>, cx: &mut WindowContext) {
let dock_item = Self::init_default_layout(&dock_area, cx);
let left_panels: Vec<Arc<dyn PanelView>> =
vec![Arc::new(StoryContainer::panel::<ListStory>(cx))];

let bottom_panels: Vec<Arc<dyn PanelView>> = vec![
Arc::new(StoryContainer::panel::<TooltipStory>(cx)),
Arc::new(StoryContainer::panel::<IconStory>(cx)),
];

let right_panels: Vec<Arc<dyn PanelView>> =
vec![Arc::new(StoryContainer::panel::<ImageStory>(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<DockArea>, cx: &mut WindowContext) -> DockItem {
DockItem::split_with_sizes(
Axis::Vertical,
vec![DockItem::tabs(
vec![
Arc::new(StoryContainer::panel::<ButtonStory>(cx)),
Arc::new(StoryContainer::panel::<InputStory>(cx)),
Arc::new(StoryContainer::panel::<TextStory>(cx)),
Arc::new(StoryContainer::panel::<DropdownStory>(cx)),
Arc::new(StoryContainer::panel::<TextStory>(cx)),
Arc::new(StoryContainer::panel::<ModalStory>(cx)),
Arc::new(StoryContainer::panel::<PopupStory>(cx)),
Arc::new(StoryContainer::panel::<SwitchStory>(cx)),
Expand All @@ -191,10 +225,10 @@ impl StoryWorkspace {
Arc::new(StoryContainer::panel::<IconStory>(cx)),
Arc::new(StoryContainer::panel::<TooltipStory>(cx)),
Arc::new(StoryContainer::panel::<ProgressStory>(cx)),
Arc::new(StoryContainer::panel::<WebViewStory>(cx)),
Arc::new(StoryContainer::panel::<CalendarStory>(cx)),
Arc::new(StoryContainer::panel::<ResizableStory>(cx)),
Arc::new(StoryContainer::panel::<ScrollableStory>(cx)),
Arc::new(StoryContainer::panel::<WebViewStory>(cx)),
],
None,
&dock_area,
Expand Down
8 changes: 5 additions & 3 deletions crates/story/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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(),
};
Expand Down
2 changes: 1 addition & 1 deletion crates/ui/src/dock/invalid_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
17 changes: 16 additions & 1 deletion crates/ui/src/dock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize>,
pub(crate) bounds: Bounds<Pixels>,

/// The center view of the dockarea.
Expand Down Expand Up @@ -181,7 +183,11 @@ impl DockItem {
}

impl DockArea {
pub fn new(id: impl Into<SharedString>, cx: &mut ViewContext<Self>) -> Self {
pub fn new(
id: impl Into<SharedString>,
version: Option<usize>,
cx: &mut ViewContext<Self>,
) -> Self {
let stack_panel = cx.new_view(|cx| StackPanel::new(Axis::Horizontal, cx));
let dock_item = DockItem::Split {
axis: Axis::Horizontal,
Expand All @@ -192,6 +198,7 @@ impl DockArea {

Self {
id: id.into(),
version,
bounds: Bounds::default(),
items: dock_item,
zoom_view: None,
Expand All @@ -201,6 +208,12 @@ impl DockArea {
}
}

/// Set version of the dock area.
pub fn set_version(&mut self, version: usize, cx: &mut ViewContext<Self>) {
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.
Expand Down Expand Up @@ -299,6 +312,7 @@ impl DockArea {
///
/// See also [DockeArea::dump].
pub fn load(&mut self, state: DockAreaState, cx: &mut ViewContext<Self>) -> Result<()> {
self.version = state.version;
let weak_self = cx.view().downgrade();

if let Some(left_dock) = state.left_dock {
Expand Down Expand Up @@ -348,6 +362,7 @@ impl DockArea {
.map(|dock| DockState::new(dock.clone(), cx));

DockAreaState {
version: self.version,
center,
left_dock,
right_dock,
Expand Down
26 changes: 17 additions & 9 deletions crates/ui/src/dock/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,33 +60,28 @@ pub trait Panel: EventEmitter<PanelEvent> + 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<TitleStyle>;

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

impl<T: Panel> PanelView for View<T> {
fn panel_name(&self, cx: &WindowContext) -> &'static str {
self.read(cx).panel_name()
}

fn title(&self, cx: &WindowContext) -> AnyElement {
self.read(cx).title(cx)
}
Expand Down Expand Up @@ -145,7 +140,14 @@ impl PartialEq for dyn PanelView {
pub struct PanelRegistry {
pub(super) items: HashMap<
String,
Arc<dyn Fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView>>,
Arc<
dyn Fn(
WeakView<DockArea>,
&DockItemState,
&DockItemInfo,
&mut WindowContext,
) -> Box<dyn PanelView>,
>,
>,
}
impl PanelRegistry {
Expand All @@ -160,7 +162,13 @@ impl Global for PanelRegistry {}
/// Register the Panel init by panel_name to global registry.
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,
F: Fn(
WeakView<DockArea>,
&DockItemState,
&DockItemInfo,
&mut WindowContext,
) -> Box<dyn PanelView>
+ 'static,
{
if let None = cx.try_global::<PanelRegistry>() {
cx.set_global(PanelRegistry::new());
Expand Down
2 changes: 1 addition & 1 deletion crates/ui/src/dock/stack_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
16 changes: 11 additions & 5 deletions crates/ui/src/dock/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize>,
pub center: DockItemState,
pub left_dock: Option<DockState>,
pub right_dock: Option<DockState>,
Expand Down Expand Up @@ -138,9 +143,9 @@ impl Default for DockItemState {
}

impl DockItemState {
pub fn new(panel_name: &str) -> Self {
pub fn new<P: Panel>(panel: &P) -> Self {
Self {
panel_name: panel_name.to_string(),
panel_name: panel.panel_name().to_string(),
..Default::default()
}
}
Expand Down Expand Up @@ -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(
Expand All @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion crates/ui/src/dock/tab_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading