use crate::DragDropEvent;
use std::{
cell::UnsafeCell,
ffi::OsString,
os::{raw::c_void, windows::ffi::OsStringExt},
path::PathBuf,
ptr,
rc::Rc,
};
use windows::{
core::{implement, BOOL},
Win32::{
Foundation::{DRAGDROP_E_INVALIDHWND, HWND, LPARAM, POINT, POINTL},
Graphics::Gdi::ScreenToClient,
System::{
Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL},
Ole::{
IDropTarget, IDropTarget_Impl, RegisterDragDrop, RevokeDragDrop, CF_HDROP, DROPEFFECT,
DROPEFFECT_COPY, DROPEFFECT_NONE,
},
SystemServices::MODIFIERKEYS_FLAGS,
},
UI::{
Shell::{DragFinish, DragQueryFileW, HDROP},
WindowsAndMessaging::EnumChildWindows,
},
},
};
#[derive(Default)]
pub(crate) struct DragDropController {
drop_targets: Vec<IDropTarget>,
}
impl DragDropController {
#[inline]
pub(crate) fn new(hwnd: HWND, handler: Box<dyn Fn(DragDropEvent) -> bool>) -> Self {
let mut controller = DragDropController::default();
let handler = Rc::new(handler);
{
let mut callback = |hwnd| controller.inject_in_hwnd(hwnd, handler.clone());
let mut trait_obj: &mut dyn FnMut(HWND) -> bool = &mut callback;
let closure_pointer_pointer: *mut c_void = unsafe { std::mem::transmute(&mut trait_obj) };
let lparam = LPARAM(closure_pointer_pointer as _);
unsafe extern "system" fn enumerate_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
let closure = &mut *(lparam.0 as *mut c_void as *mut &mut dyn FnMut(HWND) -> bool);
closure(hwnd).into()
}
let _ = unsafe { EnumChildWindows(Some(hwnd), Some(enumerate_callback), lparam) };
}
controller
}
#[inline]
fn inject_in_hwnd(&mut self, hwnd: HWND, handler: Rc<dyn Fn(DragDropEvent) -> bool>) -> bool {
let drag_drop_target: IDropTarget = DragDropTarget::new(hwnd, handler).into();
if unsafe { RevokeDragDrop(hwnd) } != Err(DRAGDROP_E_INVALIDHWND.into())
&& unsafe { RegisterDragDrop(hwnd, &drag_drop_target) }.is_ok()
{
self.drop_targets.push(drag_drop_target);
}
true
}
}
#[implement(IDropTarget)]
pub struct DragDropTarget {
hwnd: HWND,
listener: Rc<dyn Fn(DragDropEvent) -> bool>,
cursor_effect: UnsafeCell<DROPEFFECT>,
enter_is_valid: UnsafeCell<bool>,
}
impl DragDropTarget {
pub fn new(hwnd: HWND, listener: Rc<dyn Fn(DragDropEvent) -> bool>) -> DragDropTarget {
Self {
hwnd,
listener,
cursor_effect: DROPEFFECT_NONE.into(),
enter_is_valid: false.into(),
}
}
unsafe fn iterate_filenames<F>(
data_obj: windows_core::Ref<'_, IDataObject>,
mut callback: F,
) -> Option<HDROP>
where
F: FnMut(PathBuf),
{
let drop_format = FORMATETC {
cfFormat: CF_HDROP.0,
ptd: ptr::null_mut(),
dwAspect: DVASPECT_CONTENT.0,
lindex: -1,
tymed: TYMED_HGLOBAL.0 as u32,
};
match data_obj
.as_ref()
.expect("Received null IDataObject")
.GetData(&drop_format)
{
Ok(medium) => {
let hdrop = HDROP(medium.u.hGlobal.0 as _);
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, None);
for i in 0..item_count {
let character_count = DragQueryFileW(hdrop, i, None) as usize;
let str_len = character_count + 1;
let mut path_buf = vec![0; str_len];
DragQueryFileW(hdrop, i, Some(&mut path_buf));
callback(OsString::from_wide(&path_buf[0..character_count]).into());
}
Some(hdrop)
}
Err(_error) => {
#[cfg(feature = "tracing")]
tracing::warn!(
"{}",
match _error.code() {
windows::Win32::Foundation::DV_E_FORMATETC => {
"Error occurred while processing dropped/hovered item: item is not a file."
}
_ => "Unexpected error occurred while processing dropped/hovered item.",
}
);
None
}
}
}
}
#[allow(non_snake_case)]
impl IDropTarget_Impl for DragDropTarget_Impl {
fn DragEnter(
&self,
pDataObj: windows_core::Ref<'_, IDataObject>,
_grfKeyState: MODIFIERKEYS_FLAGS,
pt: &POINTL,
pdwEffect: *mut DROPEFFECT,
) -> windows::core::Result<()> {
let mut pt = POINT { x: pt.x, y: pt.y };
let _ = unsafe { ScreenToClient(self.hwnd, &mut pt) };
let mut paths = Vec::new();
let hdrop = unsafe { DragDropTarget::iterate_filenames(pDataObj, |path| paths.push(path)) };
let enter_is_valid = hdrop.is_some();
if !enter_is_valid {
return Ok(());
};
unsafe {
*self.enter_is_valid.get() = enter_is_valid;
}
(self.listener)(DragDropEvent::Enter {
paths,
position: (pt.x as _, pt.y as _),
});
let cursor_effect = if enter_is_valid {
DROPEFFECT_COPY
} else {
DROPEFFECT_NONE
};
unsafe {
*pdwEffect = cursor_effect;
*self.cursor_effect.get() = cursor_effect;
}
Ok(())
}
fn DragOver(
&self,
_grfKeyState: MODIFIERKEYS_FLAGS,
pt: &POINTL,
pdwEffect: *mut DROPEFFECT,
) -> windows::core::Result<()> {
if unsafe { *self.enter_is_valid.get() } {
let mut pt = POINT { x: pt.x, y: pt.y };
let _ = unsafe { ScreenToClient(self.hwnd, &mut pt) };
(self.listener)(DragDropEvent::Over {
position: (pt.x as _, pt.y as _),
});
}
unsafe { *pdwEffect = *self.cursor_effect.get() };
Ok(())
}
fn DragLeave(&self) -> windows::core::Result<()> {
if unsafe { *self.enter_is_valid.get() } {
(self.listener)(DragDropEvent::Leave);
}
Ok(())
}
fn Drop(
&self,
pDataObj: windows_core::Ref<'_, IDataObject>,
_grfKeyState: MODIFIERKEYS_FLAGS,
pt: &POINTL,
_pdwEffect: *mut DROPEFFECT,
) -> windows::core::Result<()> {
if unsafe { *self.enter_is_valid.get() } {
let mut pt = POINT { x: pt.x, y: pt.y };
let _ = unsafe { ScreenToClient(self.hwnd, &mut pt) };
let mut paths = Vec::new();
let hdrop = unsafe { DragDropTarget::iterate_filenames(pDataObj, |path| paths.push(path)) };
(self.listener)(DragDropEvent::Drop {
paths,
position: (pt.x as _, pt.y as _),
});
if let Some(hdrop) = hdrop {
unsafe { DragFinish(hdrop) };
}
}
Ok(())
}
}