1use std::collections::HashMap;
2
3use fret_core::geometry::{Point, Rect};
4use fret_core::{AppWindowId, Axis, DockNodeId, DropZone, PointerId, RenderTargetId};
5
6use crate::DragKindId;
7use crate::FrameId;
8use crate::WindowUnderCursorSource;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct DockDragDiagnostics {
12 pub pointer_id: PointerId,
13 pub source_window: AppWindowId,
14 pub current_window: AppWindowId,
15 pub position: Point,
17 pub start_position: Point,
19 pub cursor_grab_offset: Option<Point>,
21 pub follow_window: Option<AppWindowId>,
23 pub cursor_screen_pos_raw_physical_px: Option<Point>,
26 pub cursor_screen_pos_used_physical_px: Option<Point>,
29 pub cursor_screen_pos_was_clamped: bool,
30 pub cursor_override_active: bool,
31 pub current_window_outer_pos_physical_px: Option<Point>,
34 pub current_window_decoration_offset_physical_px: Option<Point>,
37 pub current_window_client_origin_screen_physical_px: Option<Point>,
40 pub current_window_client_origin_source_platform: bool,
41 pub current_window_scale_factor_x1000_from_runner: Option<u32>,
44 pub current_window_local_pos_from_screen_logical_px: Option<Point>,
47 pub current_window_scale_factor_x1000: Option<u32>,
50 pub kind: DragKindId,
52 pub dragging: bool,
53 pub cross_window_hover: bool,
54 pub payload_ghost_visible: bool,
58 pub transparent_payload_applied: bool,
61 pub transparent_payload_hit_test_passthrough_applied: bool,
64 pub window_under_cursor_source: WindowUnderCursorSource,
67 pub moving_window: Option<AppWindowId>,
70 pub moving_window_scale_factor_x1000: Option<u32>,
73 pub window_under_moving_window: Option<AppWindowId>,
76 pub window_under_moving_window_source: WindowUnderCursorSource,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct DockFloatingDragDiagnostics {
83 pub pointer_id: PointerId,
84 pub floating: DockNodeId,
85 pub activated: bool,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub struct ViewportCaptureDiagnostics {
90 pub pointer_id: PointerId,
91 pub target: RenderTargetId,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq)]
95pub enum DockTabStripActiveVisibilityStatusDiagnostics {
96 Ok,
97 MissingWindowRoot,
98 NoTabsFound,
99 MissingLayoutRect,
100 MissingTabsNode,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq)]
104pub struct DockTabStripActiveVisibilityDiagnostics {
105 pub status: DockTabStripActiveVisibilityStatusDiagnostics,
106 pub tabs_node: Option<DockNodeId>,
107 pub overflow: bool,
109 pub tab_count: usize,
110 pub active: usize,
111 pub scroll: fret_core::geometry::Px,
112 pub max_scroll: fret_core::geometry::Px,
113 pub active_visible: bool,
115}
116
117#[derive(Debug, Clone, PartialEq, Default)]
118pub struct DockingInteractionDiagnostics {
119 pub dock_drag: Option<DockDragDiagnostics>,
120 pub floating_drag: Option<DockFloatingDragDiagnostics>,
121 pub dock_drop_resolve: Option<DockDropResolveDiagnostics>,
122 pub viewport_capture: Option<ViewportCaptureDiagnostics>,
123 pub tab_strip_active_visibility: Option<DockTabStripActiveVisibilityDiagnostics>,
125 pub dock_graph_stats: Option<DockGraphStatsDiagnostics>,
127 pub dock_graph_signature: Option<DockGraphSignatureDiagnostics>,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq)]
135pub enum WorkspaceTabStripActiveVisibilityStatusDiagnostics {
136 Ok,
137 NoActiveTab,
138 MissingScrollViewportRect,
139 MissingActiveTabRect,
140}
141
142#[derive(Debug, Clone, PartialEq)]
143pub struct WorkspaceTabStripActiveVisibilityDiagnostics {
144 pub status: WorkspaceTabStripActiveVisibilityStatusDiagnostics,
145 pub pane_id: Option<std::sync::Arc<str>>,
146 pub active_tab_id: Option<std::sync::Arc<str>>,
147 pub tab_count: usize,
148 pub overflow: bool,
149 pub scroll_x: fret_core::geometry::Px,
150 pub max_scroll_x: fret_core::geometry::Px,
151 pub scroll_viewport_rect: Option<Rect>,
152 pub active_tab_rect: Option<Rect>,
153 pub active_visible: bool,
154}
155
156#[derive(Debug, Clone, PartialEq)]
157pub struct WorkspaceTabStripDragDiagnostics {
158 pub pane_id: Option<std::sync::Arc<str>>,
159 pub pointer_id: Option<PointerId>,
160 pub dragging: bool,
161 pub dragged_tab_id: Option<std::sync::Arc<str>>,
162}
163
164#[derive(Debug, Clone, PartialEq, Default)]
165pub struct WorkspaceInteractionDiagnostics {
166 pub tab_strip_active_visibility: Vec<WorkspaceTabStripActiveVisibilityDiagnostics>,
171 pub tab_strip_drag: Vec<WorkspaceTabStripDragDiagnostics>,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct DockGraphSignatureDiagnostics {
180 pub signature: String,
186 pub fingerprint64: u64,
188}
189
190#[derive(Debug, Clone, Copy, PartialEq)]
191pub struct DockGraphStatsDiagnostics {
192 pub node_count: u32,
193 pub tabs_count: u32,
194 pub split_count: u32,
195 pub floating_count: u32,
196 pub max_depth: u32,
197 pub max_split_depth: u32,
198 pub canonical_ok: bool,
200 pub has_nested_same_axis_splits: bool,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205pub enum DockDropPreviewKindDiagnostics {
206 WrapBinary,
207 InsertIntoSplit {
208 axis: Axis,
209 split: DockNodeId,
210 insert_index: usize,
211 },
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub struct DockDropPreviewDiagnostics {
216 pub kind: DockDropPreviewKindDiagnostics,
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum DockDropResolveSource {
221 InvertDocking,
223 OutsideWindow,
225 FloatZone,
227 EmptyDockSpace,
231 LayoutBoundsMiss,
233 LatchedPreviousHover,
235 TabBar,
237 FloatingTitleBar,
239 OuterHintRect,
241 InnerHintRect,
243 None,
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum DockDropCandidateRectKind {
249 WindowBounds,
250 DockBounds,
251 FloatZone,
252 LayoutBounds,
253 RootRect,
254 LeafTabsRect,
255 TabBarRect,
256 InnerHintRect,
257 OuterHintRect,
258}
259
260#[derive(Debug, Clone, Copy, PartialEq)]
261pub struct DockDropCandidateRectDiagnostics {
262 pub kind: DockDropCandidateRectKind,
263 pub zone: Option<DropZone>,
264 pub rect: Rect,
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
268pub struct DockDropTargetDiagnostics {
269 pub layout_root: DockNodeId,
270 pub tabs: DockNodeId,
271 pub zone: DropZone,
272 pub insert_index: Option<usize>,
273 pub outer: bool,
274}
275
276#[derive(Debug, Clone, PartialEq)]
277pub struct DockDropResolveDiagnostics {
278 pub pointer_id: PointerId,
279 pub position: Point,
280 pub window_bounds: Rect,
281 pub dock_bounds: Rect,
282 pub source: DockDropResolveSource,
283 pub resolved: Option<DockDropTargetDiagnostics>,
284 pub preview: Option<DockDropPreviewDiagnostics>,
285 pub candidates: Vec<DockDropCandidateRectDiagnostics>,
286}
287
288#[derive(Default)]
289pub struct WindowInteractionDiagnosticsStore {
290 per_window: HashMap<AppWindowId, WindowInteractionDiagnosticsFrame>,
291}
292
293#[derive(Default)]
294struct WindowInteractionDiagnosticsFrame {
295 frame_id: FrameId,
296 docking: DockingInteractionDiagnostics,
297 latest_docking: DockingInteractionDiagnostics,
298 workspace: WorkspaceInteractionDiagnostics,
299 latest_workspace: WorkspaceInteractionDiagnostics,
300}
301
302impl WindowInteractionDiagnosticsStore {
303 pub fn begin_frame(&mut self, window: AppWindowId, frame_id: FrameId) {
304 let w = self.per_window.entry(window).or_default();
305 if w.frame_id != frame_id {
306 w.frame_id = frame_id;
307 w.docking = DockingInteractionDiagnostics::default();
308 w.workspace = WorkspaceInteractionDiagnostics::default();
309 }
310 }
311
312 pub fn record_docking(
313 &mut self,
314 window: AppWindowId,
315 frame_id: FrameId,
316 diagnostics: DockingInteractionDiagnostics,
317 ) {
318 self.begin_frame(window, frame_id);
319 let w = self.per_window.entry(window).or_default();
320 w.docking = diagnostics.clone();
321 w.latest_docking = diagnostics;
322 }
323
324 pub fn record_workspace_tab_strip_active_visibility(
325 &mut self,
326 window: AppWindowId,
327 frame_id: FrameId,
328 diagnostics: WorkspaceTabStripActiveVisibilityDiagnostics,
329 ) {
330 self.begin_frame(window, frame_id);
331 let w = self.per_window.entry(window).or_default();
332 w.workspace.tab_strip_active_visibility.push(diagnostics);
333 w.latest_workspace = w.workspace.clone();
334 }
335
336 pub fn record_workspace_tab_strip_drag(
337 &mut self,
338 window: AppWindowId,
339 frame_id: FrameId,
340 diagnostics: WorkspaceTabStripDragDiagnostics,
341 ) {
342 self.begin_frame(window, frame_id);
343 let w = self.per_window.entry(window).or_default();
344 w.workspace.tab_strip_drag.push(diagnostics);
345 w.latest_workspace = w.workspace.clone();
346 }
347
348 pub fn docking_for_window(
349 &self,
350 window: AppWindowId,
351 frame_id: FrameId,
352 ) -> Option<&DockingInteractionDiagnostics> {
353 let w = self.per_window.get(&window)?;
354 (w.frame_id == frame_id).then_some(&w.docking)
355 }
356
357 pub fn workspace_for_window(
358 &self,
359 window: AppWindowId,
360 frame_id: FrameId,
361 ) -> Option<&WorkspaceInteractionDiagnostics> {
362 let w = self.per_window.get(&window)?;
363 (w.frame_id == frame_id).then_some(&w.workspace)
364 }
365
366 pub fn docking_latest_for_window(
367 &self,
368 window: AppWindowId,
369 ) -> Option<&DockingInteractionDiagnostics> {
370 self.per_window.get(&window).map(|w| &w.latest_docking)
371 }
372
373 pub fn workspace_latest_for_window(
374 &self,
375 window: AppWindowId,
376 ) -> Option<&WorkspaceInteractionDiagnostics> {
377 self.per_window.get(&window).map(|w| &w.latest_workspace)
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn docking_latest_is_stable_across_begin_frame_resets() {
387 let mut store = WindowInteractionDiagnosticsStore::default();
388 let window = AppWindowId::default();
389
390 let snapshot = DockingInteractionDiagnostics {
391 dock_graph_stats: Some(DockGraphStatsDiagnostics {
392 node_count: 3,
393 tabs_count: 1,
394 split_count: 1,
395 floating_count: 0,
396 max_depth: 2,
397 max_split_depth: 1,
398 canonical_ok: true,
399 has_nested_same_axis_splits: false,
400 }),
401 ..Default::default()
402 };
403
404 store.record_docking(window, FrameId(1), snapshot);
405 store.begin_frame(window, FrameId(2));
406
407 assert!(
408 store
409 .docking_latest_for_window(window)
410 .and_then(|d| d.dock_graph_stats)
411 .is_some_and(|s| s.canonical_ok),
412 "latest snapshot should persist even when the current frame snapshot is reset"
413 );
414
415 assert!(
416 store
417 .docking_for_window(window, FrameId(2))
418 .is_some_and(|d| d.dock_graph_stats.is_none()),
419 "frame-scoped snapshot should be cleared by begin_frame when not recorded"
420 );
421 }
422
423 #[test]
424 fn workspace_latest_is_stable_across_begin_frame_resets() {
425 let mut store = WindowInteractionDiagnosticsStore::default();
426 let window = AppWindowId::default();
427
428 let snapshot = WorkspaceTabStripActiveVisibilityDiagnostics {
429 status: WorkspaceTabStripActiveVisibilityStatusDiagnostics::Ok,
430 pane_id: Some(std::sync::Arc::<str>::from("pane-a")),
431 active_tab_id: Some(std::sync::Arc::<str>::from("doc-a-2")),
432 tab_count: 3,
433 overflow: true,
434 scroll_x: fret_core::geometry::Px(12.0),
435 max_scroll_x: fret_core::geometry::Px(120.0),
436 scroll_viewport_rect: None,
437 active_tab_rect: None,
438 active_visible: true,
439 };
440
441 store.record_workspace_tab_strip_active_visibility(window, FrameId(1), snapshot);
442 store.begin_frame(window, FrameId(2));
443
444 assert!(
445 store
446 .workspace_latest_for_window(window)
447 .is_some_and(|w| !w.tab_strip_active_visibility.is_empty()),
448 "latest snapshot should persist even when the current frame snapshot is reset"
449 );
450
451 assert!(
452 store
453 .workspace_for_window(window, FrameId(2))
454 .is_some_and(|w| w.tab_strip_active_visibility.is_empty()),
455 "frame-scoped snapshot should be cleared by begin_frame when not recorded"
456 );
457 }
458}