Skip to main content

fret_core/dock/
mod.rs

1mod apply;
2pub(super) mod layout;
3mod mutate;
4pub(super) mod op;
5mod persistence;
6mod query;
7use self::op::DockOp;
8use crate::{
9    PanelKey,
10    geometry::{Point, Px, Rect, Size},
11    ids::{AppWindowId, DockNodeId},
12};
13use slotmap::{Key, SlotMap};
14use std::collections::HashMap;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17pub enum Axis {
18    Horizontal,
19    Vertical,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum DropZone {
24    Center,
25    Left,
26    Right,
27    Top,
28    Bottom,
29}
30
31#[derive(Debug, Clone)]
32pub enum DockNode {
33    Split {
34        axis: Axis,
35        children: Vec<DockNodeId>,
36        fractions: Vec<f32>,
37    },
38    Tabs {
39        tabs: Vec<PanelKey>,
40        active: usize,
41    },
42    /// An in-window floating dock container (ImGui docking, viewports disabled).
43    ///
44    /// The container node is stable: docking within the floating window replaces `child` while
45    /// keeping the container id stable. Window metadata (rect, z-order) is stored in `DockGraph`.
46    Floating {
47        child: DockNodeId,
48    },
49}
50
51/// A pure decision describing how an edge-dock operation will commit in the core graph.
52///
53/// This is intended to be consumed by the docking UI layer so that preview geometry matches core
54/// semantics (avoids “preview vs commit” drift).
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum EdgeDockDecision {
57    /// Insert a new tabs node into an existing same-axis split container.
58    ///
59    /// - `split` is the container being inserted into.
60    /// - `anchor_index` is the existing child whose share is split in half (v1 default).
61    /// - `insert_index` is where the new child is inserted.
62    InsertIntoSplit {
63        split: DockNodeId,
64        anchor_index: usize,
65        insert_index: usize,
66    },
67
68    /// Wrap the target in a new split container (legacy behavior / fallback).
69    WrapNewSplit,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq)]
73pub struct DockFloatingWindow {
74    pub floating: DockNodeId,
75    pub rect: Rect,
76}
77
78#[derive(Debug, Default)]
79pub struct DockGraph {
80    nodes: SlotMap<DockNodeId, DockNode>,
81    window_roots: HashMap<AppWindowId, DockNodeId>,
82    window_floatings: HashMap<AppWindowId, Vec<DockFloatingWindow>>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct DockOpApplyError {
87    pub kind: DockOpApplyErrorKind,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum DockOpApplyErrorKind {
92    UnsupportedOp,
93    TabsNodeNotFound {
94        tabs: DockNodeId,
95    },
96    NodeIsNotTabs {
97        node: DockNodeId,
98    },
99    ActiveOutOfBounds {
100        tabs: DockNodeId,
101        active: usize,
102        len: usize,
103    },
104    PanelNotFound {
105        window: AppWindowId,
106        panel: PanelKey,
107    },
108    OperationFailed,
109}
110
111impl std::fmt::Display for DockOpApplyError {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        write!(f, "dock op apply error: {:?}", self.kind)
114    }
115}
116
117impl std::error::Error for DockOpApplyError {}
118
119impl DockGraph {
120    pub fn new() -> Self {
121        Self::default()
122    }
123
124    pub fn insert_node(&mut self, node: DockNode) -> DockNodeId {
125        self.nodes.insert(node)
126    }
127
128    pub fn node(&self, id: DockNodeId) -> Option<&DockNode> {
129        self.nodes.get(id)
130    }
131
132    pub fn node_mut(&mut self, id: DockNodeId) -> Option<&mut DockNode> {
133        self.nodes.get_mut(id)
134    }
135
136    pub fn set_window_root(&mut self, window: AppWindowId, root: DockNodeId) {
137        self.window_roots.insert(window, root);
138    }
139
140    pub fn window_root(&self, window: AppWindowId) -> Option<DockNodeId> {
141        self.window_roots.get(&window).copied()
142    }
143
144    pub fn remove_window_root(&mut self, window: AppWindowId) -> Option<DockNodeId> {
145        self.window_roots.remove(&window)
146    }
147
148    pub fn floating_windows(&self, window: AppWindowId) -> &[DockFloatingWindow] {
149        self.window_floatings
150            .get(&window)
151            .map(|v| v.as_slice())
152            .unwrap_or(&[])
153    }
154
155    pub fn floating_windows_mut(&mut self, window: AppWindowId) -> &mut Vec<DockFloatingWindow> {
156        self.window_floatings.entry(window).or_default()
157    }
158
159    // DockOp application lives in `apply.rs` to keep the main dock graph module focused on the
160    // runtime tree and core mutation primitives.
161}
162
163#[cfg(test)]
164mod tests;