1use gpui::{point, px, size, App, AppContext, Axis, Bounds, Entity, Pixels, WeakEntity, Window};
2use itertools::Itertools as _;
3use serde::{Deserialize, Serialize};
4
5use super::{Dock, DockArea, DockItem, DockPlacement, Panel, PanelRegistry};
6
7#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
9pub struct DockAreaState {
10 #[serde(default)]
14 pub version: Option<usize>,
15 pub center: PanelState,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub left_dock: Option<DockState>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub right_dock: Option<DockState>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub bottom_dock: Option<DockState>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
26pub struct DockState {
27 panel: PanelState,
28 placement: DockPlacement,
29 size: Pixels,
30 open: bool,
31}
32
33impl DockState {
34 pub fn new(dock: Entity<Dock>, cx: &App) -> Self {
35 let dock = dock.read(cx);
36
37 Self {
38 placement: dock.placement,
39 size: dock.size,
40 open: dock.open,
41 panel: dock.panel.view().dump(cx),
42 }
43 }
44
45 pub fn to_dock(
47 &self,
48 dock_area: WeakEntity<DockArea>,
49 window: &mut Window,
50 cx: &mut App,
51 ) -> Entity<Dock> {
52 let item = self.panel.to_item(dock_area.clone(), window, cx);
53 cx.new(|cx| {
54 Dock::from_state(
55 dock_area.clone(),
56 self.placement,
57 self.size,
58 item,
59 self.open,
60 window,
61 cx,
62 )
63 })
64 }
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
69pub struct PanelState {
70 pub panel_name: String,
71 pub children: Vec<PanelState>,
72 pub info: PanelInfo,
73}
74
75#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
76pub struct TileMeta {
77 pub bounds: Bounds<Pixels>,
78 pub z_index: usize,
79}
80
81impl Default for TileMeta {
82 fn default() -> Self {
83 Self {
84 bounds: Bounds {
85 origin: point(px(10.), px(10.)),
86 size: size(px(200.), px(200.)),
87 },
88 z_index: 0,
89 }
90 }
91}
92
93impl From<Bounds<Pixels>> for TileMeta {
94 fn from(bounds: Bounds<Pixels>) -> Self {
95 Self { bounds, z_index: 0 }
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100pub enum PanelInfo {
101 #[serde(rename = "stack")]
102 Stack {
103 sizes: Vec<Pixels>,
104 axis: usize, },
106 #[serde(rename = "tabs")]
107 Tabs { active_index: usize },
108 #[serde(rename = "panel")]
109 Panel(serde_json::Value),
110 #[serde(rename = "tiles")]
111 Tiles { metas: Vec<TileMeta> },
112}
113
114impl PanelInfo {
115 pub fn stack(sizes: Vec<Pixels>, axis: Axis) -> Self {
116 Self::Stack {
117 sizes,
118 axis: if axis == Axis::Horizontal { 0 } else { 1 },
119 }
120 }
121
122 pub fn tabs(active_index: usize) -> Self {
123 Self::Tabs { active_index }
124 }
125
126 pub fn panel(info: serde_json::Value) -> Self {
127 Self::Panel(info)
128 }
129
130 pub fn tiles(metas: Vec<TileMeta>) -> Self {
131 Self::Tiles { metas }
132 }
133
134 pub fn axis(&self) -> Option<Axis> {
135 match self {
136 Self::Stack { axis, .. } => Some(if *axis == 0 {
137 Axis::Horizontal
138 } else {
139 Axis::Vertical
140 }),
141 _ => None,
142 }
143 }
144
145 pub fn sizes(&self) -> Option<&Vec<Pixels>> {
146 match self {
147 Self::Stack { sizes, .. } => Some(sizes),
148 _ => None,
149 }
150 }
151
152 pub fn active_index(&self) -> Option<usize> {
153 match self {
154 Self::Tabs { active_index } => Some(*active_index),
155 _ => None,
156 }
157 }
158}
159
160impl Default for PanelState {
161 fn default() -> Self {
162 Self {
163 panel_name: "".to_string(),
164 children: Vec::new(),
165 info: PanelInfo::Panel(serde_json::Value::Null),
166 }
167 }
168}
169
170impl PanelState {
171 pub fn new<P: Panel>(panel: &P) -> Self {
172 Self {
173 panel_name: panel.panel_name().to_string(),
174 ..Default::default()
175 }
176 }
177
178 pub fn add_child(&mut self, panel: PanelState) {
179 self.children.push(panel);
180 }
181
182 pub fn to_item(
183 &self,
184 dock_area: WeakEntity<DockArea>,
185 window: &mut Window,
186 cx: &mut App,
187 ) -> DockItem {
188 let info = self.info.clone();
189
190 let items: Vec<DockItem> = self
191 .children
192 .iter()
193 .map(|child| child.to_item(dock_area.clone(), window, cx))
194 .collect();
195
196 match info {
197 PanelInfo::Stack { sizes, axis } => {
198 let axis = if axis == 0 {
199 Axis::Horizontal
200 } else {
201 Axis::Vertical
202 };
203 let sizes = sizes.iter().map(|s| Some(*s)).collect_vec();
204 DockItem::split_with_sizes(axis, items, sizes, &dock_area, window, cx)
205 }
206 PanelInfo::Tabs { active_index } => {
207 if items.len() == 1 {
208 return items[0].clone();
209 }
210
211 let items = items
212 .iter()
213 .flat_map(|item| match item {
214 DockItem::Tabs { items, .. } => items.clone(),
215 _ => {
216 vec![]
218 }
219 })
220 .collect_vec();
221
222 DockItem::tabs(items, Some(active_index), &dock_area, window, cx)
223 }
224 PanelInfo::Panel(_) => {
225 let view = PanelRegistry::build_panel(
226 &self.panel_name,
227 dock_area.clone(),
228 self,
229 &info,
230 window,
231 cx,
232 );
233 DockItem::tabs(vec![view.into()], None, &dock_area, window, cx)
234 }
235 PanelInfo::Tiles { metas } => DockItem::tiles(items, metas, &dock_area, window, cx),
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use gpui::px;
243
244 use super::*;
245 #[test]
246 fn test_deserialize_item_state() {
247 let json = include_str!("../../tests/fixtures/layout.json");
248 let state: DockAreaState = serde_json::from_str(json).unwrap();
249 assert_eq!(state.version, None);
250 assert_eq!(state.center.panel_name, "StackPanel");
251 assert_eq!(state.center.children.len(), 2);
252 assert_eq!(state.center.children[0].panel_name, "TabPanel");
253 assert_eq!(state.center.children[1].children.len(), 1);
254 assert_eq!(
255 state.center.children[1].children[0].panel_name,
256 "StoryContainer"
257 );
258 assert_eq!(state.center.children[1].panel_name, "TabPanel");
259
260 let left_dock = state.left_dock.unwrap();
261 assert_eq!(left_dock.open, true);
262 assert_eq!(left_dock.size, px(350.0));
263 assert_eq!(left_dock.placement, DockPlacement::Left);
264 assert_eq!(left_dock.panel.panel_name, "TabPanel");
265 assert_eq!(left_dock.panel.children.len(), 1);
266 assert_eq!(left_dock.panel.children[0].panel_name, "StoryContainer");
267
268 let bottom_dock = state.bottom_dock.unwrap();
269 assert_eq!(bottom_dock.open, true);
270 assert_eq!(bottom_dock.size, px(200.0));
271 assert_eq!(bottom_dock.panel.panel_name, "TabPanel");
272 assert_eq!(bottom_dock.panel.children.len(), 2);
273 assert_eq!(bottom_dock.panel.children[0].panel_name, "StoryContainer");
274
275 let right_dock = state.right_dock.unwrap();
276 assert_eq!(right_dock.open, true);
277 assert_eq!(right_dock.size, px(320.0));
278 assert_eq!(right_dock.panel.panel_name, "TabPanel");
279 assert_eq!(right_dock.panel.children.len(), 1);
280 assert_eq!(right_dock.panel.children[0].panel_name, "StoryContainer");
281 }
282}