From f5dcd2aabeeddcf58fa7e47e923667ecb854aa60 Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:10:40 -0500 Subject: [PATCH] Rework Drag-And-Drop API (#4079) * Add cursor position drag and drop events. * Reword drag events to match pointer ones. * appkit: Use `convertPoint_fromView` for coordinate conversion. * appkit: use ProtocolObject. * x11: store dnd.position as pair of i16 It's what translate_coords takes anyway, so the extra precision is misleading if we're going to cast it to i16 everywhere it's used. We can also simplify the "unpacking" from the XdndPosition message--we can and should use the value of 16 as the shift instead of size_of:: * 2 or something like that, because the specification gives us the constant 16. * x11: store translated DnD coords. * x11: don't emit DragLeave without DragEnter. * windows: only emit DragEnter if valid. * windows: in DnD, always set pdwEffect. It appears other apps (like Chromium) set pdwEffect on Drop too: https://github.com/chromium/chromium/blob/61a391b86bd946d6e1105412539e77ba9fb2a6b3/ui/base/dragdrop/drop_target_win.cc * docs: make it clearer that drag events are for dragged *files*. * examples/dnd: handle RedrawRequested event. Co-authored-by: amrbashir --- examples/dnd.rs | 64 ++++++++++++ examples/window.rs | 7 +- src/changelog/unreleased.md | 19 ++++ src/event.rs | 71 +++++++++----- .../apple/appkit/window_delegate.rs | 68 ++++++++++--- src/platform_impl/linux/x11/dnd.rs | 16 ++- .../linux/x11/event_processor.rs | 81 +++++++++------- src/platform_impl/linux/x11/util/geometry.rs | 17 +++- src/platform_impl/linux/x11/window.rs | 2 +- src/platform_impl/windows/definitions.rs | 6 +- src/platform_impl/windows/drop_handler.rs | 97 ++++++++++++------- 11 files changed, 327 insertions(+), 121 deletions(-) create mode 100644 examples/dnd.rs diff --git a/examples/dnd.rs b/examples/dnd.rs new file mode 100644 index 0000000000..a05e12d57c --- /dev/null +++ b/examples/dnd.rs @@ -0,0 +1,64 @@ +use std::error::Error; + +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::window::{Window, WindowAttributes, WindowId}; + +#[path = "util/fill.rs"] +mod fill; +#[path = "util/tracing.rs"] +mod tracing; + +fn main() -> Result<(), Box> { + tracing::init(); + + let event_loop = EventLoop::new()?; + + let app = Application::new(); + Ok(event_loop.run_app(app)?) +} + +/// Application state and event handling. +struct Application { + window: Option>, +} + +impl Application { + fn new() -> Self { + Self { window: None } + } +} + +impl ApplicationHandler for Application { + fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { + let window_attributes = + WindowAttributes::default().with_title("Drag and drop files on me!"); + self.window = Some(event_loop.create_window(window_attributes).unwrap()); + } + + fn window_event( + &mut self, + event_loop: &dyn ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::DragLeft { .. } + | WindowEvent::DragEntered { .. } + | WindowEvent::DragMoved { .. } + | WindowEvent::DragDropped { .. } => { + println!("{:?}", event); + }, + WindowEvent::RedrawRequested => { + let window = self.window.as_ref().unwrap(); + window.pre_present_notify(); + fill::fill_window(window.as_ref()); + }, + WindowEvent::CloseRequested => { + event_loop.exit(); + }, + _ => {}, + } + } +} diff --git a/examples/window.rs b/examples/window.rs index 3d87e2f8d9..c0587fab93 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -541,11 +541,12 @@ impl ApplicationHandler for Application { info!("Smart zoom"); }, WindowEvent::TouchpadPressure { .. } - | WindowEvent::HoveredFileCancelled + | WindowEvent::DragLeft { .. } | WindowEvent::KeyboardInput { .. } | WindowEvent::PointerEntered { .. } - | WindowEvent::DroppedFile(_) - | WindowEvent::HoveredFile(_) + | WindowEvent::DragEntered { .. } + | WindowEvent::DragMoved { .. } + | WindowEvent::DragDropped { .. } | WindowEvent::Destroyed | WindowEvent::Moved(_) => (), } diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 6a9c4b08dc..cf9c9288a4 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -165,6 +165,25 @@ changelog entry. - Rename `VideoModeHandle` to `VideoMode`, now it only stores plain data. - Make `Fullscreen::Exclusive` contain `(MonitorHandle, VideoMode)`. - On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`. +- Reworked the file drag-and-drop API. + + The `WindowEvent::DroppedFile`, `WindowEvent::HoveredFile` and `WindowEvent::HoveredFileCancelled` + events have been removed, and replaced with `WindowEvent::DragEntered`, `WindowEvent::DragMoved`, + `WindowEvent::DragDropped` and `WindowEvent::DragLeft`. + + The old drag-and-drop events were emitted once per file. This occurred when files were *first* + hovered over the window, dropped, or left the window. The new drag-and-drop events are emitted + once per set of files dragged, and include a list of all dragged files. They also include the + pointer position. + + The rough correspondence is: + - `WindowEvent::HoveredFile` -> `WindowEvent::DragEntered` + - `WindowEvent::DroppedFile` -> `WindowEvent::DragDropped` + - `WindowEvent::HoveredFileCancelled` -> `WindowEvent::DragLeft` + + The `WindowEvent::DragMoved` event is entirely new, and is emitted whenever the pointer moves + whilst files are being dragged over the window. It doesn't contain any file paths, just the + pointer position. ### Removed diff --git a/src/event.rs b/src/event.rs index e7af7d65cd..0f9571c8d8 100644 --- a/src/event.rs +++ b/src/event.rs @@ -175,28 +175,42 @@ pub enum WindowEvent { /// The window has been destroyed. Destroyed, - /// A file is being hovered over the window. - /// - /// When the user hovers multiple files at once, this event will be emitted for each file - /// separately. - HoveredFile(PathBuf), - - /// A file has been dropped into the window. - /// - /// When the user drops multiple files at once, this event will be emitted for each file - /// separately. - /// - /// The support for this is known to be incomplete, see [#720] for more - /// information. - /// - /// [#720]: https://github.com/rust-windowing/winit/issues/720 - DroppedFile(PathBuf), - - /// A file was hovered, but has exited the window. - /// - /// There will be a single `HoveredFileCancelled` event triggered even if multiple files were - /// hovered. - HoveredFileCancelled, + /// A file drag operation has entered the window. + DragEntered { + /// List of paths that are being dragged onto the window. + paths: Vec, + /// (x,y) coordinates in pixels relative to the top-left corner of the window. May be + /// negative on some platforms if something is dragged over a window's decorations (title + /// bar, frame, etc). + position: PhysicalPosition, + }, + /// A file drag operation has moved over the window. + DragMoved { + /// (x,y) coordinates in pixels relative to the top-left corner of the window. May be + /// negative on some platforms if something is dragged over a window's decorations (title + /// bar, frame, etc). + position: PhysicalPosition, + }, + /// The file drag operation has dropped file(s) on the window. + DragDropped { + /// List of paths that are being dragged onto the window. + paths: Vec, + /// (x,y) coordinates in pixels relative to the top-left corner of the window. May be + /// negative on some platforms if something is dragged over a window's decorations (title + /// bar, frame, etc). + position: PhysicalPosition, + }, + /// The file drag operation has been cancelled or left the window. + DragLeft { + /// (x,y) coordinates in pixels relative to the top-left corner of the window. May be + /// negative on some platforms if something is dragged over a window's decorations (title + /// bar, frame, etc). + /// + /// ## Platform-specific + /// + /// - **Windows:** Always emits [`None`]. + position: Option>, + }, /// The window gained or lost focus. /// @@ -1221,9 +1235,16 @@ mod tests { with_window_event(Focused(true)); with_window_event(Moved((0, 0).into())); with_window_event(SurfaceResized((0, 0).into())); - with_window_event(DroppedFile("x.txt".into())); - with_window_event(HoveredFile("x.txt".into())); - with_window_event(HoveredFileCancelled); + with_window_event(DragEntered { + paths: vec!["x.txt".into()], + position: (0, 0).into(), + }); + with_window_event(DragMoved { position: (0, 0).into() }); + with_window_event(DragDropped { + paths: vec!["x.txt".into()], + position: (0, 0).into(), + }); + with_window_event(DragLeft { position: Some((0, 0).into()) }); with_window_event(Ime(Enabled)); with_window_event(PointerMoved { device_id: None, diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 8bb04f4337..926ff03018 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -13,7 +13,7 @@ use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClas use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, - NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, + NSColor, NSDraggingDestination, NSDraggingInfo, NSFilenamesPboardType, NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification, NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, @@ -375,19 +375,45 @@ declare_class!( unsafe impl NSDraggingDestination for WindowDelegate { /// Invoked when the dragged image enters destination bounds or frame #[method(draggingEntered:)] - fn dragging_entered(&self, sender: &NSObject) -> bool { + fn dragging_entered(&self, sender: &ProtocolObject) -> bool { trace_scope!("draggingEntered:"); use std::path::PathBuf; - let pb: Retained = unsafe { msg_send_id![sender, draggingPasteboard] }; + let pb = unsafe { sender.draggingPasteboard() }; let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); let filenames: Retained> = unsafe { Retained::cast(filenames) }; + let paths = filenames + .into_iter() + .map(|file| PathBuf::from(file.to_string())) + .collect(); - filenames.into_iter().for_each(|file| { - let path = PathBuf::from(file.to_string()); - self.queue_event(WindowEvent::HoveredFile(path)); - }); + let dl = unsafe { sender.draggingLocation() }; + let dl = self.view().convertPoint_fromView(dl, None); + let position = LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()); + + + self.queue_event(WindowEvent::DragEntered { paths, position }); + + true + } + + #[method(wantsPeriodicDraggingUpdates)] + fn wants_periodic_dragging_updates(&self) -> bool { + trace_scope!("wantsPeriodicDraggingUpdates:"); + true + } + + /// Invoked periodically as the image is held within the destination area, allowing modification of the dragging operation or mouse-pointer position. + #[method(draggingUpdated:)] + fn dragging_updated(&self, sender: &ProtocolObject) -> bool { + trace_scope!("draggingUpdated:"); + + let dl = unsafe { sender.draggingLocation() }; + let dl = self.view().convertPoint_fromView(dl, None); + let position = LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()); + + self.queue_event(WindowEvent::DragMoved { position }); true } @@ -401,19 +427,24 @@ declare_class!( /// Invoked after the released image has been removed from the screen #[method(performDragOperation:)] - fn perform_drag_operation(&self, sender: &NSObject) -> bool { + fn perform_drag_operation(&self, sender: &ProtocolObject) -> bool { trace_scope!("performDragOperation:"); use std::path::PathBuf; - let pb: Retained = unsafe { msg_send_id![sender, draggingPasteboard] }; + let pb = unsafe { sender.draggingPasteboard() }; let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); let filenames: Retained> = unsafe { Retained::cast(filenames) }; + let paths = filenames + .into_iter() + .map(|file| PathBuf::from(file.to_string())) + .collect(); - filenames.into_iter().for_each(|file| { - let path = PathBuf::from(file.to_string()); - self.queue_event(WindowEvent::DroppedFile(path)); - }); + let dl = unsafe { sender.draggingLocation() }; + let dl = self.view().convertPoint_fromView(dl, None); + let position = LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()); + + self.queue_event(WindowEvent::DragDropped { paths, position }); true } @@ -426,9 +457,16 @@ declare_class!( /// Invoked when the dragging operation is cancelled #[method(draggingExited:)] - fn dragging_exited(&self, _sender: Option<&NSObject>) { + fn dragging_exited(&self, info: Option<&ProtocolObject>) { trace_scope!("draggingExited:"); - self.queue_event(WindowEvent::HoveredFileCancelled); + + let position = info.map(|info| { + let dl = unsafe { info.draggingLocation() }; + let dl = self.view().convertPoint_fromView(dl, None); + LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()) + }); + + self.queue_event(WindowEvent::DragLeft { position } ); } } diff --git a/src/platform_impl/linux/x11/dnd.rs b/src/platform_impl/linux/x11/dnd.rs index 691e40af9b..f5988aaad6 100644 --- a/src/platform_impl/linux/x11/dnd.rs +++ b/src/platform_impl/linux/x11/dnd.rs @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf}; use std::str::Utf8Error; use std::sync::Arc; +use dpi::PhysicalPosition; use percent_encoding::percent_decode; use x11rb::protocol::xproto::{self, ConnectionExt}; @@ -45,13 +46,25 @@ pub struct Dnd { pub type_list: Option>, // Populated by XdndPosition event handler pub source_window: Option, + // Populated by XdndPosition event handler + pub position: PhysicalPosition, // Populated by SelectionNotify event handler (triggered by XdndPosition event handler) pub result: Option, DndDataParseError>>, + // Populated by SelectionNotify event handler (triggered by XdndPosition event handler) + pub dragging: bool, } impl Dnd { pub fn new(xconn: Arc) -> Result { - Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None }) + Ok(Dnd { + xconn, + version: None, + type_list: None, + source_window: None, + position: PhysicalPosition::default(), + result: None, + dragging: false, + }) } pub fn reset(&mut self) { @@ -59,6 +72,7 @@ impl Dnd { self.type_list = None; self.source_window = None; self.result = None; + self.dragging = false; } pub unsafe fn send_status( diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index dbe82935da..61365490ca 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -470,14 +470,19 @@ impl EventProcessor { let source_window = xev.data.get_long(0) as xproto::Window; - // Equivalent to `(x << shift) | y` - // where `shift = mem::size_of::() * 8` + // https://www.freedesktop.org/wiki/Specifications/XDND/#xdndposition // Note that coordinates are in "desktop space", not "window space" // (in X11 parlance, they're root window coordinates) - // let packed_coordinates = xev.data.get_long(2); - // let shift = mem::size_of::() * 8; - // let x = packed_coordinates >> shift; - // let y = packed_coordinates & !(x << shift); + let packed_coordinates = xev.data.get_long(2); + let x = (packed_coordinates >> 16) as i16; + let y = (packed_coordinates & 0xffff) as i16; + + let coords = self + .target + .xconn + .translate_coords(self.target.root, window, x, y) + .expect("Failed to translate window coordinates"); + self.dnd.position = PhysicalPosition::new(coords.dst_x as f64, coords.dst_y as f64); // By our own state flow, `version` should never be `None` at this point. let version = self.dnd.version.unwrap_or(5); @@ -502,21 +507,19 @@ impl EventProcessor { } self.dnd.source_window = Some(source_window); - if self.dnd.result.is_none() { - let time = if version >= 1 { - xev.data.get_long(3) as xproto::Timestamp - } else { - // In version 0, time isn't specified - x11rb::CURRENT_TIME - }; + let time = if version == 0 { + // In version 0, time isn't specified + x11rb::CURRENT_TIME + } else { + xev.data.get_long(3) as xproto::Timestamp + }; - // Log this timestamp. - self.target.xconn.set_timestamp(time); + // Log this timestamp. + self.target.xconn.set_timestamp(time); - // This results in the `SelectionNotify` event below - unsafe { - self.dnd.convert_selection(window, time); - } + // This results in the `SelectionNotify` event below + unsafe { + self.dnd.convert_selection(window, time); } unsafe { @@ -530,13 +533,12 @@ impl EventProcessor { if xev.message_type == atoms[XdndDrop] as c_ulong { let (source_window, state) = if let Some(source_window) = self.dnd.source_window { if let Some(Ok(ref path_list)) = self.dnd.result { - for path in path_list { - let event = Event::WindowEvent { - window_id, - event: WindowEvent::DroppedFile(path.clone()), - }; - callback(&self.target, event); - } + let event = WindowEvent::DragDropped { + paths: path_list.iter().map(Into::into).collect(), + position: self.dnd.position, + }; + + callback(&self.target, Event::WindowEvent { window_id, event }); } (source_window, DndState::Accepted) } else { @@ -557,9 +559,14 @@ impl EventProcessor { } if xev.message_type == atoms[XdndLeave] as c_ulong { + if self.dnd.dragging { + let event = Event::WindowEvent { + window_id, + event: WindowEvent::DragLeft { position: Some(self.dnd.position) }, + }; + callback(&self.target, event); + } self.dnd.reset(); - let event = Event::WindowEvent { window_id, event: WindowEvent::HoveredFileCancelled }; - callback(&self.target, event); } } @@ -583,15 +590,19 @@ impl EventProcessor { self.dnd.result = None; if let Ok(mut data) = unsafe { self.dnd.read_data(window) } { let parse_result = self.dnd.parse_data(&mut data); + if let Ok(ref path_list) = parse_result { - for path in path_list { - let event = Event::WindowEvent { - window_id, - event: WindowEvent::HoveredFile(path.clone()), - }; - callback(&self.target, event); - } + let event = if self.dnd.dragging { + WindowEvent::DragMoved { position: self.dnd.position } + } else { + let paths = path_list.iter().map(Into::into).collect(); + self.dnd.dragging = true; + WindowEvent::DragEntered { paths, position: self.dnd.position } + }; + + callback(&self.target, Event::WindowEvent { window_id, event }); } + self.dnd.result = Some(parse_result); } } diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs index c935d162f7..ab13b0410e 100644 --- a/src/platform_impl/linux/x11/util/geometry.rs +++ b/src/platform_impl/linux/x11/util/geometry.rs @@ -95,7 +95,7 @@ impl FrameExtentsHeuristic { impl XConnection { // This is adequate for inner_position - pub fn translate_coords( + pub fn translate_coords_root( &self, window: xproto::Window, root: xproto::Window, @@ -103,6 +103,19 @@ impl XConnection { self.xcb_connection().translate_coordinates(window, root, 0, 0)?.reply().map_err(Into::into) } + pub fn translate_coords( + &self, + src_w: xproto::Window, + dst_w: xproto::Window, + src_x: i16, + src_y: i16, + ) -> Result { + self.xcb_connection() + .translate_coordinates(src_w, dst_w, src_x, src_y)? + .reply() + .map_err(Into::into) + } + // This is adequate for surface_size pub fn get_geometry( &self, @@ -189,7 +202,7 @@ impl XConnection { // that, fullscreen windows often aren't nested. let (inner_y_rel_root, child) = { let coords = self - .translate_coords(window, root) + .translate_coords_root(window, root) .expect("Failed to translate window coordinates"); (coords.dst_y, coords.child) }; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 9f412356ac..4fbc1df3d6 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1520,7 +1520,7 @@ impl UnownedWindow { // This should be okay to unwrap since the only error XTranslateCoordinates can return // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn - .translate_coords(self.xwindow, self.root) + .translate_coords_root(self.xwindow, self.root) .map(|coords| (coords.dst_x.into(), coords.dst_y.into())) .unwrap() } diff --git a/src/platform_impl/windows/definitions.rs b/src/platform_impl/windows/definitions.rs index c015ffc4e3..62c0cb0625 100644 --- a/src/platform_impl/windows/definitions.rs +++ b/src/platform_impl/windows/definitions.rs @@ -72,13 +72,13 @@ pub struct IDropTargetVtbl { This: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: u32, - pt: *const POINTL, + pt: POINTL, pdwEffect: *mut u32, ) -> HRESULT, pub DragOver: unsafe extern "system" fn( This: *mut IDropTarget, grfKeyState: u32, - pt: *const POINTL, + pt: POINTL, pdwEffect: *mut u32, ) -> HRESULT, pub DragLeave: unsafe extern "system" fn(This: *mut IDropTarget) -> HRESULT, @@ -86,7 +86,7 @@ pub struct IDropTargetVtbl { This: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: u32, - pt: *const POINTL, + pt: POINTL, pdwEffect: *mut u32, ) -> HRESULT, } diff --git a/src/platform_impl/windows/drop_handler.rs b/src/platform_impl/windows/drop_handler.rs index 99c413bfaa..3f378f6ebc 100644 --- a/src/platform_impl/windows/drop_handler.rs +++ b/src/platform_impl/windows/drop_handler.rs @@ -6,12 +6,14 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use tracing::debug; use windows_sys::core::{IUnknown, GUID, HRESULT}; -use windows_sys::Win32::Foundation::{DV_E_FORMATETC, HWND, POINTL, S_OK}; +use windows_sys::Win32::Foundation::{DV_E_FORMATETC, HWND, POINT, POINTL, S_OK}; +use windows_sys::Win32::Graphics::Gdi::ScreenToClient; use windows_sys::Win32::System::Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}; use windows_sys::Win32::System::Ole::{CF_HDROP, DROPEFFECT_COPY, DROPEFFECT_NONE}; use windows_sys::Win32::UI::Shell::{DragFinish, DragQueryFileW, HDROP}; -use crate::event::Event; +use crate::dpi::PhysicalPosition; +use crate::event::{Event, WindowEvent}; use crate::platform_impl::platform::definitions::{ IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknownVtbl, }; @@ -24,8 +26,8 @@ pub struct FileDropHandlerData { window: HWND, send_event: Box, cursor_effect: u32, - hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any - * `HoveredFileCancelled` emitted */ + valid: bool, /* If the currently hovered item is not valid there must not be any + * `DragLeft` emitted */ } pub struct FileDropHandler { @@ -41,7 +43,7 @@ impl FileDropHandler { window, send_event, cursor_effect: DROPEFFECT_NONE, - hovered_is_valid: false, + valid: false, }); FileDropHandler { data: Box::into_raw(data) } } @@ -77,22 +79,26 @@ impl FileDropHandler { this: *mut IDropTarget, pDataObj: *const IDataObject, _grfKeyState: u32, - _pt: *const POINTL, + pt: POINTL, pdwEffect: *mut u32, ) -> HRESULT { - use crate::event::WindowEvent::HoveredFile; let drop_handler = unsafe { Self::from_interface(this) }; - let hdrop = unsafe { - Self::iterate_filenames(pDataObj, |filename| { - drop_handler.send_event(Event::WindowEvent { - window_id: WindowId::from_raw(drop_handler.window as usize), - event: HoveredFile(filename), - }); - }) - }; - drop_handler.hovered_is_valid = hdrop.is_some(); + let mut pt = POINT { x: pt.x, y: pt.y }; + unsafe { + ScreenToClient(drop_handler.window, &mut pt); + } + let position = PhysicalPosition::new(pt.x as f64, pt.y as f64); + let mut paths = Vec::new(); + let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) }; + drop_handler.valid = hdrop.is_some(); + if drop_handler.valid { + drop_handler.send_event(Event::WindowEvent { + window_id: WindowId::from_raw(drop_handler.window as usize), + event: WindowEvent::DragEntered { paths, position }, + }); + } drop_handler.cursor_effect = - if drop_handler.hovered_is_valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE }; + if drop_handler.valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE }; unsafe { *pdwEffect = drop_handler.cursor_effect; } @@ -103,10 +109,21 @@ impl FileDropHandler { pub unsafe extern "system" fn DragOver( this: *mut IDropTarget, _grfKeyState: u32, - _pt: *const POINTL, + pt: POINTL, pdwEffect: *mut u32, ) -> HRESULT { let drop_handler = unsafe { Self::from_interface(this) }; + if drop_handler.valid { + let mut pt = POINT { x: pt.x, y: pt.y }; + unsafe { + ScreenToClient(drop_handler.window, &mut pt); + } + let position = PhysicalPosition::new(pt.x as f64, pt.y as f64); + drop_handler.send_event(Event::WindowEvent { + window_id: WindowId::from_raw(drop_handler.window as usize), + event: WindowEvent::DragMoved { position }, + }); + } unsafe { *pdwEffect = drop_handler.cursor_effect; } @@ -115,12 +132,11 @@ impl FileDropHandler { } pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT { - use crate::event::WindowEvent::HoveredFileCancelled; let drop_handler = unsafe { Self::from_interface(this) }; - if drop_handler.hovered_is_valid { + if drop_handler.valid { drop_handler.send_event(Event::WindowEvent { window_id: WindowId::from_raw(drop_handler.window as usize), - event: HoveredFileCancelled, + event: WindowEvent::DragLeft { position: None }, }); } @@ -131,21 +147,30 @@ impl FileDropHandler { this: *mut IDropTarget, pDataObj: *const IDataObject, _grfKeyState: u32, - _pt: *const POINTL, - _pdwEffect: *mut u32, + pt: POINTL, + pdwEffect: *mut u32, ) -> HRESULT { - use crate::event::WindowEvent::DroppedFile; let drop_handler = unsafe { Self::from_interface(this) }; - let hdrop = unsafe { - Self::iterate_filenames(pDataObj, |filename| { - drop_handler.send_event(Event::WindowEvent { - window_id: WindowId::from_raw(drop_handler.window as usize), - event: DroppedFile(filename), - }); - }) - }; - if let Some(hdrop) = hdrop { - unsafe { DragFinish(hdrop) }; + if drop_handler.valid { + let mut pt = POINT { x: pt.x, y: pt.y }; + unsafe { + ScreenToClient(drop_handler.window, &mut pt); + } + let position = PhysicalPosition::new(pt.x as f64, pt.y as f64); + let mut paths = Vec::new(); + let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) }; + drop_handler.send_event(Event::WindowEvent { + window_id: WindowId::from_raw(drop_handler.window as usize), + event: WindowEvent::DragDropped { paths, position }, + }); + if let Some(hdrop) = hdrop { + unsafe { + DragFinish(hdrop); + } + } + } + unsafe { + *pdwEffect = drop_handler.cursor_effect; } S_OK @@ -155,9 +180,9 @@ impl FileDropHandler { unsafe { &mut *(this as *mut _) } } - unsafe fn iterate_filenames(data_obj: *const IDataObject, callback: F) -> Option + unsafe fn iterate_filenames(data_obj: *const IDataObject, mut callback: F) -> Option where - F: Fn(PathBuf), + F: FnMut(PathBuf), { let drop_format = FORMATETC { cfFormat: CF_HDROP,