dock: Add to support save left, right, bottom dock state. (#303)
The dock state have been updated to use `DockAreaState`.
This commit is contained in:
parent
19bb5eee3a
commit
ca5a0e02d5
9 changed files with 671 additions and 443 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/layout.json
|
/layout.json
|
||||||
|
.vscode
|
||||||
|
|
|
||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"rust-analyzer.check.command": "clippy",
|
|
||||||
"cSpell.words": ["xsmall"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use prelude::FluentBuilder as _;
|
use prelude::FluentBuilder as _;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
@ -11,7 +11,7 @@ use story::{
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonStyled as _},
|
button::{Button, ButtonStyled as _},
|
||||||
color_picker::{ColorPicker, ColorPickerEvent},
|
color_picker::{ColorPicker, ColorPickerEvent},
|
||||||
dock::{DockArea, DockEvent, DockItem, DockItemState, PanelView},
|
dock::{DockArea, DockAreaState, DockEvent, DockItem, PanelView},
|
||||||
h_flex,
|
h_flex,
|
||||||
popup_menu::PopupMenuExt,
|
popup_menu::PopupMenuExt,
|
||||||
theme::{ActiveTheme, Colorize as _, Theme},
|
theme::{ActiveTheme, Colorize as _, Theme},
|
||||||
|
|
@ -39,13 +39,13 @@ pub struct StoryWorkspace {
|
||||||
dock_area: View<DockArea>,
|
dock_area: View<DockArea>,
|
||||||
locale_selector: View<LocaleSelector>,
|
locale_selector: View<LocaleSelector>,
|
||||||
theme_color_picker: View<ColorPicker>,
|
theme_color_picker: View<ColorPicker>,
|
||||||
last_layout_state: Option<DockItemState>,
|
last_layout_state: Option<DockAreaState>,
|
||||||
_save_layout_task: Option<Task<()>>,
|
_save_layout_task: Option<Task<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoryWorkspace {
|
impl StoryWorkspace {
|
||||||
pub fn new(_app_state: Arc<AppState>, cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(_app_state: Arc<AppState>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
cx.observe_window_appearance(|_workspace, cx| {
|
cx.observe_window_appearance(|_, cx| {
|
||||||
Theme::sync_system_appearance(cx);
|
Theme::sync_system_appearance(cx);
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
@ -53,45 +53,48 @@ impl StoryWorkspace {
|
||||||
let dock_area = cx.new_view(|cx| DockArea::new("main-dock", cx));
|
let dock_area = cx.new_view(|cx| DockArea::new("main-dock", cx));
|
||||||
let weak_dock_area = dock_area.downgrade();
|
let weak_dock_area = dock_area.downgrade();
|
||||||
|
|
||||||
let dock_item = match Self::load_layout(&weak_dock_area, cx) {
|
match Self::load_layout(dock_area.clone(), cx) {
|
||||||
Ok(item) => item,
|
Ok(_) => {
|
||||||
|
println!("load layout success");
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("load layout error: {:?}", err);
|
eprintln!("load layout error: {:?}", err);
|
||||||
Self::init_default_layout(&weak_dock_area, cx)
|
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::<TextStory>(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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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::<TextStory>(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);
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.subscribe(&dock_area, |this, dock_area, ev: &DockEvent, cx| match ev {
|
cx.subscribe(&dock_area, |this, dock_area, ev: &DockEvent, cx| match ev {
|
||||||
DockEvent::LayoutChanged => this.save_layout(dock_area, cx),
|
DockEvent::LayoutChanged => this.save_layout(dock_area, cx),
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let dock_area1 = dock_area.clone();
|
cx.on_app_quit({
|
||||||
cx.on_app_quit(move |cx| {
|
let dock_area = dock_area.clone();
|
||||||
let state = dock_area1.read(cx).dump(cx);
|
move |cx| {
|
||||||
|
let state = dock_area.read(cx).dump(cx);
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
// Save layout before quitting
|
// Save layout before quitting
|
||||||
Self::save_state(&state).unwrap();
|
Self::save_state(&state).unwrap();
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
|
@ -132,7 +135,7 @@ impl StoryWorkspace {
|
||||||
|
|
||||||
fn save_layout(&mut self, dock_area: View<DockArea>, cx: &mut ViewContext<Self>) {
|
fn save_layout(&mut self, dock_area: View<DockArea>, cx: &mut ViewContext<Self>) {
|
||||||
self._save_layout_task = Some(cx.spawn(|this, mut cx| async move {
|
self._save_layout_task = Some(cx.spawn(|this, mut cx| async move {
|
||||||
Timer::after(Duration::from_secs(1)).await;
|
Timer::after(Duration::from_secs(10)).await;
|
||||||
|
|
||||||
let _ = cx.update(|cx| {
|
let _ = cx.update(|cx| {
|
||||||
let dock_area = dock_area.read(cx);
|
let dock_area = dock_area.read(cx);
|
||||||
|
|
@ -151,82 +154,52 @@ impl StoryWorkspace {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_state(state: &DockItemState) -> Result<()> {
|
fn save_state(state: &DockAreaState) -> Result<()> {
|
||||||
println!("Save layout...");
|
println!("Save layout...");
|
||||||
let json = serde_json::to_string_pretty(state)?;
|
let json = serde_json::to_string_pretty(state)?;
|
||||||
std::fs::write("layout.json", json)?;
|
std::fs::write("layout.json", json)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_layout(dock_area: &WeakView<DockArea>, cx: &mut WindowContext) -> Result<DockItem> {
|
fn load_layout(dock_area: View<DockArea>, cx: &mut WindowContext) -> Result<()> {
|
||||||
let fname = "layout.json";
|
let fname = "layout.json";
|
||||||
let json = std::fs::read_to_string(fname)?;
|
let json = std::fs::read_to_string(fname)?;
|
||||||
let state = serde_json::from_str::<DockItemState>(&json)?;
|
let state = serde_json::from_str::<DockAreaState>(&json)?;
|
||||||
|
|
||||||
return Ok(state.to_item(dock_area.clone(), cx));
|
dock_area.update(cx, |dock_area, cx| {
|
||||||
|
dock_area.load(state, cx).context("load layout")?;
|
||||||
|
|
||||||
|
Ok::<(), anyhow::Error>(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_default_layout(dock_area: &WeakView<DockArea>, cx: &mut WindowContext) -> DockItem {
|
fn init_default_layout(dock_area: &WeakView<DockArea>, cx: &mut WindowContext) -> DockItem {
|
||||||
DockItem::split_with_sizes(
|
DockItem::split_with_sizes(
|
||||||
Axis::Horizontal,
|
Axis::Vertical,
|
||||||
vec![
|
vec![DockItem::tabs(
|
||||||
DockItem::split(
|
vec![
|
||||||
Axis::Vertical,
|
Arc::new(StoryContainer::panel::<ButtonStory>(cx)),
|
||||||
vec![
|
Arc::new(StoryContainer::panel::<InputStory>(cx)),
|
||||||
DockItem::tab(StoryContainer::panel::<IconStory>(cx), &dock_area, cx),
|
Arc::new(StoryContainer::panel::<TextStory>(cx)),
|
||||||
DockItem::tab(StoryContainer::panel::<CalendarStory>(cx), &dock_area, cx),
|
Arc::new(StoryContainer::panel::<DropdownStory>(cx)),
|
||||||
],
|
Arc::new(StoryContainer::panel::<ModalStory>(cx)),
|
||||||
&dock_area,
|
Arc::new(StoryContainer::panel::<PopupStory>(cx)),
|
||||||
cx,
|
Arc::new(StoryContainer::panel::<SwitchStory>(cx)),
|
||||||
),
|
Arc::new(StoryContainer::panel::<ProgressStory>(cx)),
|
||||||
DockItem::split_with_sizes(
|
Arc::new(StoryContainer::panel::<TableStory>(cx)),
|
||||||
Axis::Vertical,
|
Arc::new(StoryContainer::panel::<ImageStory>(cx)),
|
||||||
vec![
|
Arc::new(StoryContainer::panel::<IconStory>(cx)),
|
||||||
DockItem::tabs(
|
Arc::new(StoryContainer::panel::<TooltipStory>(cx)),
|
||||||
vec![
|
Arc::new(StoryContainer::panel::<ProgressStory>(cx)),
|
||||||
Arc::new(StoryContainer::panel::<ButtonStory>(cx)),
|
Arc::new(StoryContainer::panel::<CalendarStory>(cx)),
|
||||||
Arc::new(StoryContainer::panel::<InputStory>(cx)),
|
Arc::new(StoryContainer::panel::<ResizableStory>(cx)),
|
||||||
Arc::new(StoryContainer::panel::<DropdownStory>(cx)),
|
Arc::new(StoryContainer::panel::<ScrollableStory>(cx)),
|
||||||
Arc::new(StoryContainer::panel::<ModalStory>(cx)),
|
],
|
||||||
Arc::new(StoryContainer::panel::<PopupStory>(cx)),
|
None,
|
||||||
Arc::new(StoryContainer::panel::<SwitchStory>(cx)),
|
&dock_area,
|
||||||
Arc::new(StoryContainer::panel::<ProgressStory>(cx)),
|
cx,
|
||||||
Arc::new(StoryContainer::panel::<TableStory>(cx)),
|
)],
|
||||||
Arc::new(StoryContainer::panel::<ImageStory>(cx)),
|
vec![None],
|
||||||
Arc::new(StoryContainer::panel::<ResizableStory>(cx)),
|
|
||||||
Arc::new(StoryContainer::panel::<ScrollableStory>(cx)),
|
|
||||||
],
|
|
||||||
None,
|
|
||||||
&dock_area,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
DockItem::tabs(
|
|
||||||
vec![
|
|
||||||
Arc::new(StoryContainer::panel::<ProgressStory>(cx)),
|
|
||||||
Arc::new(StoryContainer::panel::<TextStory>(cx)),
|
|
||||||
],
|
|
||||||
None,
|
|
||||||
&dock_area,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
vec![None, None, Some(px(300.))],
|
|
||||||
&dock_area,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
DockItem::split_with_sizes(
|
|
||||||
Axis::Vertical,
|
|
||||||
vec![
|
|
||||||
DockItem::tab(StoryContainer::panel::<TooltipStory>(cx), &dock_area, cx),
|
|
||||||
DockItem::tab(StoryContainer::panel::<CalendarStory>(cx), &dock_area, cx),
|
|
||||||
DockItem::tab(StoryContainer::panel::<ImageStory>(cx), &dock_area, cx),
|
|
||||||
],
|
|
||||||
vec![None, None, Some(px(300.))],
|
|
||||||
&dock_area,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
vec![Some(px(300.)), None, Some(px(350.))],
|
|
||||||
&dock_area,
|
&dock_area,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use gpui::{
|
||||||
StatefulInteractiveElement, Style, Styled as _, View, ViewContext, VisualContext as _,
|
StatefulInteractiveElement, Style, Styled as _, View, ViewContext, VisualContext as _,
|
||||||
WeakView,
|
WeakView,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
resizable::{HANDLE_PADDING, HANDLE_SIZE, PANEL_MIN_SIZE},
|
resizable::{HANDLE_PADDING, HANDLE_SIZE, PANEL_MIN_SIZE},
|
||||||
|
|
@ -20,10 +21,13 @@ use super::{DockArea, PanelView, TabPanel};
|
||||||
#[derive(Clone, Render)]
|
#[derive(Clone, Render)]
|
||||||
struct ResizePanel;
|
struct ResizePanel;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum DockPlacement {
|
pub enum DockPlacement {
|
||||||
|
#[serde(rename = "left")]
|
||||||
Left,
|
Left,
|
||||||
|
#[serde(rename = "bottom")]
|
||||||
Bottom,
|
Bottom,
|
||||||
|
#[serde(rename = "right")]
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,18 +56,17 @@ impl DockPlacement {
|
||||||
///
|
///
|
||||||
/// This is unlike Panel, it can't be move or add any other panel.
|
/// This is unlike Panel, it can't be move or add any other panel.
|
||||||
pub struct Dock {
|
pub struct Dock {
|
||||||
placement: DockPlacement,
|
pub(super) placement: DockPlacement,
|
||||||
dock_area: WeakView<DockArea>,
|
dock_area: WeakView<DockArea>,
|
||||||
pub(crate) panel: View<TabPanel>,
|
pub(crate) panel: View<TabPanel>,
|
||||||
/// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height.
|
/// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height.
|
||||||
size: Pixels,
|
pub(super) size: Pixels,
|
||||||
open: bool,
|
pub(super) open: bool,
|
||||||
resizeable: bool,
|
|
||||||
is_resizing: bool,
|
is_resizing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dock {
|
impl Dock {
|
||||||
fn new(
|
pub(crate) fn new(
|
||||||
dock_area: WeakView<DockArea>,
|
dock_area: WeakView<DockArea>,
|
||||||
placement: DockPlacement,
|
placement: DockPlacement,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
|
@ -80,7 +83,6 @@ impl Dock {
|
||||||
dock_area,
|
dock_area,
|
||||||
panel,
|
panel,
|
||||||
open: true,
|
open: true,
|
||||||
resizeable: true,
|
|
||||||
size: px(200.0),
|
size: px(200.0),
|
||||||
is_resizing: false,
|
is_resizing: false,
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +100,23 @@ impl Dock {
|
||||||
Self::new(dock_area, DockPlacement::Right, cx)
|
Self::new(dock_area, DockPlacement::Right, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_state(
|
||||||
|
dock_area: WeakView<DockArea>,
|
||||||
|
placement: DockPlacement,
|
||||||
|
size: Pixels,
|
||||||
|
panel: View<TabPanel>,
|
||||||
|
open: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
placement,
|
||||||
|
dock_area,
|
||||||
|
panel,
|
||||||
|
open,
|
||||||
|
size,
|
||||||
|
is_resizing: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_panels(&mut self, panels: Vec<Arc<dyn PanelView>>, cx: &mut ViewContext<Self>) {
|
pub fn set_panels(&mut self, panels: Vec<Arc<dyn PanelView>>, cx: &mut ViewContext<Self>) {
|
||||||
self.panel.update(cx, |tab_panel, _| {
|
self.panel.update(cx, |tab_panel, _| {
|
||||||
tab_panel.panels = panels;
|
tab_panel.panels = panels;
|
||||||
|
|
@ -106,12 +125,6 @@ impl Dock {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the Dock to be resizeable, default: true
|
|
||||||
pub fn resizeable(mut self, resizeable: bool) -> Self {
|
|
||||||
self.resizeable = resizeable;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_open(&self) -> bool {
|
pub fn is_open(&self) -> bool {
|
||||||
self.open
|
self.open
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ mod dock;
|
||||||
mod invalid_panel;
|
mod invalid_panel;
|
||||||
mod panel;
|
mod panel;
|
||||||
mod stack_panel;
|
mod stack_panel;
|
||||||
|
mod state;
|
||||||
mod tab_panel;
|
mod tab_panel;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
pub use dock::*;
|
pub use dock::*;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds,
|
actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds,
|
||||||
|
|
@ -14,6 +14,8 @@ use gpui::{
|
||||||
};
|
};
|
||||||
pub use panel::*;
|
pub use panel::*;
|
||||||
pub use stack_panel::*;
|
pub use stack_panel::*;
|
||||||
|
pub use state::*;
|
||||||
|
use std::sync::Arc;
|
||||||
pub use tab_panel::*;
|
pub use tab_panel::*;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
|
@ -293,12 +295,64 @@ impl DockArea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load the state of the DockArea from the DockAreaState.
|
||||||
|
///
|
||||||
|
/// See also [DockeArea::dump].
|
||||||
|
pub fn load(&mut self, state: DockAreaState, cx: &mut ViewContext<Self>) -> Result<()> {
|
||||||
|
let weak_self = cx.view().downgrade();
|
||||||
|
|
||||||
|
if let Some(left_dock) = state.left_dock {
|
||||||
|
match left_dock.to_dock(weak_self.clone(), cx) {
|
||||||
|
Ok(dock) => self.left_dock = Some(dock),
|
||||||
|
Err(err) => bail!("failed to load left dock: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(right_dock) = state.right_dock {
|
||||||
|
match right_dock.to_dock(weak_self.clone(), cx) {
|
||||||
|
Ok(dock) => self.right_dock = Some(dock),
|
||||||
|
Err(err) => bail!("failed to load right dock: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(bottom_dock) = state.bottom_dock {
|
||||||
|
match bottom_dock.to_dock(weak_self.clone(), cx) {
|
||||||
|
Ok(dock) => self.bottom_dock = Some(dock),
|
||||||
|
Err(err) => bail!("failed to load bottom dock: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.items = state.center.to_item(weak_self, cx);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Dump the dock panels layout to DockItemState.
|
/// Dump the dock panels layout to DockItemState.
|
||||||
///
|
///
|
||||||
/// See also `DockItemState::to_item` for the load DockItem from DockItemState.
|
/// See also [DockArea::load].
|
||||||
pub fn dump(&self, cx: &AppContext) -> DockItemState {
|
pub fn dump(&self, cx: &AppContext) -> DockAreaState {
|
||||||
let root = self.items.view();
|
let root = self.items.view();
|
||||||
root.dump(cx)
|
let center = root.dump(cx);
|
||||||
|
|
||||||
|
let left_dock = self
|
||||||
|
.left_dock
|
||||||
|
.as_ref()
|
||||||
|
.map(|dock| DockState::new(dock.clone(), cx));
|
||||||
|
let right_dock = self
|
||||||
|
.right_dock
|
||||||
|
.as_ref()
|
||||||
|
.map(|dock| DockState::new(dock.clone(), cx));
|
||||||
|
let bottom_dock = self
|
||||||
|
.bottom_dock
|
||||||
|
.as_ref()
|
||||||
|
.map(|dock| DockState::new(dock.clone(), cx));
|
||||||
|
|
||||||
|
DockAreaState {
|
||||||
|
center,
|
||||||
|
left_dock,
|
||||||
|
right_dock,
|
||||||
|
bottom_dock,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subscribe event on the panels
|
/// Subscribe event on the panels
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::popup_menu::PopupMenu;
|
use crate::popup_menu::PopupMenu;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, AnyView, AppContext, Axis, EventEmitter, FocusHandle, FocusableView, Global, Hsla,
|
AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Global, Hsla,
|
||||||
IntoElement, Pixels, SharedString, View, VisualContext, WeakView, WindowContext,
|
IntoElement, SharedString, View, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
|
||||||
use rust_i18n::t;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use super::{invalid_panel::InvalidPanel, DockArea, DockItem};
|
use rust_i18n::t;
|
||||||
|
|
||||||
|
use super::{DockArea, DockItemInfo, DockItemState};
|
||||||
|
|
||||||
pub enum PanelEvent {
|
pub enum PanelEvent {
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
|
|
@ -143,150 +142,8 @@ impl PartialEq for dyn PanelView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct DockItemState {
|
|
||||||
pub panel_name: String,
|
|
||||||
pub children: Vec<DockItemState>,
|
|
||||||
pub info: DockItemInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum DockItemInfo {
|
|
||||||
#[serde(rename = "stack")]
|
|
||||||
Stack {
|
|
||||||
sizes: Vec<Pixels>,
|
|
||||||
/// The axis of the stack, 0 is horizontal, 1 is vertical
|
|
||||||
axis: usize,
|
|
||||||
},
|
|
||||||
#[serde(rename = "tabs")]
|
|
||||||
Tabs { active_index: usize },
|
|
||||||
#[serde(rename = "panel")]
|
|
||||||
Panel(serde_json::Value),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DockItemInfo {
|
|
||||||
pub fn stack(sizes: Vec<Pixels>, axis: Axis) -> Self {
|
|
||||||
Self::Stack {
|
|
||||||
sizes,
|
|
||||||
axis: if axis == Axis::Horizontal { 0 } else { 1 },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tabs(active_index: usize) -> Self {
|
|
||||||
Self::Tabs { active_index }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn panel(value: serde_json::Value) -> Self {
|
|
||||||
Self::Panel(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis(&self) -> Option<Axis> {
|
|
||||||
match self {
|
|
||||||
Self::Stack { axis, .. } => Some(if *axis == 0 {
|
|
||||||
Axis::Horizontal
|
|
||||||
} else {
|
|
||||||
Axis::Vertical
|
|
||||||
}),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sizes(&self) -> Option<&Vec<Pixels>> {
|
|
||||||
match self {
|
|
||||||
Self::Stack { sizes, .. } => Some(sizes),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_index(&self) -> Option<usize> {
|
|
||||||
match self {
|
|
||||||
Self::Tabs { active_index } => Some(*active_index),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_child(&mut self, panel: DockItemState) {
|
|
||||||
self.children.push(panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_item(&self, dock_area: WeakView<DockArea>, cx: &mut WindowContext) -> DockItem {
|
|
||||||
let info = self.info.clone();
|
|
||||||
|
|
||||||
let items: Vec<DockItem> = self
|
|
||||||
.children
|
|
||||||
.iter()
|
|
||||||
.map(|child| child.to_item(dock_area.clone(), cx))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
match info {
|
|
||||||
DockItemInfo::Stack { sizes, axis } => {
|
|
||||||
let axis = if axis == 0 {
|
|
||||||
Axis::Horizontal
|
|
||||||
} else {
|
|
||||||
Axis::Vertical
|
|
||||||
};
|
|
||||||
let sizes = sizes.iter().map(|s| Some(*s)).collect_vec();
|
|
||||||
DockItem::split_with_sizes(axis, items, sizes, &dock_area, cx)
|
|
||||||
}
|
|
||||||
DockItemInfo::Tabs { active_index } => {
|
|
||||||
if items.len() == 1 {
|
|
||||||
return items[0].clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
let items = items
|
|
||||||
.iter()
|
|
||||||
.flat_map(|item| match item {
|
|
||||||
DockItem::Tabs { items, .. } => items.clone(),
|
|
||||||
_ => {
|
|
||||||
unreachable!("Invalid DockItem type in DockItemInfo::Tabs")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
DockItem::tabs(items, Some(active_index), &dock_area, 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, info.clone(), cx)),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
DockItem::tabs(vec![view.into()], None, &dock_area, cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PanelRegistry {
|
pub struct PanelRegistry {
|
||||||
items: HashMap<
|
pub(super) items: HashMap<
|
||||||
String,
|
String,
|
||||||
Arc<dyn Fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView>>,
|
Arc<dyn Fn(WeakView<DockArea>, DockItemInfo, &mut WindowContext) -> Box<dyn PanelView>>,
|
||||||
>,
|
>,
|
||||||
|
|
@ -313,19 +170,3 @@ where
|
||||||
.items
|
.items
|
||||||
.insert(panel_name.to_string(), Arc::new(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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -182,13 +182,13 @@ impl StackPanel {
|
||||||
} else if let Ok(stack_panel) = panel.view().downcast::<Self>() {
|
} else if let Ok(stack_panel) = panel.view().downcast::<Self>() {
|
||||||
stack_panel.update(cx, |stack_panel, _| stack_panel.parent = Some(view));
|
stack_panel.update(cx, |stack_panel, _| stack_panel.parent = Some(view));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subscribe to the panel's layout change event.
|
// Subscribe to the panel's layout change event.
|
||||||
_ = dock_area.update(cx, |_, cx| {
|
_ = dock_area.update(cx, |_, cx| {
|
||||||
if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
|
if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
|
||||||
DockArea::subscribe_panel(&tab_panel, cx);
|
DockArea::subscribe_panel(&tab_panel, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
250
crates/ui/src/dock/state.rs
Normal file
250
crates/ui/src/dock/state.rs
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use gpui::{AppContext, Axis, Pixels, View, VisualContext as _, WeakView, WindowContext};
|
||||||
|
use itertools::Itertools as _;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
invalid_panel::InvalidPanel, Dock, DockArea, DockItem, DockPlacement, PanelRegistry, PanelView,
|
||||||
|
TabPanel,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Used to serialize and deserialize the DockArea
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct DockAreaState {
|
||||||
|
pub center: DockItemState,
|
||||||
|
pub left_dock: Option<DockState>,
|
||||||
|
pub right_dock: Option<DockState>,
|
||||||
|
pub bottom_dock: Option<DockState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to serialize and deserialize the Dock
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct DockState {
|
||||||
|
panel: DockItemState,
|
||||||
|
placement: DockPlacement,
|
||||||
|
size: Pixels,
|
||||||
|
open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DockState {
|
||||||
|
pub fn new(dock: View<Dock>, cx: &AppContext) -> Self {
|
||||||
|
let dock = dock.read(cx);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
placement: dock.placement,
|
||||||
|
size: dock.size,
|
||||||
|
open: dock.open,
|
||||||
|
panel: dock.panel.dump(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the DockState to Dock
|
||||||
|
pub fn to_dock(
|
||||||
|
&self,
|
||||||
|
dock_area: WeakView<DockArea>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Result<View<Dock>> {
|
||||||
|
let view = self.panel.to_item(dock_area.clone(), cx).view();
|
||||||
|
if let Ok(tab_panel) = view.view().downcast::<TabPanel>() {
|
||||||
|
let dock = cx.new_view(|_| {
|
||||||
|
Dock::from_state(
|
||||||
|
dock_area.clone(),
|
||||||
|
self.placement,
|
||||||
|
self.size,
|
||||||
|
tab_panel,
|
||||||
|
self.open,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(dock)
|
||||||
|
} else {
|
||||||
|
bail!("Invalid panel, failed to downcast to TabPanel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to serialize and deserialize the DockerItem
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct DockItemState {
|
||||||
|
pub panel_name: String,
|
||||||
|
pub children: Vec<DockItemState>,
|
||||||
|
pub info: DockItemInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum DockItemInfo {
|
||||||
|
#[serde(rename = "stack")]
|
||||||
|
Stack {
|
||||||
|
sizes: Vec<Pixels>,
|
||||||
|
/// The axis of the stack, 0 is horizontal, 1 is vertical
|
||||||
|
axis: usize,
|
||||||
|
},
|
||||||
|
#[serde(rename = "tabs")]
|
||||||
|
Tabs { active_index: usize },
|
||||||
|
#[serde(rename = "panel")]
|
||||||
|
Panel(serde_json::Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DockItemInfo {
|
||||||
|
pub fn stack(sizes: Vec<Pixels>, axis: Axis) -> Self {
|
||||||
|
Self::Stack {
|
||||||
|
sizes,
|
||||||
|
axis: if axis == Axis::Horizontal { 0 } else { 1 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tabs(active_index: usize) -> Self {
|
||||||
|
Self::Tabs { active_index }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn panel(value: serde_json::Value) -> Self {
|
||||||
|
Self::Panel(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn axis(&self) -> Option<Axis> {
|
||||||
|
match self {
|
||||||
|
Self::Stack { axis, .. } => Some(if *axis == 0 {
|
||||||
|
Axis::Horizontal
|
||||||
|
} else {
|
||||||
|
Axis::Vertical
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sizes(&self) -> Option<&Vec<Pixels>> {
|
||||||
|
match self {
|
||||||
|
Self::Stack { sizes, .. } => Some(sizes),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_index(&self) -> Option<usize> {
|
||||||
|
match self {
|
||||||
|
Self::Tabs { active_index } => Some(*active_index),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_child(&mut self, panel: DockItemState) {
|
||||||
|
self.children.push(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_item(&self, dock_area: WeakView<DockArea>, cx: &mut WindowContext) -> DockItem {
|
||||||
|
let info = self.info.clone();
|
||||||
|
|
||||||
|
let items: Vec<DockItem> = self
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(|child| child.to_item(dock_area.clone(), cx))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match info {
|
||||||
|
DockItemInfo::Stack { sizes, axis } => {
|
||||||
|
let axis = if axis == 0 {
|
||||||
|
Axis::Horizontal
|
||||||
|
} else {
|
||||||
|
Axis::Vertical
|
||||||
|
};
|
||||||
|
let sizes = sizes.iter().map(|s| Some(*s)).collect_vec();
|
||||||
|
DockItem::split_with_sizes(axis, items, sizes, &dock_area, cx)
|
||||||
|
}
|
||||||
|
DockItemInfo::Tabs { active_index } => {
|
||||||
|
if items.len() == 1 {
|
||||||
|
return items[0].clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let items = items
|
||||||
|
.iter()
|
||||||
|
.flat_map(|item| match item {
|
||||||
|
DockItem::Tabs { items, .. } => items.clone(),
|
||||||
|
_ => {
|
||||||
|
unreachable!("Invalid DockItem type in DockItemInfo::Tabs")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
DockItem::tabs(items, Some(active_index), &dock_area, 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, info.clone(), cx)),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
DockItem::tabs(vec![view.into()], None, &dock_area, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use gpui::px;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
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.center.panel_name, "StackPanel");
|
||||||
|
assert_eq!(state.center.children.len(), 2);
|
||||||
|
assert_eq!(state.center.children[0].panel_name, "TabPanel");
|
||||||
|
assert_eq!(state.center.children[1].children.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
state.center.children[1].children[0].panel_name,
|
||||||
|
"StoryContainer"
|
||||||
|
);
|
||||||
|
assert_eq!(state.center.children[1].panel_name, "TabPanel");
|
||||||
|
|
||||||
|
let left_dock = state.left_dock.unwrap();
|
||||||
|
assert_eq!(left_dock.open, true);
|
||||||
|
assert_eq!(left_dock.size, px(350.0));
|
||||||
|
assert_eq!(left_dock.placement, DockPlacement::Left);
|
||||||
|
assert_eq!(left_dock.panel.panel_name, "TabPanel");
|
||||||
|
assert_eq!(left_dock.panel.children.len(), 1);
|
||||||
|
assert_eq!(left_dock.panel.children[0].panel_name, "StoryContainer");
|
||||||
|
|
||||||
|
let bottom_dock = state.bottom_dock.unwrap();
|
||||||
|
assert_eq!(bottom_dock.open, true);
|
||||||
|
assert_eq!(bottom_dock.size, px(200.0));
|
||||||
|
assert_eq!(bottom_dock.panel.panel_name, "TabPanel");
|
||||||
|
assert_eq!(bottom_dock.panel.children.len(), 2);
|
||||||
|
assert_eq!(bottom_dock.panel.children[0].panel_name, "StoryContainer");
|
||||||
|
|
||||||
|
let right_dock = state.right_dock.unwrap();
|
||||||
|
assert_eq!(right_dock.open, true);
|
||||||
|
assert_eq!(right_dock.size, px(320.0));
|
||||||
|
assert_eq!(right_dock.panel.panel_name, "TabPanel");
|
||||||
|
assert_eq!(right_dock.panel.children.len(), 1);
|
||||||
|
assert_eq!(right_dock.panel.children[0].panel_name, "StoryContainer");
|
||||||
|
}
|
||||||
|
}
|
||||||
406
crates/ui/tests/fixtures/layout.json
vendored
406
crates/ui/tests/fixtures/layout.json
vendored
|
|
@ -1,161 +1,261 @@
|
||||||
{
|
{
|
||||||
"panel_name": "StackPanel",
|
"center": {
|
||||||
"children": [
|
"panel_name": "StackPanel",
|
||||||
{
|
"children": [
|
||||||
"panel_name": "StackPanel",
|
{
|
||||||
"children": [
|
"panel_name": "TabPanel",
|
||||||
{
|
"children": [
|
||||||
"panel_name": "TabPanel",
|
{
|
||||||
"children": [
|
"panel_name": "StoryContainer",
|
||||||
{
|
"children": [],
|
||||||
"panel_name": "StoryContainer",
|
"info": {
|
||||||
"children": [],
|
"panel": {
|
||||||
"info": { "panel": { "story_klass": "IconStory" } }
|
"story_klass": "ButtonStory"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
"info": { "tabs": { "active_index": 0 } }
|
{
|
||||||
},
|
"panel_name": "StoryContainer",
|
||||||
{
|
"children": [],
|
||||||
"panel_name": "TabPanel",
|
"info": {
|
||||||
"children": [
|
"panel": {
|
||||||
{
|
"story_klass": "InputStory"
|
||||||
"panel_name": "StoryContainer",
|
}
|
||||||
"children": [],
|
|
||||||
"info": { "panel": { "story_klass": "CalendarStory" } }
|
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
"info": { "tabs": { "active_index": 0 } }
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "TextStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "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": "IconStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "TooltipStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "ProgressStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "CalendarStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "ResizableStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "ScrollableStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"tabs": {
|
||||||
|
"active_index": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
"info": { "stack": { "sizes": [584.0, 583.0], "axis": 1 } }
|
{
|
||||||
},
|
"panel_name": "TabPanel",
|
||||||
{
|
"children": [
|
||||||
"panel_name": "StackPanel",
|
{
|
||||||
"children": [
|
"panel_name": "StoryContainer",
|
||||||
{
|
"children": [],
|
||||||
"panel_name": "TabPanel",
|
"info": {
|
||||||
"children": [
|
"panel": {
|
||||||
{
|
"story_klass": "PopupStory"
|
||||||
"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 } }
|
],
|
||||||
},
|
"info": {
|
||||||
{
|
"tabs": {
|
||||||
"panel_name": "TabPanel",
|
"active_index": 0
|
||||||
"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 } }
|
],
|
||||||
},
|
"info": {
|
||||||
{
|
"stack": {
|
||||||
"panel_name": "StackPanel",
|
"sizes": [704.0, 263.0],
|
||||||
"children": [
|
"axis": 1
|
||||||
{
|
}
|
||||||
"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 } }
|
"left_dock": {
|
||||||
|
"panel": {
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "ListStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"tabs": {
|
||||||
|
"active_index": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"placement": "left",
|
||||||
|
"size": 350.0,
|
||||||
|
"open": true,
|
||||||
|
"resizeable": true
|
||||||
|
},
|
||||||
|
"right_dock": {
|
||||||
|
"panel": {
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "ImageStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"tabs": {
|
||||||
|
"active_index": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"placement": "right",
|
||||||
|
"size": 320.0,
|
||||||
|
"open": true,
|
||||||
|
"resizeable": true
|
||||||
|
},
|
||||||
|
"bottom_dock": {
|
||||||
|
"panel": {
|
||||||
|
"panel_name": "TabPanel",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "TextStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_name": "StoryContainer",
|
||||||
|
"children": [],
|
||||||
|
"info": {
|
||||||
|
"panel": {
|
||||||
|
"story_klass": "IconStory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"tabs": {
|
||||||
|
"active_index": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"placement": "bottom",
|
||||||
|
"size": 200.0,
|
||||||
|
"open": true,
|
||||||
|
"resizeable": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue