Skip to content

Commit

Permalink
Merge pull request #148 from ilmai/drag-and-drop
Browse files Browse the repository at this point in the history
Drag and drop
  • Loading branch information
micahrj authored Jun 18, 2023
2 parents c129b12 + 9887737 commit 1d9806d
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ xcb-util = { version = "0.3", features = ["icccm"] }
nix = "0.22.0"

[target.'cfg(target_os="windows")'.dependencies]
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi"] }
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi", "ole2", "oleidl", "shellapi", "winerror"] }
uuid = { version = "0.8", features = ["v4"], optional = true }

[target.'cfg(target_os="macos")'.dependencies]
Expand Down
50 changes: 49 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use keyboard_types::{KeyboardEvent, Modifiers};

use crate::{Point, WindowInfo};
Expand Down Expand Up @@ -32,7 +34,7 @@ pub enum ScrollDelta {
},
}

#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub enum MouseEvent {
/// The mouse cursor was moved
CursorMoved {
Expand Down Expand Up @@ -75,6 +77,35 @@ pub enum MouseEvent {
///
/// May not be available on all platforms.
CursorLeft,

DragEntered {
/// The logical coordinates of the mouse position
position: Point,
/// The modifiers that were held down just before the event.
modifiers: Modifiers,
/// Data being dragged
data: DropData,
},

DragMoved {
/// The logical coordinates of the mouse position
position: Point,
/// The modifiers that were held down just before the event.
modifiers: Modifiers,
/// Data being dragged
data: DropData,
},

DragLeft,

DragDropped {
/// The logical coordinates of the mouse position
position: Point,
/// The modifiers that were held down just before the event.
modifiers: Modifiers,
/// Data being dragged
data: DropData,
},
}

#[derive(Debug, Clone)]
Expand All @@ -92,6 +123,20 @@ pub enum Event {
Window(WindowEvent),
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DropEffect {
Copy,
Move,
Link,
Scroll,
}

#[derive(Debug, Clone, PartialEq)]
pub enum DropData {
None,
Files(Vec<PathBuf>),
}

/// Return value for [WindowHandler::on_event](`crate::WindowHandler::on_event()`),
/// indicating whether the event was handled by your window or should be passed
/// back to the platform.
Expand All @@ -111,4 +156,7 @@ pub enum EventStatus {
/// DAW functionality for playing piano keys with the keyboard while a
/// plugin window is in focus.
Ignored,
/// We are prepared to handle the data in the drag and dropping will
/// result in [DropEffect]
AcceptDrop(DropEffect),
}
268 changes: 268 additions & 0 deletions src/win/drop_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
use std::ffi::OsString;
use std::mem::transmute;
use std::os::windows::prelude::OsStringExt;
use std::ptr::null_mut;
use std::rc::{Weak, Rc};

use winapi::Interface;
use winapi::shared::guiddef::{REFIID, IsEqualIID};
use winapi::shared::minwindef::{DWORD, WPARAM};
use winapi::shared::ntdef::{HRESULT, ULONG};
use winapi::shared::windef::POINTL;
use winapi::shared::winerror::{S_OK, E_NOINTERFACE, E_UNEXPECTED};
use winapi::shared::wtypes::DVASPECT_CONTENT;
use winapi::um::objidl::{IDataObject, FORMATETC, TYMED_HGLOBAL, STGMEDIUM};
use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, DROPEFFECT_COPY, DROPEFFECT_MOVE, DROPEFFECT_LINK, DROPEFFECT_SCROLL, DROPEFFECT_NONE};
use winapi::um::shellapi::DragQueryFileW;
use winapi::um::unknwnbase::{IUnknownVtbl, IUnknown};
use winapi::um::winuser::CF_HDROP;

use crate::{Point, DropData, MouseEvent, Event, EventStatus, DropEffect, PhyPoint};

use super::WindowState;

// These function pointers have to be stored in a (const) variable before they can be transmuted
// Transmuting is needed because winapi has a bug where the pt parameter has an incorrect
// type `*const POINTL`
const DRAG_ENTER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_enter;
const DRAG_OVER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_over;
const DROP_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drop;
const DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl {
parent: IUnknownVtbl {
QueryInterface: DropTarget::query_interface,
AddRef: DropTarget::add_ref,
Release: DropTarget::release,
},
DragEnter: unsafe { transmute(DRAG_ENTER_PTR) },
DragOver: unsafe { transmute(DRAG_OVER_PTR) },
DragLeave: DropTarget::drag_leave,
Drop: unsafe { transmute(DROP_PTR) },
};

#[repr(C)]
pub(super) struct DropTarget {
base: IDropTarget,

window_state: Weak<WindowState>,

// These are cached since DragOver and DragLeave callbacks don't provide them,
// and handling drag move events gets awkward on the client end otherwise
drag_position: Point,
drop_data: DropData,
}

impl DropTarget {
pub(super) fn new(window_state: Weak<WindowState>) -> Self {
Self {
base: IDropTarget { lpVtbl: &DROP_TARGET_VTBL },

window_state,

drag_position: Point::new(0.0, 0.0),
drop_data: DropData::None,
}
}

fn on_event(&self, pdwEffect: Option<*mut DWORD>, event: MouseEvent) {
let Some(window_state) = self.window_state.upgrade() else {
return;
};

unsafe {
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);

let event = Event::Mouse(event);
let event_status = window_state.handler_mut().as_mut().unwrap().on_event(&mut window, event);

if let Some(pdwEffect) = pdwEffect {
match event_status {
EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY,
EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE,
EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK,
EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL,
_ => *pdwEffect = DROPEFFECT_NONE,
}
}
}
}

fn parse_coordinates(&mut self, pt: POINTL) {
let Some(window_state) = self.window_state.upgrade() else {
return;
};

let phy_point = PhyPoint::new(pt.x, pt.y);
self.drag_position = phy_point.to_logical(&window_state.window_info());
}

fn parse_drop_data(&mut self, data_object: &IDataObject) {
let format = FORMATETC {
cfFormat: CF_HDROP as u16,
ptd: null_mut(),
dwAspect: DVASPECT_CONTENT,
lindex: -1,
tymed: TYMED_HGLOBAL,
};

let mut medium = STGMEDIUM {
tymed: 0,
u: null_mut(),
pUnkForRelease: null_mut(),
};

unsafe {
let hresult = data_object.GetData(&format, &mut medium);
if hresult != S_OK {
self.drop_data = DropData::None;
return;
}

let hdrop = transmute((*medium.u).hGlobal());

let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, null_mut(), 0);
if item_count == 0 {
self.drop_data = DropData::None;
return;
}

let mut paths = Vec::with_capacity(item_count as usize);

for i in 0..item_count {
let characters = DragQueryFileW(hdrop, i, null_mut(), 0);
let buffer_size = characters as usize + 1;
let mut buffer = Vec::<u16>::with_capacity(buffer_size);

DragQueryFileW(hdrop, i, transmute(buffer.spare_capacity_mut().as_mut_ptr()), buffer_size as u32);
buffer.set_len(buffer_size);

paths.push(OsString::from_wide(&buffer[..characters as usize]).into())
}

self.drop_data = DropData::Files(paths);
}
}

