#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::as_conversions,
// We intentionally keep explicit casts for FFI clarity; avoid auto-fix churn.
clippy::unnecessary_cast
)]
use crate::{Ui, sys};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(i32)]
#[allow(clippy::unnecessary_cast)]
pub enum DragDropPayloadCond {
Always = sys::ImGuiCond_Always as i32,
Once = sys::ImGuiCond_Once as i32,
}
use std::{any, ffi};
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DragDropFlags: u32 {
const NONE = 0;
const SOURCE_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_SourceNoPreviewTooltip as u32;
const SOURCE_NO_DISABLE_HOVER = sys::ImGuiDragDropFlags_SourceNoDisableHover as u32;
const SOURCE_NO_HOLD_TO_OPEN_OTHERS = sys::ImGuiDragDropFlags_SourceNoHoldToOpenOthers as u32;
const SOURCE_ALLOW_NULL_ID = sys::ImGuiDragDropFlags_SourceAllowNullID as u32;
const SOURCE_EXTERN = sys::ImGuiDragDropFlags_SourceExtern as u32;
const PAYLOAD_AUTO_EXPIRE = sys::ImGuiDragDropFlags_PayloadAutoExpire as u32;
const ACCEPT_BEFORE_DELIVERY = sys::ImGuiDragDropFlags_AcceptBeforeDelivery as u32;
const ACCEPT_NO_DRAW_DEFAULT_RECT = sys::ImGuiDragDropFlags_AcceptNoDrawDefaultRect as u32;
const ACCEPT_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_AcceptNoPreviewTooltip as u32;
const ACCEPT_DRAW_AS_HOVERED = sys::ImGuiDragDropFlags_AcceptDrawAsHovered as u32;
const ACCEPT_PEEK_ONLY = sys::ImGuiDragDropFlags_AcceptPeekOnly as u32;
}
}
impl Ui {
pub fn drag_drop_source_config<T: AsRef<str>>(&self, name: T) -> DragDropSource<'_, T> {
DragDropSource {
name,
flags: DragDropFlags::NONE,
cond: DragDropPayloadCond::Always,
ui: self,
}
}
#[doc(alias = "BeginDragDropTarget")]
pub fn drag_drop_target(&self) -> Option<DragDropTarget<'_>> {
let should_begin = unsafe { sys::igBeginDragDropTarget() };
if should_begin {
Some(DragDropTarget(self))
} else {
None
}
}
#[doc(alias = "GetDragDropPayload")]
pub fn drag_drop_payload(&self) -> Option<DragDropPayload> {
unsafe {
let ptr = sys::igGetDragDropPayload();
if ptr.is_null() {
return None;
}
let inner = *ptr;
let size = if inner.DataSize <= 0 || inner.Data.is_null() {
0
} else {
inner.DataSize as usize
};
Some(DragDropPayload {
data: inner.Data,
size,
preview: inner.Preview,
delivery: inner.Delivery,
})
}
}
}
#[derive(Debug)]
pub struct DragDropSource<'ui, T> {
name: T,
flags: DragDropFlags,
cond: DragDropPayloadCond,
ui: &'ui Ui,
}
impl<'ui, T: AsRef<str>> DragDropSource<'ui, T> {
#[inline]
pub fn flags(mut self, flags: DragDropFlags) -> Self {
self.flags = flags;
self
}
#[inline]
pub fn condition(mut self, cond: DragDropPayloadCond) -> Self {
self.cond = cond;
self
}
#[inline]
pub fn begin(self) -> Option<DragDropSourceTooltip<'ui>> {
self.begin_payload(())
}
#[inline]
pub fn begin_payload<P: Copy + 'static>(
self,
payload: P,
) -> Option<DragDropSourceTooltip<'ui>> {
unsafe {
let payload = make_typed_payload(payload);
self.begin_payload_unchecked(
&payload as *const _ as *const ffi::c_void,
std::mem::size_of::<TypedPayload<P>>(),
)
}
}
pub unsafe fn begin_payload_unchecked(
&self,
ptr: *const ffi::c_void,
size: usize,
) -> Option<DragDropSourceTooltip<'ui>> {
unsafe {
let should_begin = sys::igBeginDragDropSource(self.flags.bits() as i32);
if should_begin {
sys::igSetDragDropPayload(
self.ui.scratch_txt(&self.name),
ptr,
size,
self.cond as i32,
);
Some(DragDropSourceTooltip::new(self.ui))
} else {
None
}
}
}
}
#[derive(Debug)]
pub struct DragDropSourceTooltip<'ui> {
_ui: &'ui Ui,
}
impl<'ui> DragDropSourceTooltip<'ui> {
fn new(ui: &'ui Ui) -> Self {
Self { _ui: ui }
}
pub fn end(self) {
}
}
impl Drop for DragDropSourceTooltip<'_> {
fn drop(&mut self) {
unsafe {
sys::igEndDragDropSource();
}
}
}
#[derive(Debug)]
pub struct DragDropTarget<'ui>(&'ui Ui);
impl<'ui> DragDropTarget<'ui> {
pub fn accept_payload_empty(
&self,
name: impl AsRef<str>,
flags: DragDropFlags,
) -> Option<DragDropPayloadEmpty> {
self.accept_payload(name, flags)?
.ok()
.map(|payload_pod: DragDropPayloadPod<()>| DragDropPayloadEmpty {
preview: payload_pod.preview,
delivery: payload_pod.delivery,
})
}
pub fn accept_payload<T: 'static + Copy, Name: AsRef<str>>(
&self,
name: Name,
flags: DragDropFlags,
) -> Option<Result<DragDropPayloadPod<T>, PayloadIsWrongType>> {
let output = unsafe { self.accept_payload_unchecked(name, flags) };
output.map(|payload| {
if payload.data.is_null() || payload.size < std::mem::size_of::<TypedPayload<T>>() {
return Err(PayloadIsWrongType);
}
let typed_payload: TypedPayload<T> =
unsafe { std::ptr::read_unaligned(payload.data as *const TypedPayload<T>) };
if typed_payload.type_id == any::TypeId::of::<T>() {
Ok(DragDropPayloadPod {
data: typed_payload.data,
preview: payload.preview,
delivery: payload.delivery,
})
} else {
Err(PayloadIsWrongType)
}
})
}
pub unsafe fn accept_payload_unchecked(
&self,
name: impl AsRef<str>,
flags: DragDropFlags,
) -> Option<DragDropPayload> {
let inner =
unsafe { sys::igAcceptDragDropPayload(self.0.scratch_txt(name), flags.bits() as i32) };
if inner.is_null() {
None
} else {
let inner = unsafe { *inner };
let size = if inner.DataSize <= 0 || inner.Data.is_null() {
0
} else {
inner.DataSize as usize
};
Some(DragDropPayload {
data: inner.Data,
size,
preview: inner.Preview,
delivery: inner.Delivery,
})
}
}
pub fn pop(self) {
}
}
impl Drop for DragDropTarget<'_> {
fn drop(&mut self) {
unsafe {
sys::igEndDragDropTarget();
}
}
}
#[repr(C)]
#[derive(Copy, Clone)]
struct TypedPayload<T: Copy> {
type_id: any::TypeId,
data: T,
}
fn make_typed_payload<T: Copy + 'static>(data: T) -> TypedPayload<T> {
let mut out = std::mem::MaybeUninit::<TypedPayload<T>>::zeroed();
unsafe {
let ptr = out.as_mut_ptr();
std::ptr::addr_of_mut!((*ptr).type_id).write(any::TypeId::of::<T>());
std::ptr::addr_of_mut!((*ptr).data).write(data);
out.assume_init()
}
}
#[derive(Debug, Clone, Copy)]
pub struct DragDropPayloadEmpty {
pub preview: bool,
pub delivery: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct DragDropPayloadPod<T> {
pub data: T,
pub preview: bool,
pub delivery: bool,
}
#[derive(Debug)]
pub struct DragDropPayload {
pub data: *const ffi::c_void,
pub size: usize,
pub preview: bool,
pub delivery: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PayloadIsWrongType;
impl std::fmt::Display for PayloadIsWrongType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "drag drop payload has wrong type")
}
}
impl std::error::Error for PayloadIsWrongType {}
#[cfg(test)]
mod tests {
use super::*;
fn payload_bytes<T: Copy + 'static>(value: T) -> Vec<u8> {
let payload = make_typed_payload(value);
let size = std::mem::size_of::<TypedPayload<T>>();
let mut out = vec![0u8; size];
unsafe {
std::ptr::copy_nonoverlapping(
std::ptr::from_ref(&payload).cast::<u8>(),
out.as_mut_ptr(),
size,
);
}
out
}
#[test]
fn typed_payload_bytes_are_deterministic() {
assert_eq!(payload_bytes(7u8), payload_bytes(7u8));
assert_eq!(payload_bytes(0x1122_3344u32), payload_bytes(0x1122_3344u32));
}
#[test]
fn typed_payload_can_be_read_unaligned() {
let bytes = payload_bytes(7u8);
let mut buf = vec![0u8; 1 + bytes.len()];
buf[1..].copy_from_slice(&bytes);
let ptr = unsafe { buf.as_ptr().add(1) } as *const TypedPayload<u8>;
let decoded = unsafe { std::ptr::read_unaligned(ptr) };
assert_eq!(decoded.type_id, any::TypeId::of::<u8>());
assert_eq!(decoded.data, 7u8);
}
}