use std::collections::HashMap;
use fret_core::geometry::{Point, Rect};
use fret_core::{AppWindowId, Axis, DockNodeId, DropZone, PointerId, RenderTargetId};
use crate::DragKindId;
use crate::FrameId;
use crate::WindowUnderCursorSource;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DockDragDiagnostics {
pub pointer_id: PointerId,
pub source_window: AppWindowId,
pub current_window: AppWindowId,
pub position: Point,
pub start_position: Point,
pub cursor_grab_offset: Option<Point>,
pub follow_window: Option<AppWindowId>,
pub cursor_screen_pos_raw_physical_px: Option<Point>,
pub cursor_screen_pos_used_physical_px: Option<Point>,
pub cursor_screen_pos_was_clamped: bool,
pub cursor_override_active: bool,
pub current_window_outer_pos_physical_px: Option<Point>,
pub current_window_decoration_offset_physical_px: Option<Point>,
pub current_window_client_origin_screen_physical_px: Option<Point>,
pub current_window_client_origin_source_platform: bool,
pub current_window_scale_factor_x1000_from_runner: Option<u32>,
pub current_window_local_pos_from_screen_logical_px: Option<Point>,
pub current_window_scale_factor_x1000: Option<u32>,
pub kind: DragKindId,
pub dragging: bool,
pub cross_window_hover: bool,
pub payload_ghost_visible: bool,
pub transparent_payload_applied: bool,
pub transparent_payload_hit_test_passthrough_applied: bool,
pub window_under_cursor_source: WindowUnderCursorSource,
pub moving_window: Option<AppWindowId>,
pub moving_window_scale_factor_x1000: Option<u32>,
pub window_under_moving_window: Option<AppWindowId>,
pub window_under_moving_window_source: WindowUnderCursorSource,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DockFloatingDragDiagnostics {
pub pointer_id: PointerId,
pub floating: DockNodeId,
pub activated: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ViewportCaptureDiagnostics {
pub pointer_id: PointerId,
pub target: RenderTargetId,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DockTabStripActiveVisibilityStatusDiagnostics {
Ok,
MissingWindowRoot,
NoTabsFound,
MissingLayoutRect,
MissingTabsNode,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DockTabStripActiveVisibilityDiagnostics {
pub status: DockTabStripActiveVisibilityStatusDiagnostics,
pub tabs_node: Option<DockNodeId>,
pub overflow: bool,
pub tab_count: usize,
pub active: usize,
pub scroll: fret_core::geometry::Px,
pub max_scroll: fret_core::geometry::Px,
pub active_visible: bool,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct DockingInteractionDiagnostics {
pub dock_drag: Option<DockDragDiagnostics>,
pub floating_drag: Option<DockFloatingDragDiagnostics>,
pub dock_drop_resolve: Option<DockDropResolveDiagnostics>,
pub viewport_capture: Option<ViewportCaptureDiagnostics>,
pub tab_strip_active_visibility: Option<DockTabStripActiveVisibilityDiagnostics>,
pub dock_graph_stats: Option<DockGraphStatsDiagnostics>,
pub dock_graph_signature: Option<DockGraphSignatureDiagnostics>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WorkspaceTabStripActiveVisibilityStatusDiagnostics {
Ok,
NoActiveTab,
MissingScrollViewportRect,
MissingActiveTabRect,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WorkspaceTabStripActiveVisibilityDiagnostics {
pub status: WorkspaceTabStripActiveVisibilityStatusDiagnostics,
pub pane_id: Option<std::sync::Arc<str>>,
pub active_tab_id: Option<std::sync::Arc<str>>,
pub tab_count: usize,
pub overflow: bool,
pub scroll_x: fret_core::geometry::Px,
pub max_scroll_x: fret_core::geometry::Px,
pub scroll_viewport_rect: Option<Rect>,
pub active_tab_rect: Option<Rect>,
pub active_visible: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WorkspaceTabStripDragDiagnostics {
pub pane_id: Option<std::sync::Arc<str>>,
pub pointer_id: Option<PointerId>,
pub dragging: bool,
pub dragged_tab_id: Option<std::sync::Arc<str>>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct WorkspaceInteractionDiagnostics {
pub tab_strip_active_visibility: Vec<WorkspaceTabStripActiveVisibilityDiagnostics>,
pub tab_strip_drag: Vec<WorkspaceTabStripDragDiagnostics>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DockGraphSignatureDiagnostics {
pub signature: String,
pub fingerprint64: u64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DockGraphStatsDiagnostics {
pub node_count: u32,
pub tabs_count: u32,
pub split_count: u32,
pub floating_count: u32,
pub max_depth: u32,
pub max_split_depth: u32,
pub canonical_ok: bool,
pub has_nested_same_axis_splits: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DockDropPreviewKindDiagnostics {
WrapBinary,
InsertIntoSplit {
axis: Axis,
split: DockNodeId,
insert_index: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DockDropPreviewDiagnostics {
pub kind: DockDropPreviewKindDiagnostics,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DockDropResolveSource {
InvertDocking,
OutsideWindow,
FloatZone,
EmptyDockSpace,
LayoutBoundsMiss,
LatchedPreviousHover,
TabBar,
FloatingTitleBar,
OuterHintRect,
InnerHintRect,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DockDropCandidateRectKind {
WindowBounds,
DockBounds,
FloatZone,
LayoutBounds,
RootRect,
LeafTabsRect,
TabBarRect,
InnerHintRect,
OuterHintRect,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DockDropCandidateRectDiagnostics {
pub kind: DockDropCandidateRectKind,
pub zone: Option<DropZone>,
pub rect: Rect,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DockDropTargetDiagnostics {
pub layout_root: DockNodeId,
pub tabs: DockNodeId,
pub zone: DropZone,
pub insert_index: Option<usize>,
pub outer: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DockDropResolveDiagnostics {
pub pointer_id: PointerId,
pub position: Point,
pub window_bounds: Rect,
pub dock_bounds: Rect,
pub source: DockDropResolveSource,
pub resolved: Option<DockDropTargetDiagnostics>,
pub preview: Option<DockDropPreviewDiagnostics>,
pub candidates: Vec<DockDropCandidateRectDiagnostics>,
}
#[derive(Default)]
pub struct WindowInteractionDiagnosticsStore {
per_window: HashMap<AppWindowId, WindowInteractionDiagnosticsFrame>,
}
#[derive(Default)]
struct WindowInteractionDiagnosticsFrame {
frame_id: FrameId,
docking: DockingInteractionDiagnostics,
latest_docking: DockingInteractionDiagnostics,
workspace: WorkspaceInteractionDiagnostics,
latest_workspace: WorkspaceInteractionDiagnostics,
}
impl WindowInteractionDiagnosticsStore {
pub fn begin_frame(&mut self, window: AppWindowId, frame_id: FrameId) {
let w = self.per_window.entry(window).or_default();
if w.frame_id != frame_id {
w.frame_id = frame_id;
w.docking = DockingInteractionDiagnostics::default();
w.workspace = WorkspaceInteractionDiagnostics::default();
}
}
pub fn record_docking(
&mut self,
window: AppWindowId,
frame_id: FrameId,
diagnostics: DockingInteractionDiagnostics,
) {
self.begin_frame(window, frame_id);
let w = self.per_window.entry(window).or_default();
w.docking = diagnostics.clone();
w.latest_docking = diagnostics;
}
pub fn record_workspace_tab_strip_active_visibility(
&mut self,
window: AppWindowId,
frame_id: FrameId,
diagnostics: WorkspaceTabStripActiveVisibilityDiagnostics,
) {
self.begin_frame(window, frame_id);
let w = self.per_window.entry(window).or_default();
w.workspace.tab_strip_active_visibility.push(diagnostics);
w.latest_workspace = w.workspace.clone();
}
pub fn record_workspace_tab_strip_drag(
&mut self,
window: AppWindowId,
frame_id: FrameId,
diagnostics: WorkspaceTabStripDragDiagnostics,
) {
self.begin_frame(window, frame_id);
let w = self.per_window.entry(window).or_default();
w.workspace.tab_strip_drag.push(diagnostics);
w.latest_workspace = w.workspace.clone();
}
pub fn docking_for_window(
&self,
window: AppWindowId,
frame_id: FrameId,
) -> Option<&DockingInteractionDiagnostics> {
let w = self.per_window.get(&window)?;
(w.frame_id == frame_id).then_some(&w.docking)
}
pub fn workspace_for_window(
&self,
window: AppWindowId,
frame_id: FrameId,
) -> Option<&WorkspaceInteractionDiagnostics> {
let w = self.per_window.get(&window)?;
(w.frame_id == frame_id).then_some(&w.workspace)
}
pub fn docking_latest_for_window(
&self,
window: AppWindowId,
) -> Option<&DockingInteractionDiagnostics> {
self.per_window.get(&window).map(|w| &w.latest_docking)
}
pub fn workspace_latest_for_window(
&self,
window: AppWindowId,
) -> Option<&WorkspaceInteractionDiagnostics> {
self.per_window.get(&window).map(|w| &w.latest_workspace)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn docking_latest_is_stable_across_begin_frame_resets() {
let mut store = WindowInteractionDiagnosticsStore::default();
let window = AppWindowId::default();
let snapshot = DockingInteractionDiagnostics {
dock_graph_stats: Some(DockGraphStatsDiagnostics {
node_count: 3,
tabs_count: 1,
split_count: 1,
floating_count: 0,
max_depth: 2,
max_split_depth: 1,
canonical_ok: true,
has_nested_same_axis_splits: false,
}),
..Default::default()
};
store.record_docking(window, FrameId(1), snapshot);
store.begin_frame(window, FrameId(2));
assert!(
store
.docking_latest_for_window(window)
.and_then(|d| d.dock_graph_stats)
.is_some_and(|s| s.canonical_ok),
"latest snapshot should persist even when the current frame snapshot is reset"
);
assert!(
store
.docking_for_window(window, FrameId(2))
.is_some_and(|d| d.dock_graph_stats.is_none()),
"frame-scoped snapshot should be cleared by begin_frame when not recorded"
);
}
#[test]
fn workspace_latest_is_stable_across_begin_frame_resets() {
let mut store = WindowInteractionDiagnosticsStore::default();
let window = AppWindowId::default();
let snapshot = WorkspaceTabStripActiveVisibilityDiagnostics {
status: WorkspaceTabStripActiveVisibilityStatusDiagnostics::Ok,
pane_id: Some(std::sync::Arc::<str>::from("pane-a")),
active_tab_id: Some(std::sync::Arc::<str>::from("doc-a-2")),
tab_count: 3,
overflow: true,
scroll_x: fret_core::geometry::Px(12.0),
max_scroll_x: fret_core::geometry::Px(120.0),
scroll_viewport_rect: None,
active_tab_rect: None,
active_visible: true,
};
store.record_workspace_tab_strip_active_visibility(window, FrameId(1), snapshot);
store.begin_frame(window, FrameId(2));
assert!(
store
.workspace_latest_for_window(window)
.is_some_and(|w| !w.tab_strip_active_visibility.is_empty()),
"latest snapshot should persist even when the current frame snapshot is reset"
);
assert!(
store
.workspace_for_window(window, FrameId(2))
.is_some_and(|w| w.tab_strip_active_visibility.is_empty()),
"frame-scoped snapshot should be cleared by begin_frame when not recorded"
);
}
}