unsafe extern "system" fn query_interface(
this: *mut IUnknown,
riid: REFIID,
ppvObject: *mut *mut winapi::ctypes::c_void,
) -> HRESULT
{
if IsEqualIID(&*riid, &IUnknown::uuidof()) || IsEqualIID(&*riid, &IDropTarget::uuidof()){
Self::add_ref(this);
*ppvObject = this as *mut winapi::ctypes::c_void;
return S_OK;
}

return E_NOINTERFACE;
}

unsafe extern "system" fn add_ref(this: *mut IUnknown) -> ULONG {
let arc = Rc::from_raw(this);
let result = Rc::strong_count(&arc) + 1;
let _ = Rc::into_raw(arc);

Rc::increment_strong_count(this);

result as ULONG
}

unsafe extern "system" fn release(this: *mut IUnknown) -> ULONG {
let arc = Rc::from_raw(this);
let result = Rc::strong_count(&arc) - 1;
let _ = Rc::into_raw(arc);

Rc::decrement_strong_count(this);

result as ULONG
}

unsafe extern "system" fn drag_enter(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: DWORD,
pt: POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT
{
let drop_target = &mut *(this as *mut DropTarget);
let Some(window_state) = drop_target.window_state.upgrade() else {
return E_UNEXPECTED;
};

let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM);

drop_target.parse_coordinates(pt);
drop_target.parse_drop_data(&*pDataObj);

let event = MouseEvent::DragEntered {
position: drop_target.drag_position,
modifiers,
data: drop_target.drop_data.clone(),
};

drop_target.on_event(Some(pdwEffect), event);
S_OK
}

unsafe extern "system" fn drag_over(
this: *mut IDropTarget,
grfKeyState: DWORD,
pt: POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT
{
let drop_target = &mut *(this as *mut DropTarget);
let Some(window_state) = drop_target.window_state.upgrade() else {
return E_UNEXPECTED;
};

let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM);

drop_target.parse_coordinates(pt);

let event = MouseEvent::DragMoved {
position: drop_target.drag_position,
modifiers,
data: drop_target.drop_data.clone(),
};

drop_target.on_event(Some(pdwEffect), event);
S_OK
}

unsafe extern "system" fn drag_leave(this: *mut IDropTarget) -> HRESULT {
let drop_target = &mut *(this as *mut DropTarget);
drop_target.on_event(None, MouseEvent::DragLeft);
S_OK
}

unsafe extern "system" fn drop(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: DWORD,
pt: POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT
{
let drop_target = &mut *(this as *mut DropTarget);
let Some(window_state) = drop_target.window_state.upgrade() else {
return E_UNEXPECTED;
};

let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM);

drop_target.parse_coordinates(pt);
drop_target.parse_drop_data(&*pDataObj);

let event = MouseEvent::DragDropped {
position: drop_target.drag_position,
modifiers,
data: drop_target.drop_data.clone(),
};

drop_target.on_event(Some(pdwEffect), event);
S_OK
}
}
1 change: 1 addition & 0 deletions src/win/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod drop_target;
mod keyboard;
mod window;

Expand Down
Loading

0 comments on commit 1d9806d

Please sign in to comment.