fret_runtime/drag.rs
1use std::any::Any;
2
3use fret_core::{AppWindowId, Point, PointerId};
4
5/// Best-effort diagnostics hint: which mechanism was used to select the hovered window during a
6/// cross-window drag session.
7///
8/// This is primarily intended for multi-window docking diagnostics ("hovered window under cursor"
9/// selection under overlap), so bundles can answer whether the runner used an OS-backed path or a
10/// heuristic fallback.
11#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum WindowUnderCursorSource {
13 /// No source information is available (or the runner has not attempted selection yet).
14 #[default]
15 Unknown,
16 /// OS-backed Win32 window-under-cursor selection (z-order traversal).
17 PlatformWin32,
18 /// OS-backed macOS window-under-cursor selection.
19 PlatformMacos,
20 /// Stable latch (reuse the previously hovered window while the cursor remains inside it).
21 Latched,
22 /// Runner-maintained z-order list / rect scan (best-effort heuristic).
23 HeuristicZOrder,
24 /// Full window-rect scan (best-effort heuristic).
25 HeuristicRects,
26}
27
28#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct DragKindId(pub u64);
30
31pub const DRAG_KIND_DOCK_PANEL: DragKindId = DragKindId(1);
32pub const DRAG_KIND_DOCK_TABS: DragKindId = DragKindId(2);
33
34#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct DragSessionId(pub u64);
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum DragPhase {
39 Starting,
40 Dragging,
41 Dropped,
42 Canceled,
43}
44
45#[derive(Debug)]
46pub struct DragSession {
47 pub session_id: DragSessionId,
48 pub pointer_id: PointerId,
49 pub source_window: AppWindowId,
50 pub current_window: AppWindowId,
51 pub cross_window_hover: bool,
52 pub kind: DragKindId,
53 pub start_position: Point,
54 pub position: Point,
55 /// Cursor grab offset in window-local logical coordinates.
56 ///
57 /// Runners may use this to keep an OS window under the cursor during docking interactions
58 /// (ImGui-style multi-viewport behavior), without needing to downcast the typed payload.
59 pub cursor_grab_offset: Option<Point>,
60 /// If set, requests the runner to treat this drag as a "move the OS window" interaction for
61 /// the given window id, while still allowing cross-window docking hover/drop routing.
62 pub follow_window: Option<AppWindowId>,
63 /// Best-effort diagnostics hint: OS window currently being moved by the runner for this drag
64 /// session (ImGui-style "follow window" multi-viewport behavior).
65 ///
66 /// This is intentionally diagnostics-only: it does not request follow behavior; it records
67 /// what the runner is currently doing.
68 pub moving_window: Option<AppWindowId>,
69 /// Best-effort diagnostics hint: when [`Self::moving_window`] is set, the window considered
70 /// "under" the moving window at the current cursor position.
71 ///
72 /// This exists to support ImGui-style terminology where `HoveredWindow` and
73 /// `HoveredWindowUnderMovingWindow` can differ during a viewport drag. Fret currently keeps
74 /// `current_window` as the runner-selected hover/drop target; this field makes it possible to
75 /// gate and evolve "peek-behind" behavior without reinterpreting `current_window`.
76 pub window_under_moving_window: Option<AppWindowId>,
77 /// Best-effort diagnostics hint: which mechanism was used to select
78 /// [`Self::window_under_moving_window`].
79 pub window_under_moving_window_source: WindowUnderCursorSource,
80 /// Best-effort diagnostics hint: true when the runner has applied an ImGui-style "transparent
81 /// payload" treatment to the moving dock window (e.g. reduced opacity and/or click-through
82 /// hit-test passthrough while following the cursor).
83 pub transparent_payload_applied: bool,
84 /// Best-effort diagnostics hint: true when the runner successfully applied click-through
85 /// hit-test passthrough to the moving dock window while transparent payload is enabled.
86 ///
87 /// This is a result signal (applied by the OS/window backend), not a request. When false,
88 /// the runner either did not attempt passthrough or the platform/backend rejected it.
89 pub transparent_payload_hit_test_passthrough_applied: bool,
90 /// Best-effort diagnostics hint: which mechanism was used to select the hovered window during
91 /// cross-window drag routing (OS-backed vs heuristic).
92 pub window_under_cursor_source: WindowUnderCursorSource,
93 /// Best-effort diagnostics hint: raw cursor position in screen-space physical pixels, as
94 /// observed by the runner.
95 pub diag_cursor_screen_pos_raw_physical_px: Option<Point>,
96 /// Best-effort diagnostics hint: cursor position in screen-space physical pixels used for
97 /// local position conversion (may be clamped during scripted injection).
98 pub diag_cursor_screen_pos_used_physical_px: Option<Point>,
99 /// True when [`Self::diag_cursor_screen_pos_used_physical_px`] differs from
100 /// [`Self::diag_cursor_screen_pos_raw_physical_px`] due to runner-side clamping.
101 pub diag_cursor_screen_pos_was_clamped: bool,
102 /// True when diagnostics cursor override/input isolation is active while recording the
103 /// cursor conversion hints.
104 pub diag_cursor_override_active: bool,
105 /// Best-effort diagnostics hint: outer window position (top-left) in screen-space physical
106 /// pixels for [`Self::current_window`] at the time routing was computed.
107 pub diag_current_window_outer_pos_physical_px: Option<Point>,
108 /// Best-effort diagnostics hint: window decoration offset (client origin relative to outer
109 /// origin) in physical pixels for [`Self::current_window`].
110 pub diag_current_window_decoration_offset_physical_px: Option<Point>,
111 /// Best-effort diagnostics hint: computed client origin in screen-space physical pixels for
112 /// [`Self::current_window`] at the time routing was computed.
113 pub diag_current_window_client_origin_screen_physical_px: Option<Point>,
114 /// True when the runner obtained the client origin from a platform API (e.g. Win32 HWND),
115 /// rather than falling back to `outer_position + surface_position`.
116 pub diag_current_window_client_origin_source_platform: bool,
117 /// Best-effort diagnostics hint: scale factor (DPI) of [`Self::current_window`] used by the
118 /// runner to convert screen physical pixels into window-local logical pixels.
119 pub diag_current_window_scale_factor_x1000: Option<u32>,
120 /// Best-effort diagnostics hint: local cursor position derived from the screen-space cursor
121 /// position + client origin + scale factor.
122 pub diag_current_window_local_pos_from_screen_logical_px: Option<Point>,
123 pub dragging: bool,
124 pub phase: DragPhase,
125 payload: Box<dyn Any>,
126}
127
128impl DragSession {
129 pub fn new<T: Any>(
130 session_id: DragSessionId,
131 pointer_id: PointerId,
132 source_window: AppWindowId,
133 kind: DragKindId,
134 start_position: Point,
135 payload: T,
136 ) -> Self {
137 Self {
138 session_id,
139 pointer_id,
140 source_window,
141 current_window: source_window,
142 cross_window_hover: false,
143 kind,
144 start_position,
145 position: start_position,
146 cursor_grab_offset: None,
147 follow_window: None,
148 moving_window: None,
149 window_under_moving_window: None,
150 window_under_moving_window_source: WindowUnderCursorSource::Unknown,
151 transparent_payload_applied: false,
152 transparent_payload_hit_test_passthrough_applied: false,
153 window_under_cursor_source: WindowUnderCursorSource::Unknown,
154 diag_cursor_screen_pos_raw_physical_px: None,
155 diag_cursor_screen_pos_used_physical_px: None,
156 diag_cursor_screen_pos_was_clamped: false,
157 diag_cursor_override_active: false,
158 diag_current_window_outer_pos_physical_px: None,
159 diag_current_window_decoration_offset_physical_px: None,
160 diag_current_window_client_origin_screen_physical_px: None,
161 diag_current_window_client_origin_source_platform: false,
162 diag_current_window_scale_factor_x1000: None,
163 diag_current_window_local_pos_from_screen_logical_px: None,
164 dragging: false,
165 phase: DragPhase::Starting,
166 payload: Box::new(payload),
167 }
168 }
169
170 pub fn new_cross_window<T: Any>(
171 session_id: DragSessionId,
172 pointer_id: PointerId,
173 source_window: AppWindowId,
174 kind: DragKindId,
175 start_position: Point,
176 payload: T,
177 ) -> Self {
178 Self {
179 session_id,
180 pointer_id,
181 source_window,
182 current_window: source_window,
183 cross_window_hover: true,
184 kind,
185 start_position,
186 position: start_position,
187 cursor_grab_offset: None,
188 follow_window: None,
189 moving_window: None,
190 window_under_moving_window: None,
191 window_under_moving_window_source: WindowUnderCursorSource::Unknown,
192 transparent_payload_applied: false,
193 transparent_payload_hit_test_passthrough_applied: false,
194 window_under_cursor_source: WindowUnderCursorSource::Unknown,
195 diag_cursor_screen_pos_raw_physical_px: None,
196 diag_cursor_screen_pos_used_physical_px: None,
197 diag_cursor_screen_pos_was_clamped: false,
198 diag_cursor_override_active: false,
199 diag_current_window_outer_pos_physical_px: None,
200 diag_current_window_decoration_offset_physical_px: None,
201 diag_current_window_client_origin_screen_physical_px: None,
202 diag_current_window_client_origin_source_platform: false,
203 diag_current_window_scale_factor_x1000: None,
204 diag_current_window_local_pos_from_screen_logical_px: None,
205 dragging: false,
206 phase: DragPhase::Starting,
207 payload: Box::new(payload),
208 }
209 }
210
211 pub fn payload<T: Any>(&self) -> Option<&T> {
212 self.payload.downcast_ref::<T>()
213 }
214
215 pub fn payload_mut<T: Any>(&mut self) -> Option<&mut T> {
216 self.payload.downcast_mut::<T>()
217 }
218
219 pub fn into_payload(self) -> Box<dyn Any> {
220 self.payload
221 }
222}