Skip to main content

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}