diff --git a/src/win/drop_target.rs b/src/win/drop_target.rs index 42661c4f..fa25a1a1 100644 --- a/src/win/drop_target.rs +++ b/src/win/drop_target.rs @@ -10,7 +10,10 @@ use windows::Win32::System::Ole::*; use windows::Win32::System::SystemServices::MODIFIERKEYS_FLAGS; use windows_core::Ref; use windows_sys::Win32::{ - Foundation::POINT, Graphics::Gdi::ScreenToClient, UI::Shell::DragQueryFileW, + Foundation::{POINT, RECT}, + Graphics::Gdi::ScreenToClient, + UI::Shell::DragQueryFileW, + UI::WindowsAndMessaging::{GetClientRect, GetCursorPos}, }; use crate::{DropData, DropEffect, Event, EventStatus, MouseEvent, PhyPoint, Point}; @@ -25,6 +28,10 @@ pub(super) struct DropTarget { // and handling drag move events gets awkward on the client end otherwise drag_position: Cell, drop_data: RefCell, + /// Whether drag client coordinates are physical pixels (`true`) or already logical + /// (`false`). Cached when `is_drag_coords_physical` is called, and cleared on `DragLeave` + /// and `Drop`. + drag_coords_physical: Cell>, } impl DropTarget { @@ -33,6 +40,7 @@ impl DropTarget { window_state, drag_position: Cell::new(Point::new(0.0, 0.0)), drop_data: RefCell::new(DropData::None), + drag_coords_physical: Cell::new(None), } } @@ -61,14 +69,54 @@ impl DropTarget { } } - fn parse_coordinates(&self, pt: POINTL) { + /// Returns `true` when client coordinates from `GetCursorPos`/`ScreenToClient` are physical + /// pixels and should be scaled with [`PhyPoint::to_logical`]. Returns `false` when they are + /// already in logical space. + /// + /// For some reason, this can vary based on the combination of parent window AND drag source. + /// Most of the time the coordinates are physical, but logical coordinates have been observed + /// with Bitwig as the parent and Windows Explorer as the drag source. + /// + /// Cached on self.drag_coords_physical. + fn is_drag_coords_physical(&self, window_state: &WindowState) -> bool { + match self.drag_coords_physical.get() { + Some(physical) => physical, + None => { + let mut rect = RECT { left: 0, top: 0, right: 0, bottom: 0 }; + unsafe { GetClientRect(window_state.hwnd, &mut rect) }; + let client_w = (rect.right - rect.left) as u32; + let physical_w = window_state.window_info().physical_size().width; + let logical_w = window_state.window_info().logical_size().width as u32; + let physical = client_w.abs_diff(physical_w) < client_w.abs_diff(logical_w); + + self.drag_coords_physical.set(Some(physical)); + physical + } + } + } + + fn end_drag_session(&self) { + self.drag_coords_physical.set(None); + } + + fn parse_coordinates(&self) { let Some(window_state) = self.window_state.upgrade() else { return; }; - let mut pt = POINT { x: pt.x, y: pt.y }; - unsafe { ScreenToClient(window_state.hwnd, &mut pt as *mut POINT) }; - let phy_point = PhyPoint::new(pt.x, pt.y); - self.drag_position.set(phy_point.to_logical(&window_state.window_info())); + + // Some parents pass weird coordinates via OLE `pt`. Query the cursor directly instead. + let mut pt = POINT { x: 0, y: 0 }; + unsafe { + GetCursorPos(&mut pt as *mut POINT); + ScreenToClient(window_state.hwnd, &mut pt as *mut POINT); + } + + let logical_point = if self.is_drag_coords_physical(&window_state) { + PhyPoint::new(pt.x, pt.y).to_logical(&window_state.window_info()) + } else { + Point::new(pt.x as f64, pt.y as f64) + }; + self.drag_position.set(logical_point); } fn parse_drop_data(&self, data_object: &IDataObject) { @@ -114,7 +162,7 @@ impl DropTarget { #[allow(non_snake_case)] impl IDropTarget_Impl for DropTarget_Impl { fn DragEnter( - &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, pt: &POINTL, + &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, _pt: &POINTL, pdweffect: *mut DROPEFFECT, ) -> windows_core::Result<()> { let Some(window_state) = self.window_state.upgrade() else { @@ -124,7 +172,7 @@ impl IDropTarget_Impl for DropTarget_Impl { let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfkeystate.0 as usize); - self.parse_coordinates(*pt); + self.parse_coordinates(); self.parse_drop_data(pdataobj.unwrap()); let event = MouseEvent::DragEntered { @@ -138,7 +186,7 @@ impl IDropTarget_Impl for DropTarget_Impl { } fn DragOver( - &self, grfkeystate: MODIFIERKEYS_FLAGS, pt: &POINTL, pdweffect: *mut DROPEFFECT, + &self, grfkeystate: MODIFIERKEYS_FLAGS, _pt: &POINTL, pdweffect: *mut DROPEFFECT, ) -> windows_core::Result<()> { let Some(window_state) = self.window_state.upgrade() else { return Err(E_UNEXPECTED.into()); @@ -147,7 +195,7 @@ impl IDropTarget_Impl for DropTarget_Impl { let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfkeystate.0 as usize); - self.parse_coordinates(*pt); + self.parse_coordinates(); let event = MouseEvent::DragMoved { position: self.drag_position.get(), @@ -160,12 +208,13 @@ impl IDropTarget_Impl for DropTarget_Impl { } fn DragLeave(&self) -> windows_core::Result<()> { + self.end_drag_session(); self.on_event(None, MouseEvent::DragLeft); Ok(()) } fn Drop( - &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, pt: &POINTL, + &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, _pt: &POINTL, pdweffect: *mut DROPEFFECT, ) -> windows_core::Result<()> { let Some(window_state) = self.window_state.upgrade() else { @@ -175,7 +224,7 @@ impl IDropTarget_Impl for DropTarget_Impl { let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfkeystate.0 as usize); - self.parse_coordinates(*pt); + self.parse_coordinates(); self.parse_drop_data(pdataobj.unwrap()); let event = MouseEvent::DragDropped { @@ -185,6 +234,7 @@ impl IDropTarget_Impl for DropTarget_Impl { }; self.on_event(Some(pdweffect), event); + self.end_drag_session(); Ok(()) } }