use crate::*;
use bitflags::bitflags;
use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use tokio::sync::oneshot;
use windows::Win32::{
Foundation::*,
System::{Com::*, Memory::*, Ole::*, SystemServices::*},
UI::Shell::{CLSID_DragDropHelper, DragQueryFileW, HDROP, IDropTargetHelper},
};
use windows::core::{Ref, implement};
#[derive(Debug)]
#[non_exhaustive]
pub enum Data {
Files(Vec<PathBuf>),
}
impl Data {
fn new(data: &IDataObject) -> windows::core::Result<Option<Self>> {
unsafe {
let format = FORMATETC {
cfFormat: CF_HDROP.0,
tymed: TYMED_HGLOBAL.0 as u32,
..Default::default()
};
if data.QueryGetData(&format).is_ok() {
let mut d = data.GetData(&format)?;
let hdrop = HDROP(GlobalLock(d.u.hGlobal));
let len = DragQueryFileW(hdrop, u32::MAX, None);
let mut files = Vec::with_capacity(len as usize);
for i in 0..len {
let len = DragQueryFileW(hdrop, i, None);
let mut buf = vec![0u16; len as usize + 1];
DragQueryFileW(hdrop, i, Some(&mut buf));
buf.pop();
files.push(String::from_utf16_lossy(&buf).into());
}
GlobalUnlock(HGLOBAL(hdrop.0))?;
ReleaseStgMedium(&mut d);
Ok(Some(Self::Files(files)))
} else {
Ok(None)
}
}
}
}
bitflags! {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Effect: u32 {
const NONE = DROPEFFECT_NONE.0;
const COPY = DROPEFFECT_COPY.0;
const MOVE = DROPEFFECT_MOVE.0;
const LINK = DROPEFFECT_LINK.0;
const SCROLL = DROPEFFECT_SCROLL.0;
}
}
impl Effect {
pub const fn as_raw(&self) -> DROPEFFECT {
DROPEFFECT(self.bits())
}
}
impl From<Effect> for DROPEFFECT {
#[inline]
fn from(value: Effect) -> Self {
value.as_raw()
}
}
impl From<DROPEFFECT> for Effect {
#[inline]
fn from(value: DROPEFFECT) -> Self {
Self::from_bits_retain(value.0)
}
}
#[derive(Debug)]
struct Current {
data: Arc<Data>,
effect: Effect,
}
#[derive(Clone, Debug)]
#[implement(IDropTarget)]
pub struct DropTarget {
handle: WindowHandle,
helper: IDropTargetHelper,
current: Rc<RefCell<Option<Current>>>,
}
impl DropTarget {
#[inline]
pub fn new(handle: &WindowHandle) -> Self {
let helper: IDropTargetHelper =
unsafe { CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER).unwrap() };
Self {
handle: handle.clone(),
helper,
current: Rc::new(RefCell::new(None)),
}
}
#[inline]
pub fn as_raw(&self) -> IDropTarget {
self.clone().into()
}
}
impl IDropTarget_Impl for DropTarget_Impl {
fn DragEnter(
&self,
pdataobj: Ref<IDataObject>,
grfkeystate: MODIFIERKEYS_FLAGS,
pt: &POINTL,
pdweffect: *mut DROPEFFECT,
) -> windows_core::Result<()> {
unsafe {
let Some(dataobj) = pdataobj.as_ref() else {
*pdweffect = DROPEFFECT_NONE;
return Ok(());
};
let Some(data) = Data::new(dataobj)? else {
*pdweffect = DROPEFFECT_NONE;
return Ok(());
};
let data = Arc::new(data);
let (tx, rx) = oneshot::channel();
let ev = event::DragEnter {
position: screen_to_client(&self.handle, (pt.x, pt.y).into()),
modifier_keys: grfkeystate.into(),
data: data.clone(),
effect: (*pdweffect).into(),
tx: Some(tx),
};
Context::send_event(self.handle, Event::DragEnter(ev));
let effect = rx.blocking_recv().unwrap();
*pdweffect = effect.into();
self.helper.DragEnter(
self.handle.as_hwnd(),
dataobj,
&POINT { x: pt.x, y: pt.y },
*pdweffect,
)?;
let mut current = self.current.borrow_mut();
*current = Some(Current { data, effect });
Ok(())
}
}
fn DragOver(
&self,
grfkeystate: MODIFIERKEYS_FLAGS,
pt: &POINTL,
pdweffect: *mut DROPEFFECT,
) -> windows_core::Result<()> {
unsafe {
let current = self.current.borrow();
let current = current.as_ref().unwrap();
let (tx, rx) = oneshot::channel();
let ev = event::DragOver {
position: screen_to_client(&self.handle, (pt.x, pt.y).into()),
modifier_keys: grfkeystate.into(),
data: current.data.clone(),
effect: current.effect,
tx: Some(tx),
};
Context::send_event(self.handle, Event::DragOver(ev));
let effect = rx.blocking_recv().unwrap();
*pdweffect = effect.into();
self.helper
.DragOver(&POINT { x: pt.x, y: pt.y }, *pdweffect)?;
Ok(())
}
}
fn DragLeave(&self) -> windows_core::Result<()> {
Context::send_event(self.handle, Event::DragLeave);
unsafe {
self.helper.DragLeave()?;
}
Ok(())
}
fn Drop(
&self,
pdataobj: Ref<IDataObject>,
grfkeystate: MODIFIERKEYS_FLAGS,
pt: &POINTL,
pdweffect: *mut DROPEFFECT,
) -> windows_core::Result<()> {
unsafe {
let Some(dataobj) = pdataobj.as_ref() else {
*pdweffect = DROPEFFECT_NONE;
return Ok(());
};
let current = self.current.borrow_mut().take().unwrap();
let (tx, rx) = oneshot::channel();
let ev = event::Drop {
data: current.data,
position: screen_to_client(&self.handle, (pt.x, pt.y).into()),
modifier_keys: grfkeystate.into(),
effect: current.effect,
tx: Some(tx),
};
Context::send_event(self.handle, Event::Drop(ev));
let effect = rx.blocking_recv().unwrap();
*pdweffect = effect.into();
self.helper
.Drop(dataobj, &POINT { x: pt.x, y: pt.y }, *pdweffect)?;
Ok(())
}
}
}