1use super::*;
2
3impl DockGraph {
4 pub fn export_layout(&self, windows: &[(AppWindowId, String)]) -> crate::DockLayout {
5 self.export_layout_with_placement(windows, |_| None)
6 }
7
8 pub fn export_layout_with_placement(
9 &self,
10 windows: &[(AppWindowId, String)],
11 mut placement: impl FnMut(AppWindowId) -> Option<crate::DockWindowPlacement>,
12 ) -> crate::DockLayout {
13 use crate::{DockLayoutFloatingWindow, DockLayoutNode, DockLayoutWindow};
14 use std::collections::HashMap;
15
16 fn visit(
17 graph: &DockGraph,
18 node: DockNodeId,
19 next_id: &mut u32,
20 ids: &mut HashMap<DockNodeId, u32>,
21 out: &mut Vec<DockLayoutNode>,
22 ) {
23 if ids.contains_key(&node) {
24 return;
25 }
26
27 let id = *next_id;
28 *next_id = next_id.saturating_add(1);
29 ids.insert(node, id);
30
31 let Some(n) = graph.nodes.get(node) else {
32 return;
33 };
34
35 match n {
36 DockNode::Tabs { tabs, active } => {
37 out.push(DockLayoutNode::Tabs {
38 id,
39 tabs: tabs.clone(),
40 active: *active,
41 });
42 }
43 DockNode::Split {
44 axis,
45 children,
46 fractions,
47 } => {
48 for child in children {
49 visit(graph, *child, next_id, ids, out);
50 }
51 let child_ids: Vec<u32> = children
52 .iter()
53 .filter_map(|c| ids.get(c).copied())
54 .collect();
55 out.push(DockLayoutNode::Split {
56 id,
57 axis: *axis,
58 children: child_ids,
59 fractions: fractions.clone(),
60 });
61 }
62 DockNode::Floating { child } => {
63 visit(graph, *child, next_id, ids, out);
64 if let Some(&child_id) = ids.get(child) {
65 ids.insert(node, child_id);
66 }
67 }
68 }
69 }
70
71 let mut next_id: u32 = 1;
72 let mut ids: HashMap<DockNodeId, u32> = HashMap::new();
73 let mut nodes: Vec<DockLayoutNode> = Vec::new();
74 let mut out_windows: Vec<DockLayoutWindow> = Vec::new();
75
76 for (window, logical_window_id) in windows {
77 let Some(root) = self.window_root(*window) else {
78 continue;
79 };
80 visit(self, root, &mut next_id, &mut ids, &mut nodes);
81 let Some(root_id) = ids.get(&root).copied() else {
82 continue;
83 };
84
85 let mut floatings: Vec<DockLayoutFloatingWindow> = Vec::new();
86 for floating in self.floating_windows(*window) {
87 visit(self, floating.floating, &mut next_id, &mut ids, &mut nodes);
88 let Some(floating_root) = ids.get(&floating.floating).copied() else {
89 continue;
90 };
91 floatings.push(DockLayoutFloatingWindow {
92 root: floating_root,
93 rect: crate::DockRect::from_rect(floating.rect),
94 });
95 }
96
97 out_windows.push(DockLayoutWindow {
98 logical_window_id: logical_window_id.clone(),
99 root: root_id,
100 placement: placement(*window),
101 floatings,
102 });
103 }
104
105 crate::DockLayout::new(out_windows, nodes)
106 }
107
108 pub fn import_subtree_from_layout(
109 &mut self,
110 layout: &crate::DockLayout,
111 root: u32,
112 ) -> Option<DockNodeId> {
113 use crate::DockLayoutNode;
114 use std::collections::HashMap;
115
116 if layout.layout_version != crate::DOCK_LAYOUT_VERSION {
117 return None;
118 }
119
120 let mut by_id: HashMap<u32, &DockLayoutNode> = HashMap::new();
121 for node in &layout.nodes {
122 let id = match node {
123 DockLayoutNode::Split { id, .. } => *id,
124 DockLayoutNode::Tabs { id, .. } => *id,
125 };
126 by_id.insert(id, node);
127 }
128
129 fn build(
130 graph: &mut DockGraph,
131 by_id: &HashMap<u32, &DockLayoutNode>,
132 id: u32,
133 visiting: &mut HashMap<u32, ()>,
134 ) -> Option<DockNodeId> {
135 if visiting.contains_key(&id) {
136 return None;
137 }
138 visiting.insert(id, ());
139
140 let node = by_id.get(&id)?;
141 let out = match node {
142 DockLayoutNode::Tabs { tabs, active, .. } => {
143 Some(graph.insert_node(DockNode::Tabs {
144 tabs: tabs.clone(),
145 active: *active,
146 }))
147 }
148 DockLayoutNode::Split {
149 axis,
150 children,
151 fractions,
152 ..
153 } => {
154 let mut child_nodes: Vec<DockNodeId> = Vec::new();
155 for child in children {
156 child_nodes.push(build(graph, by_id, *child, visiting)?);
157 }
158 Some(graph.insert_node(DockNode::Split {
159 axis: *axis,
160 children: child_nodes,
161 fractions: fractions.clone(),
162 }))
163 }
164 };
165
166 visiting.remove(&id);
167 out
168 }
169
170 let mut visiting: HashMap<u32, ()> = HashMap::new();
171 build(self, &by_id, root, &mut visiting)
172 }
173
174 pub fn import_subtree_from_layout_checked(
175 &mut self,
176 layout: &crate::DockLayout,
177 root: u32,
178 ) -> Result<DockNodeId, crate::DockLayoutValidationError> {
179 use crate::DockLayoutNode;
180 use std::collections::HashMap;
181
182 layout.validate()?;
183
184 let mut by_id: HashMap<u32, &DockLayoutNode> = HashMap::new();
185 for node in &layout.nodes {
186 let id = match node {
187 DockLayoutNode::Split { id, .. } => *id,
188 DockLayoutNode::Tabs { id, .. } => *id,
189 };
190 by_id.insert(id, node);
191 }
192
193 let mut built: HashMap<u32, DockNodeId> = HashMap::new();
194 fn build_checked(
195 graph: &mut DockGraph,
196 by_id: &HashMap<u32, &DockLayoutNode>,
197 built: &mut HashMap<u32, DockNodeId>,
198 id: u32,
199 ) -> DockNodeId {
200 if let Some(&node) = built.get(&id) {
201 return node;
202 }
203
204 let node = by_id
205 .get(&id)
206 .copied()
207 .expect("layout.validate ensures node id exists");
208
209 let out = match node {
210 DockLayoutNode::Tabs { tabs, active, .. } => graph.insert_node(DockNode::Tabs {
211 tabs: tabs.clone(),
212 active: *active,
213 }),
214 DockLayoutNode::Split {
215 axis,
216 children,
217 fractions,
218 ..
219 } => {
220 let mut child_nodes: Vec<DockNodeId> = Vec::new();
221 for child in children {
222 child_nodes.push(build_checked(graph, by_id, built, *child));
223 }
224 graph.insert_node(DockNode::Split {
225 axis: *axis,
226 children: child_nodes,
227 fractions: fractions.clone(),
228 })
229 }
230 };
231
232 built.insert(id, out);
233 out
234 }
235
236 if !by_id.contains_key(&root) {
237 return Err(crate::DockLayoutValidationError {
238 kind: crate::DockLayoutValidationErrorKind::MissingNodeId { id: root },
239 });
240 }
241
242 Ok(build_checked(self, &by_id, &mut built, root))
243 }
244
245 pub fn import_layout_for_windows(
246 &mut self,
247 layout: &crate::DockLayout,
248 windows: &[(AppWindowId, String)],
249 ) -> bool {
250 use std::collections::HashMap;
251
252 if layout.layout_version != crate::DOCK_LAYOUT_VERSION {
253 return false;
254 }
255
256 let mut by_logical: HashMap<&str, AppWindowId> = HashMap::new();
257 for (window, logical_id) in windows {
258 by_logical.insert(logical_id.as_str(), *window);
259 }
260
261 let mut imported_any = false;
262 for w in &layout.windows {
263 let Some(window) = by_logical.get(w.logical_window_id.as_str()).copied() else {
264 continue;
265 };
266
267 let Some(root) = self.import_subtree_from_layout(layout, w.root) else {
268 continue;
269 };
270 self.set_window_root(window, root);
271
272 self.floating_windows_mut(window).clear();
273 for f in &w.floatings {
274 let Some(child) = self.import_subtree_from_layout(layout, f.root) else {
275 continue;
276 };
277 let floating = self.insert_node(DockNode::Floating { child });
278 self.floating_windows_mut(window).push(DockFloatingWindow {
279 floating,
280 rect: f.rect.to_rect(),
281 });
282 }
283
284 self.simplify_window_forest(window);
285 imported_any = true;
286 }
287
288 imported_any
289 }
290
291 pub fn import_layout_for_windows_checked(
292 &mut self,
293 layout: &crate::DockLayout,
294 windows: &[(AppWindowId, String)],
295 ) -> Result<bool, crate::DockLayoutValidationError> {
296 use std::collections::HashMap;
297
298 layout.validate()?;
299
300 let mut by_logical: HashMap<&str, AppWindowId> = HashMap::new();
301 for (window, logical_id) in windows {
302 by_logical.insert(logical_id.as_str(), *window);
303 }
304
305 let mut imported_any = false;
306 for w in &layout.windows {
307 let Some(window) = by_logical.get(w.logical_window_id.as_str()).copied() else {
308 continue;
309 };
310
311 let root = self.import_subtree_from_layout_checked(layout, w.root)?;
312 self.set_window_root(window, root);
313
314 self.floating_windows_mut(window).clear();
315 for f in &w.floatings {
316 let child = self.import_subtree_from_layout_checked(layout, f.root)?;
317 let floating = self.insert_node(DockNode::Floating { child });
318 self.floating_windows_mut(window).push(DockFloatingWindow {
319 floating,
320 rect: f.rect.to_rect(),
321 });
322 }
323
324 self.simplify_window_forest(window);
325 imported_any = true;
326 }
327
328 Ok(imported_any)
329 }
330
331 pub fn import_layout_for_windows_with_fallback_floatings(
338 &mut self,
339 layout: &crate::DockLayout,
340 windows: &[(AppWindowId, String)],
341 fallback_window: AppWindowId,
342 ) -> bool {
343 use std::collections::HashMap;
344
345 if layout.layout_version != crate::DOCK_LAYOUT_VERSION {
346 return false;
347 }
348
349 fn offset_rect(rect: Rect, delta: Point) -> Rect {
350 Rect::new(
351 Point::new(
352 Px(rect.origin.x.0 + delta.x.0),
353 Px(rect.origin.y.0 + delta.y.0),
354 ),
355 rect.size,
356 )
357 }
358
359 fn rect_for_unmapped_window(w: &crate::DockLayoutWindow, index: usize) -> Rect {
360 let default_w = 640.0;
361 let default_h = 480.0;
362 let (w_px, h_px) = w
363 .placement
364 .as_ref()
365 .map(|p| (p.width as f32, p.height as f32))
366 .unwrap_or((default_w, default_h));
367
368 let width = w_px.clamp(240.0, 1400.0);
369 let height = h_px.clamp(180.0, 1000.0);
370
371 let stagger = (index as f32).min(12.0) * 24.0;
372 Rect::new(
373 Point::new(Px(32.0 + stagger), Px(32.0 + stagger)),
374 Size::new(Px(width), Px(height)),
375 )
376 }
377
378 let mut by_logical: HashMap<&str, AppWindowId> = HashMap::new();
379 for (window, logical_id) in windows {
380 by_logical.insert(logical_id.as_str(), *window);
381 }
382
383 self.floating_windows_mut(fallback_window).clear();
386
387 let mut imported_any = false;
388 let mut unmapped_index: usize = 0;
389
390 for w in &layout.windows {
391 if let Some(window) = by_logical.get(w.logical_window_id.as_str()).copied() {
392 let Some(root) = self.import_subtree_from_layout(layout, w.root) else {
393 continue;
394 };
395 self.set_window_root(window, root);
396
397 self.floating_windows_mut(window).clear();
398 for f in &w.floatings {
399 let Some(child) = self.import_subtree_from_layout(layout, f.root) else {
400 continue;
401 };
402 let floating = self.insert_node(DockNode::Floating { child });
403 self.floating_windows_mut(window).push(DockFloatingWindow {
404 floating,
405 rect: f.rect.to_rect(),
406 });
407 }
408
409 self.simplify_window_forest(window);
410 imported_any = true;
411 continue;
412 }
413
414 let Some(child) = self.import_subtree_from_layout(layout, w.root) else {
416 continue;
417 };
418 let window_rect = rect_for_unmapped_window(w, unmapped_index);
419 let floating = self.insert_node(DockNode::Floating { child });
420 self.floating_windows_mut(fallback_window)
421 .push(DockFloatingWindow {
422 floating,
423 rect: window_rect,
424 });
425
426 for f in &w.floatings {
427 let Some(child) = self.import_subtree_from_layout(layout, f.root) else {
428 continue;
429 };
430 let floating = self.insert_node(DockNode::Floating { child });
431 self.floating_windows_mut(fallback_window)
432 .push(DockFloatingWindow {
433 floating,
434 rect: offset_rect(f.rect.to_rect(), window_rect.origin),
435 });
436 }
437
438 unmapped_index = unmapped_index.saturating_add(1);
439 imported_any = true;
440 }
441
442 self.simplify_window_forest(fallback_window);
443 imported_any
444 }
445
446 pub fn import_layout_for_windows_with_fallback_floatings_checked(
447 &mut self,
448 layout: &crate::DockLayout,
449 windows: &[(AppWindowId, String)],
450 fallback_window: AppWindowId,
451 ) -> Result<bool, crate::DockLayoutValidationError> {
452 use std::collections::HashMap;
453
454 layout.validate()?;
455
456 fn offset_rect(rect: Rect, delta: Point) -> Rect {
457 Rect::new(
458 Point::new(
459 Px(rect.origin.x.0 + delta.x.0),
460 Px(rect.origin.y.0 + delta.y.0),
461 ),
462 rect.size,
463 )
464 }
465
466 fn rect_for_unmapped_window(w: &crate::DockLayoutWindow, index: usize) -> Rect {
467 let default_w = 640.0;
468 let default_h = 480.0;
469 let (w_px, h_px) = w
470 .placement
471 .as_ref()
472 .map(|p| (p.width as f32, p.height as f32))
473 .unwrap_or((default_w, default_h));
474
475 let width = w_px.clamp(240.0, 1400.0);
476 let height = h_px.clamp(180.0, 1000.0);
477
478 let stagger = (index as f32).min(12.0) * 24.0;
479 Rect::new(
480 Point::new(Px(32.0 + stagger), Px(32.0 + stagger)),
481 Size::new(Px(width), Px(height)),
482 )
483 }
484
485 let mut by_logical: HashMap<&str, AppWindowId> = HashMap::new();
486 for (window, logical_id) in windows {
487 by_logical.insert(logical_id.as_str(), *window);
488 }
489
490 self.floating_windows_mut(fallback_window).clear();
491
492 let mut imported_any = false;
493 let mut unmapped_index: usize = 0;
494
495 for w in &layout.windows {
496 if let Some(window) = by_logical.get(w.logical_window_id.as_str()).copied() {
497 let root = self.import_subtree_from_layout_checked(layout, w.root)?;
498 self.set_window_root(window, root);
499
500 self.floating_windows_mut(window).clear();
501 for f in &w.floatings {
502 let child = self.import_subtree_from_layout_checked(layout, f.root)?;
503 let floating = self.insert_node(DockNode::Floating { child });
504 self.floating_windows_mut(window).push(DockFloatingWindow {
505 floating,
506 rect: f.rect.to_rect(),
507 });
508 }
509
510 self.simplify_window_forest(window);
511 imported_any = true;
512 continue;
513 }
514
515 let child = self.import_subtree_from_layout_checked(layout, w.root)?;
516 let window_rect = rect_for_unmapped_window(w, unmapped_index);
517 let floating = self.insert_node(DockNode::Floating { child });
518 self.floating_windows_mut(fallback_window)
519 .push(DockFloatingWindow {
520 floating,
521 rect: window_rect,
522 });
523
524 for f in &w.floatings {
525 let child = self.import_subtree_from_layout_checked(layout, f.root)?;
526 let floating = self.insert_node(DockNode::Floating { child });
527 self.floating_windows_mut(fallback_window)
528 .push(DockFloatingWindow {
529 floating,
530 rect: offset_rect(f.rect.to_rect(), window_rect.origin),
531 });
532 }
533
534 unmapped_index = unmapped_index.saturating_add(1);
535 imported_any = true;
536 }
537
538 self.simplify_window_forest(fallback_window);
539 Ok(imported_any)
540 }
541}