1#![allow(
2 clippy::cast_possible_truncation,
3 clippy::cast_sign_loss,
4 clippy::as_conversions,
5 clippy::unnecessary_cast
7)]
8use crate::{Ui, sys};
38
39#[derive(Debug, Copy, Clone, PartialEq, Eq)]
43#[repr(i32)]
44#[allow(clippy::unnecessary_cast)]
45pub enum DragDropPayloadCond {
46 Always = sys::ImGuiCond_Always as i32,
48 Once = sys::ImGuiCond_Once as i32,
50}
51use std::{any, ffi};
52
53const MAX_PAYLOAD_TYPE_LEN: usize = 32;
54
55fn validate_payload_type_name(name: &str, caller: &str) {
56 assert!(
57 name.len() <= MAX_PAYLOAD_TYPE_LEN,
58 "{caller} payload type name must be at most {MAX_PAYLOAD_TYPE_LEN} bytes"
59 );
60}
61
62fn validate_payload_data(ptr: *const ffi::c_void, size: usize, caller: &str) {
63 assert!(
64 size <= i32::MAX as usize,
65 "{caller} payload size exceeds Dear ImGui's i32 payload range"
66 );
67 assert!(
68 (size == 0 && ptr.is_null()) || (size > 0 && !ptr.is_null()),
69 "{caller} payload pointer and size must both be empty or both be non-empty"
70 );
71}
72
73fn validate_payload_submission(name: &str, ptr: *const ffi::c_void, size: usize, caller: &str) {
74 validate_payload_type_name(name, caller);
75 validate_payload_data(ptr, size, caller);
76}
77
78bitflags::bitflags! {
79 #[repr(transparent)]
81 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82 pub struct DragDropFlags: u32 {
83 const NONE = 0;
85
86 const SOURCE_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_SourceNoPreviewTooltip as u32;
89 const SOURCE_NO_DISABLE_HOVER = sys::ImGuiDragDropFlags_SourceNoDisableHover as u32;
91 const SOURCE_NO_HOLD_TO_OPEN_OTHERS = sys::ImGuiDragDropFlags_SourceNoHoldToOpenOthers as u32;
93 const SOURCE_ALLOW_NULL_ID = sys::ImGuiDragDropFlags_SourceAllowNullID as u32;
95 const SOURCE_EXTERN = sys::ImGuiDragDropFlags_SourceExtern as u32;
97 const PAYLOAD_AUTO_EXPIRE = sys::ImGuiDragDropFlags_PayloadAutoExpire as u32;
99
100 const ACCEPT_BEFORE_DELIVERY = sys::ImGuiDragDropFlags_AcceptBeforeDelivery as u32;
103 const ACCEPT_NO_DRAW_DEFAULT_RECT = sys::ImGuiDragDropFlags_AcceptNoDrawDefaultRect as u32;
105 const ACCEPT_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_AcceptNoPreviewTooltip as u32;
107 const ACCEPT_DRAW_AS_HOVERED = sys::ImGuiDragDropFlags_AcceptDrawAsHovered as u32;
109 const ACCEPT_PEEK_ONLY = sys::ImGuiDragDropFlags_AcceptPeekOnly as u32;
111 }
112}
113
114impl Ui {
115 pub fn drag_drop_source_config<T: AsRef<str>>(&self, name: T) -> DragDropSource<'_, T> {
134 DragDropSource {
135 name,
136 flags: DragDropFlags::NONE,
137 cond: DragDropPayloadCond::Always,
138 ui: self,
139 }
140 }
141
142 #[doc(alias = "BeginDragDropTarget")]
161 pub fn drag_drop_target(&self) -> Option<DragDropTarget<'_>> {
162 let should_begin = unsafe { sys::igBeginDragDropTarget() };
163 if should_begin {
164 Some(DragDropTarget(self))
165 } else {
166 None
167 }
168 }
169
170 #[doc(alias = "GetDragDropPayload")]
177 pub fn drag_drop_payload(&self) -> Option<DragDropPayload> {
178 unsafe {
179 let ptr = sys::igGetDragDropPayload();
180 if ptr.is_null() {
181 return None;
182 }
183 Some(DragDropPayload::from_raw(*ptr))
184 }
185 }
186}
187
188#[derive(Debug)]
193pub struct DragDropSource<'ui, T> {
194 name: T,
195 flags: DragDropFlags,
196 cond: DragDropPayloadCond,
197 ui: &'ui Ui,
198}
199
200impl<'ui, T: AsRef<str>> DragDropSource<'ui, T> {
201 #[inline]
206 pub fn flags(mut self, flags: DragDropFlags) -> Self {
207 self.flags = flags;
208 self
209 }
210
211 #[inline]
216 pub fn condition(mut self, cond: DragDropPayloadCond) -> Self {
217 self.cond = cond;
218 self
219 }
220
221 #[inline]
228 pub fn begin(self) -> Option<DragDropSourceTooltip<'ui>> {
229 self.begin_payload(())
230 }
231
232 #[inline]
242 pub fn begin_payload<P: Copy + 'static>(
243 self,
244 payload: P,
245 ) -> Option<DragDropSourceTooltip<'ui>> {
246 unsafe {
247 let payload_size = std::mem::size_of::<TypedPayload<P>>();
248 assert!(
249 payload_size <= i32::MAX as usize,
250 "DragDropSource::begin_payload() payload size exceeds Dear ImGui's i32 payload range"
251 );
252
253 let payload = make_typed_payload(payload);
254 self.begin_payload_unchecked(&payload as *const _ as *const ffi::c_void, payload_size)
255 }
256 }
257
258 pub unsafe fn begin_payload_unchecked(
270 &self,
271 ptr: *const ffi::c_void,
272 size: usize,
273 ) -> Option<DragDropSourceTooltip<'ui>> {
274 validate_payload_submission(
275 self.name.as_ref(),
276 ptr,
277 size,
278 "DragDropSource::begin_payload_unchecked()",
279 );
280 unsafe {
281 let should_begin = sys::igBeginDragDropSource(self.flags.bits() as i32);
282
283 if should_begin {
284 sys::igSetDragDropPayload(
285 self.ui.scratch_txt(self.name.as_ref()),
286 ptr,
287 size,
288 self.cond as i32,
289 );
290
291 Some(DragDropSourceTooltip::new(self.ui))
292 } else {
293 None
294 }
295 }
296 }
297}
298
299#[derive(Debug)]
304pub struct DragDropSourceTooltip<'ui> {
305 _ui: &'ui Ui,
306}
307
308impl<'ui> DragDropSourceTooltip<'ui> {
309 fn new(ui: &'ui Ui) -> Self {
310 Self { _ui: ui }
311 }
312
313 pub fn end(self) {
317 }
319}
320
321impl Drop for DragDropSourceTooltip<'_> {
322 fn drop(&mut self) {
323 unsafe {
324 sys::igEndDragDropSource();
325 }
326 }
327}
328
329#[derive(Debug)]
334pub struct DragDropTarget<'ui>(&'ui Ui);
335
336impl<'ui> DragDropTarget<'ui> {
337 pub fn accept_payload_empty(
349 &self,
350 name: impl AsRef<str>,
351 flags: DragDropFlags,
352 ) -> Option<DragDropPayloadEmpty> {
353 self.accept_payload(name, flags)?
354 .ok()
355 .map(|payload_pod: DragDropPayloadPod<()>| DragDropPayloadEmpty {
356 preview: payload_pod.preview,
357 delivery: payload_pod.delivery,
358 })
359 }
360
361 pub fn accept_payload<T: 'static + Copy, Name: AsRef<str>>(
372 &self,
373 name: Name,
374 flags: DragDropFlags,
375 ) -> Option<Result<DragDropPayloadPod<T>, PayloadIsWrongType>> {
376 let output = unsafe { self.accept_payload_unchecked(name, flags) };
377
378 output.map(decode_typed_payload)
379 }
380
381 pub unsafe fn accept_payload_unchecked(
392 &self,
393 name: impl AsRef<str>,
394 flags: DragDropFlags,
395 ) -> Option<DragDropPayload> {
396 validate_payload_type_name(name.as_ref(), "DragDropTarget::accept_payload_unchecked()");
397 let inner =
398 unsafe { sys::igAcceptDragDropPayload(self.0.scratch_txt(name), flags.bits() as i32) };
399
400 if inner.is_null() {
401 None
402 } else {
403 Some(DragDropPayload::from_raw(unsafe { *inner }))
404 }
405 }
406
407 pub fn pop(self) {
411 }
413}
414
415impl Drop for DragDropTarget<'_> {
416 fn drop(&mut self) {
417 unsafe {
418 sys::igEndDragDropTarget();
419 }
420 }
421}
422
423#[repr(C)]
431#[derive(Copy, Clone)]
432struct TypedPayload<T: Copy> {
433 type_id: any::TypeId,
434 data: T,
435}
436
437fn make_typed_payload<T: Copy + 'static>(data: T) -> TypedPayload<T> {
438 let mut out = std::mem::MaybeUninit::<TypedPayload<T>>::zeroed();
440 unsafe {
441 let ptr = out.as_mut_ptr();
442 std::ptr::addr_of_mut!((*ptr).type_id).write(any::TypeId::of::<T>());
443 std::ptr::addr_of_mut!((*ptr).data).write(data);
444 out.assume_init()
445 }
446}
447
448#[derive(Debug, Clone, Copy)]
450pub struct DragDropPayloadEmpty {
451 pub preview: bool,
453 pub delivery: bool,
455}
456
457#[derive(Debug, Clone, Copy)]
459pub struct DragDropPayloadPod<T> {
460 pub data: T,
462 pub preview: bool,
464 pub delivery: bool,
466}
467
468#[derive(Debug)]
470pub struct DragDropPayload {
471 pub data: *const ffi::c_void,
473 pub size: usize,
475 pub preview: bool,
477 pub delivery: bool,
479}
480
481impl DragDropPayload {
482 fn from_raw(inner: sys::ImGuiPayload) -> Self {
483 let size = if inner.DataSize <= 0 || inner.Data.is_null() {
484 0
485 } else {
486 inner.DataSize as usize
487 };
488
489 Self {
490 data: inner.Data,
491 size,
492 preview: inner.Preview,
493 delivery: inner.Delivery,
494 }
495 }
496}
497
498#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500pub struct PayloadIsWrongType;
501
502impl std::fmt::Display for PayloadIsWrongType {
503 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
504 write!(f, "drag drop payload has wrong type")
505 }
506}
507
508impl std::error::Error for PayloadIsWrongType {}
509
510fn decode_typed_payload<T: 'static + Copy>(
511 payload: DragDropPayload,
512) -> Result<DragDropPayloadPod<T>, PayloadIsWrongType> {
513 if payload.data.is_null() || payload.size != std::mem::size_of::<TypedPayload<T>>() {
514 return Err(PayloadIsWrongType);
515 }
516
517 let typed_payload: TypedPayload<T> =
519 unsafe { std::ptr::read_unaligned(payload.data as *const TypedPayload<T>) };
520
521 if typed_payload.type_id == any::TypeId::of::<T>() {
522 Ok(DragDropPayloadPod {
523 data: typed_payload.data,
524 preview: payload.preview,
525 delivery: payload.delivery,
526 })
527 } else {
528 Err(PayloadIsWrongType)
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535
536 fn payload_bytes<T: Copy + 'static>(value: T) -> Vec<u8> {
537 let payload = make_typed_payload(value);
538 let size = std::mem::size_of::<TypedPayload<T>>();
539 let mut out = vec![0u8; size];
540 unsafe {
541 std::ptr::copy_nonoverlapping(
542 std::ptr::from_ref(&payload).cast::<u8>(),
543 out.as_mut_ptr(),
544 size,
545 );
546 }
547 out
548 }
549
550 #[test]
551 fn typed_payload_bytes_are_deterministic() {
552 assert_eq!(payload_bytes(7u8), payload_bytes(7u8));
554 assert_eq!(payload_bytes(0x1122_3344u32), payload_bytes(0x1122_3344u32));
555 }
556
557 #[test]
558 fn typed_payload_can_be_read_unaligned() {
559 let bytes = payload_bytes(7u8);
560 let mut buf = vec![0u8; 1 + bytes.len()];
561 buf[1..].copy_from_slice(&bytes);
562 let ptr = unsafe { buf.as_ptr().add(1) } as *const TypedPayload<u8>;
563 let decoded = unsafe { std::ptr::read_unaligned(ptr) };
564 assert_eq!(decoded.type_id, any::TypeId::of::<u8>());
565 assert_eq!(decoded.data, 7u8);
566 }
567
568 #[test]
569 fn payload_submission_rejects_imgui_assert_conditions_before_ffi() {
570 let byte = 1u8;
571 let ptr = std::ptr::from_ref(&byte).cast::<ffi::c_void>();
572 let long_name = "x".repeat(MAX_PAYLOAD_TYPE_LEN + 1);
573
574 assert!(
575 std::panic::catch_unwind(|| {
576 validate_payload_submission(
577 &long_name,
578 ptr,
579 1,
580 "payload_submission_rejects_imgui_assert_conditions_before_ffi",
581 );
582 })
583 .is_err()
584 );
585 assert!(
586 std::panic::catch_unwind(|| {
587 validate_payload_submission(
588 "payload",
589 std::ptr::null(),
590 1,
591 "payload_submission_rejects_imgui_assert_conditions_before_ffi",
592 );
593 })
594 .is_err()
595 );
596 assert!(
597 std::panic::catch_unwind(|| {
598 validate_payload_submission(
599 "payload",
600 ptr,
601 0,
602 "payload_submission_rejects_imgui_assert_conditions_before_ffi",
603 );
604 })
605 .is_err()
606 );
607 }
608
609 #[test]
610 fn typed_accept_rejects_trailing_payload_bytes() {
611 let bytes = payload_bytes(7u8);
612 let mut buf = bytes.clone();
613 buf.push(0);
614
615 let payload = DragDropPayload {
616 data: buf.as_ptr().cast::<ffi::c_void>(),
617 size: buf.len(),
618 preview: false,
619 delivery: false,
620 };
621
622 assert_ne!(payload.size, std::mem::size_of::<TypedPayload<u8>>());
623 assert!(decode_typed_payload::<u8>(payload).is_err());
624 }
625}