1use super::*;
2
3#[derive(Debug, Default)]
4struct DockParentIndex {
5 root_for: HashMap<DockNodeId, DockNodeId>,
6 parent: HashMap<DockNodeId, DockNodeId>,
7 split_child_index: HashMap<DockNodeId, usize>,
8}
9
10impl DockGraph {
11 fn build_parent_index_for_window(&self, window: AppWindowId) -> DockParentIndex {
12 fn index_subtree(graph: &DockGraph, root: DockNodeId, index: &mut DockParentIndex) {
13 let mut stack: Vec<DockNodeId> = vec![root];
14 while let Some(node) = stack.pop() {
15 if index.root_for.contains_key(&node) {
16 continue;
17 }
18 index.root_for.insert(node, root);
19
20 let Some(n) = graph.nodes.get(node) else {
21 continue;
22 };
23 match n {
24 DockNode::Tabs { .. } => {}
25 DockNode::Floating { child } => {
26 index.parent.insert(*child, node);
27 stack.push(*child);
28 }
29 DockNode::Split { children, .. } => {
30 for (i, child) in children.iter().copied().enumerate() {
31 index.parent.insert(child, node);
32 index.split_child_index.insert(child, i);
33 stack.push(child);
34 }
35 }
36 }
37 }
38 }
39
40 let mut index = DockParentIndex::default();
41 if let Some(root) = self.window_root(window) {
42 index_subtree(self, root, &mut index);
43 }
44 if let Some(list) = self.window_floatings.get(&window) {
45 for w in list {
46 index_subtree(self, w.floating, &mut index);
47 }
48 }
49 index
50 }
51
52 pub fn edge_dock_decision(
58 &self,
59 window: AppWindowId,
60 target: DockNodeId,
61 zone: DropZone,
62 ) -> Option<EdgeDockDecision> {
63 if zone == DropZone::Center {
64 return None;
65 }
66
67 let axis = match zone {
68 DropZone::Left | DropZone::Right => Axis::Horizontal,
69 DropZone::Top | DropZone::Bottom => Axis::Vertical,
70 DropZone::Center => unreachable!(),
71 };
72
73 let index = self.build_parent_index_for_window(window);
79 if !index.root_for.contains_key(&target) {
80 return None;
81 }
82
83 if let Some(DockNode::Split {
86 axis: split_axis,
87 children,
88 fractions,
89 }) = self.nodes.get(target)
90 && *split_axis == axis
91 && !children.is_empty()
92 && children.len() == fractions.len()
93 {
94 let len = children.len();
95 let (anchor_index, insert_index) = match zone {
96 DropZone::Left | DropZone::Top => (0, 0),
97 DropZone::Right | DropZone::Bottom => {
98 let last = len.saturating_sub(1);
99 (last, last.saturating_add(1))
100 }
101 DropZone::Center => unreachable!(),
102 };
103 return Some(EdgeDockDecision::InsertIntoSplit {
104 split: target,
105 anchor_index,
106 insert_index,
107 });
108 }
109
110 let mut cur = target;
111 while let Some(parent) = index.parent.get(&cur).copied() {
112 let Some(DockNode::Split {
113 axis: split_axis,
114 children,
115 fractions,
116 }) = self.nodes.get(parent)
117 else {
118 cur = parent;
119 continue;
120 };
121
122 if *split_axis == axis && !children.is_empty() && children.len() == fractions.len() {
123 let Some(anchor_index) = index.split_child_index.get(&cur).copied() else {
124 break;
125 };
126
127 let insert_index = match zone {
128 DropZone::Left | DropZone::Top => anchor_index,
129 DropZone::Right | DropZone::Bottom => anchor_index.saturating_add(1),
130 DropZone::Center => unreachable!(),
131 };
132
133 return Some(EdgeDockDecision::InsertIntoSplit {
134 split: parent,
135 anchor_index,
136 insert_index,
137 });
138 }
139
140 cur = parent;
141 }
142
143 Some(EdgeDockDecision::WrapNewSplit)
144 }
145
146 pub fn compute_layout(
147 &self,
148 root: DockNodeId,
149 bounds: Rect,
150 out: &mut HashMap<DockNodeId, Rect>,
151 ) {
152 let Some(node) = self.nodes.get(root) else {
153 return;
154 };
155
156 out.insert(root, bounds);
157 match node {
158 DockNode::Tabs { .. } => {}
159 DockNode::Split {
160 axis,
161 children,
162 fractions,
163 } => {
164 if children.is_empty() {
165 return;
166 }
167
168 let cleaned_share_at = |i: usize| -> f32 {
172 let raw = fractions.get(i).copied().unwrap_or(1.0);
173 if raw.is_finite() && raw > 0.0 {
174 raw
175 } else {
176 0.0
177 }
178 };
179
180 let mut total = 0.0;
181 for i in 0..children.len() {
182 total += cleaned_share_at(i);
183 }
184 let uniform = 1.0 / children.len() as f32;
185 let inv_total = if total > 0.0 { 1.0 / total } else { 0.0 };
186
187 let mut cursor = 0.0;
188 for (i, child) in children.iter().copied().enumerate() {
189 let f = if total > 0.0 {
190 cleaned_share_at(i) * inv_total
191 } else {
192 uniform
193 };
194 let (child_rect, next_cursor) = match axis {
195 Axis::Horizontal => {
196 let w = bounds.size.width.0 * f;
197 let rect = Rect {
198 origin: Point::new(Px(bounds.origin.x.0 + cursor), bounds.origin.y),
199 size: Size::new(Px(w), bounds.size.height),
200 };
201 (rect, cursor + w)
202 }
203 Axis::Vertical => {
204 let h = bounds.size.height.0 * f;
205 let rect = Rect {
206 origin: Point::new(bounds.origin.x, Px(bounds.origin.y.0 + cursor)),
207 size: Size::new(bounds.size.width, Px(h)),
208 };
209 (rect, cursor + h)
210 }
211 };
212
213 cursor = next_cursor;
214 self.compute_layout(child, child_rect, out);
215 }
216 }
217 DockNode::Floating { child } => {
218 self.compute_layout(*child, bounds, out);
219 }
220 }
221 }
222
223 pub fn find_panel_in_window(
224 &self,
225 window: AppWindowId,
226 panel: &PanelKey,
227 ) -> Option<(DockNodeId, usize)> {
228 if let Some(root) = self.window_root(window)
229 && let Some(found) = self.find_panel_in_subtree(root, panel)
230 {
231 return Some(found);
232 }
233
234 self.window_floatings.get(&window).and_then(|list| {
235 list.iter()
236 .find_map(|w| self.find_panel_in_subtree(w.floating, panel))
237 })
238 }
239
240 pub fn windows(&self) -> Vec<AppWindowId> {
241 let mut windows: Vec<AppWindowId> = self.window_roots.keys().copied().collect();
242 windows.sort_by_key(|w| w.data().as_ffi());
243 windows
244 }
245
246 pub fn collect_panels_in_window(&self, window: AppWindowId) -> Vec<PanelKey> {
247 let mut out: Vec<PanelKey> = Vec::new();
248 if let Some(root) = self.window_root(window) {
249 out.extend(self.collect_panels_in_subtree(root));
250 }
251 if let Some(list) = self.window_floatings.get(&window) {
252 for w in list {
253 out.extend(self.collect_panels_in_subtree(w.floating));
254 }
255 }
256 out
257 }
258
259 pub fn first_tabs_in_window(&self, window: AppWindowId) -> Option<DockNodeId> {
260 if let Some(root) = self.window_root(window)
261 && let Some(tabs) = self.first_tabs_in_subtree(root)
262 {
263 return Some(tabs);
264 }
265
266 self.window_floatings.get(&window).and_then(|list| {
267 list.iter()
268 .find_map(|w| self.first_tabs_in_subtree(w.floating))
269 })
270 }
271
272 pub(super) fn collect_panels_in_subtree(&self, node: DockNodeId) -> Vec<PanelKey> {
273 let Some(n) = self.nodes.get(node) else {
274 return Vec::new();
275 };
276 match n {
277 DockNode::Tabs { tabs, .. } => tabs.clone(),
278 DockNode::Split { children, .. } => {
279 let mut out = Vec::new();
280 for child in children {
281 out.extend(self.collect_panels_in_subtree(*child));
282 }
283 out
284 }
285 DockNode::Floating { child } => self.collect_panels_in_subtree(*child),
286 }
287 }
288
289 fn first_tabs_in_subtree(&self, node: DockNodeId) -> Option<DockNodeId> {
290 let n = self.nodes.get(node)?;
291 match n {
292 DockNode::Tabs { .. } => Some(node),
293 DockNode::Split { children, .. } => children
294 .iter()
295 .copied()
296 .find_map(|child| self.first_tabs_in_subtree(child)),
297 DockNode::Floating { child } => self.first_tabs_in_subtree(*child),
298 }
299 }
300
301 fn find_panel_in_subtree(
302 &self,
303 node: DockNodeId,
304 panel: &PanelKey,
305 ) -> Option<(DockNodeId, usize)> {
306 let n = self.nodes.get(node)?;
307 match n {
308 DockNode::Tabs { tabs, .. } => tabs.iter().position(|p| p == panel).map(|i| (node, i)),
309 DockNode::Split { children, .. } => children
310 .iter()
311 .copied()
312 .find_map(|child| self.find_panel_in_subtree(child, panel)),
313 DockNode::Floating { child } => self.find_panel_in_subtree(*child, panel),
314 }
315 }
316}