-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #148 from ilmai/drag-and-drop
Drag and drop
- Loading branch information
Showing
5 changed files
with
350 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
mod drop_target; | ||
mod keyboard; | ||
mod window; | ||
|
||
|
Oops, something went wrong.