use gpui::{ point, px, size, AppContext, Axis, Bounds, Pixels, View, VisualContext as _, WeakView, WindowContext, }; use itertools::Itertools as _; use serde::{Deserialize, Serialize}; use super::{ invalid_panel::InvalidPanel, Dock, DockArea, DockItem, DockPlacement, Panel, PanelRegistry, }; /// Used to serialize and deserialize the DockArea #[derive(Debug, Default, 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: PanelState, #[serde(skip_serializing_if = "Option::is_none")] pub left_dock: Option, #[serde(skip_serializing_if = "Option::is_none")] pub right_dock: Option, #[serde(skip_serializing_if = "Option::is_none")] pub bottom_dock: Option, } /// Used to serialize and deserialize the Dock #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct DockState { panel: PanelState, placement: DockPlacement, size: Pixels, open: bool, } impl DockState { pub fn new(dock: View, cx: &AppContext) -> Self { let dock = dock.read(cx); Self { placement: dock.placement, size: dock.size, open: dock.open, panel: dock.panel.view().dump(cx), } } /// Convert the DockState to Dock pub fn to_dock(&self, dock_area: WeakView, cx: &mut WindowContext) -> View { let item = self.panel.to_item(dock_area.clone(), cx); cx.new_view(|cx| { Dock::from_state( dock_area.clone(), self.placement, self.size, item, self.open, cx, ) }) } } /// Used to serialize and deserialize the DockerItem #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct PanelState { pub panel_name: String, pub children: Vec, pub info: PanelInfo, } #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] pub struct TileMeta { pub bounds: Bounds, pub z_index: usize, } impl Default for TileMeta { fn default() -> Self { Self { bounds: Bounds { origin: point(px(10.), px(10.)), size: size(px(200.), px(200.)), }, z_index: 0, } } } impl From> for TileMeta { fn from(bounds: Bounds) -> Self { Self { bounds, z_index: 0 } } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PanelInfo { #[serde(rename = "stack")] Stack { sizes: Vec, axis: usize, // 0 for horizontal, 1 for vertical }, #[serde(rename = "tabs")] Tabs { active_index: usize }, #[serde(rename = "panel")] Panel(serde_json::Value), #[serde(rename = "tiles")] Tiles { metas: Vec }, } impl PanelInfo { pub fn stack(sizes: Vec, 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(info: serde_json::Value) -> Self { Self::Panel(info) } pub fn tiles(metas: Vec) -> Self { Self::Tiles { metas } } pub fn axis(&self) -> Option { match self { Self::Stack { axis, .. } => Some(if *axis == 0 { Axis::Horizontal } else { Axis::Vertical }), _ => None, } } pub fn sizes(&self) -> Option<&Vec> { match self { Self::Stack { sizes, .. } => Some(sizes), _ => None, } } pub fn active_index(&self) -> Option { match self { Self::Tabs { active_index } => Some(*active_index), _ => None, } } } impl Default for PanelState { fn default() -> Self { Self { panel_name: "".to_string(), children: Vec::new(), info: PanelInfo::Panel(serde_json::Value::Null), } } } impl PanelState { pub fn new(panel: &P) -> Self { Self { panel_name: panel.panel_id().to_string(), ..Default::default() } } pub fn add_child(&mut self, panel: PanelState) { self.children.push(panel); } pub fn to_item(&self, dock_area: WeakView, cx: &mut WindowContext) -> DockItem { let info = self.info.clone(); let items: Vec = self .children .iter() .map(|child| child.to_item(dock_area.clone(), cx)) .collect(); match info { PanelInfo::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) } PanelInfo::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(), _ => { // ignore invalid panels in tabs vec![] } }) .collect_vec(); DockItem::tabs(items, Some(active_index), &dock_area, cx) } PanelInfo::Panel(_) => { let view = if let Some(f) = cx .global::() .items .get(&self.panel_name) .cloned() { f(dock_area.clone(), self, &info, cx) } else { // Show an invalid panel if the panel is not registered. Box::new( cx.new_view(|cx| InvalidPanel::new(&self.panel_name, self.clone(), cx)), ) }; DockItem::tabs(vec![view.into()], None, &dock_area, cx) } PanelInfo::Tiles { metas } => DockItem::tiles(items, metas, &dock_area, cx), } } }