dear_imgui_rs/
drag_drop.rs

1#![allow(
2    clippy::cast_possible_truncation,
3    clippy::cast_sign_loss,
4    clippy::as_conversions,
5    // We intentionally keep explicit casts for FFI clarity; avoid auto-fix churn.
6    clippy::unnecessary_cast
7)]
8//! Drag and Drop functionality for Dear ImGui
9//!
10//! This module provides a complete drag and drop system that allows users to transfer
11//! data between UI elements. The system consists of drag sources and drop targets,
12//! with type-safe payload management.
13//!
14//! # Basic Usage
15//!
16//! ```no_run
17//! # use dear_imgui_rs::*;
18//! # let mut ctx = Context::create();
19//! # let ui = ctx.frame();
20//! // Create a drag source
21//! ui.button("Drag me!");
22//! if let Some(source) = ui.drag_drop_source_config("MY_DATA").begin() {
23//!     ui.text("Dragging...");
24//!     source.end();
25//! }
26//!
27//! // Create a drop target
28//! ui.button("Drop here!");
29//! if let Some(target) = ui.drag_drop_target() {
30//!     if target.accept_payload_empty("MY_DATA", DragDropFlags::empty()).is_some() {
31//!         println!("Data dropped!");
32//!     }
33//!     target.pop();
34//! }
35//! ```
36
37use crate::{Condition, Ui, sys};
38use std::{any, ffi};
39
40bitflags::bitflags! {
41    /// Flags for drag and drop operations
42    #[repr(transparent)]
43    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44    pub struct DragDropFlags: u32 {
45        /// No flags
46        const NONE = 0;
47
48        // Source flags
49        /// Disable preview tooltip during drag
50        const SOURCE_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_SourceNoPreviewTooltip as u32;
51        /// Don't disable hover during drag
52        const SOURCE_NO_DISABLE_HOVER = sys::ImGuiDragDropFlags_SourceNoDisableHover as u32;
53        /// Don't open tree nodes/headers when hovering during drag
54        const SOURCE_NO_HOLD_TO_OPEN_OTHERS = sys::ImGuiDragDropFlags_SourceNoHoldToOpenOthers as u32;
55        /// Allow items without unique ID to be drag sources
56        const SOURCE_ALLOW_NULL_ID = sys::ImGuiDragDropFlags_SourceAllowNullID as u32;
57        /// External drag source (from outside ImGui)
58        const SOURCE_EXTERN = sys::ImGuiDragDropFlags_SourceExtern as u32;
59        /// Automatically expire payload if source stops being submitted
60        const PAYLOAD_AUTO_EXPIRE = sys::ImGuiDragDropFlags_PayloadAutoExpire as u32;
61
62        // Target flags
63        /// Accept payload before mouse button is released
64        const ACCEPT_BEFORE_DELIVERY = sys::ImGuiDragDropFlags_AcceptBeforeDelivery as u32;
65        /// Don't draw default highlight rectangle when hovering
66        const ACCEPT_NO_DRAW_DEFAULT_RECT = sys::ImGuiDragDropFlags_AcceptNoDrawDefaultRect as u32;
67        /// Don't show preview tooltip from source
68        const ACCEPT_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_AcceptNoPreviewTooltip as u32;
69        /// Convenience flag for peeking (ACCEPT_BEFORE_DELIVERY | ACCEPT_NO_DRAW_DEFAULT_RECT)
70        const ACCEPT_PEEK_ONLY = sys::ImGuiDragDropFlags_AcceptPeekOnly as u32;
71    }
72}
73
74impl Ui {
75    /// Creates a new drag drop source configuration
76    ///
77    /// # Arguments
78    /// * `name` - Identifier for this drag source (must match target name)
79    ///
80    /// # Example
81    /// ```no_run
82    /// # use dear_imgui_rs::*;
83    /// # let mut ctx = Context::create();
84    /// # let ui = ctx.frame();
85    /// ui.button("Drag me!");
86    /// if let Some(source) = ui.drag_drop_source_config("MY_DATA")
87    ///     .flags(DragDropFlags::SOURCE_NO_PREVIEW_TOOLTIP)
88    ///     .begin() {
89    ///     ui.text("Custom drag tooltip");
90    ///     source.end();
91    /// }
92    /// ```
93    pub fn drag_drop_source_config<T: AsRef<str>>(&self, name: T) -> DragDropSource<'_, T> {
94        DragDropSource {
95            name,
96            flags: DragDropFlags::NONE,
97            cond: Condition::Always,
98            ui: self,
99        }
100    }
101
102    /// Creates a drag drop target for the last item
103    ///
104    /// Returns `Some(DragDropTarget)` if the last item can accept drops,
105    /// `None` otherwise.
106    ///
107    /// # Example
108    /// ```no_run
109    /// # use dear_imgui_rs::*;
110    /// # let mut ctx = Context::create();
111    /// # let ui = ctx.frame();
112    /// ui.button("Drop target");
113    /// if let Some(target) = ui.drag_drop_target() {
114    ///     if target.accept_payload_empty("MY_DATA", DragDropFlags::NONE).is_some() {
115    ///         println!("Received drop!");
116    ///     }
117    ///     target.pop();
118    /// }
119    /// ```
120    #[doc(alias = "BeginDragDropTarget")]
121    pub fn drag_drop_target(&self) -> Option<DragDropTarget<'_>> {
122        let should_begin = unsafe { sys::igBeginDragDropTarget() };
123        if should_begin {
124            Some(DragDropTarget(self))
125        } else {
126            None
127        }
128    }
129}
130
131/// Builder for creating drag drop sources
132///
133/// This struct is created by [`Ui::drag_drop_source_config`] and provides
134/// a fluent interface for configuring drag sources.
135#[derive(Debug)]
136pub struct DragDropSource<'ui, T> {
137    name: T,
138    flags: DragDropFlags,
139    cond: Condition,
140    ui: &'ui Ui,
141}
142
143impl<'ui, T: AsRef<str>> DragDropSource<'ui, T> {
144    /// Set flags for this drag source
145    ///
146    /// # Arguments
147    /// * `flags` - Combination of source-related `DragDropFlags`
148    #[inline]
149    pub fn flags(mut self, flags: DragDropFlags) -> Self {
150        self.flags = flags;
151        self
152    }
153
154    /// Set condition for when to update the payload
155    ///
156    /// # Arguments
157    /// * `cond` - When to update the payload data
158    #[inline]
159    pub fn condition(mut self, cond: Condition) -> Self {
160        self.cond = cond;
161        self
162    }
163
164    /// Begin drag source with empty payload
165    ///
166    /// This is the safest option for simple drag and drop operations.
167    /// Use shared state or other mechanisms to transfer actual data.
168    ///
169    /// Returns a tooltip token if dragging started, `None` otherwise.
170    #[inline]
171    pub fn begin(self) -> Option<DragDropSourceTooltip<'ui>> {
172        self.begin_payload(())
173    }
174
175    /// Begin drag source with typed payload
176    ///
177    /// The payload data will be copied and managed by ImGui.
178    /// The data must be `Copy + 'static` for safety.
179    ///
180    /// # Arguments
181    /// * `payload` - Data to transfer (must be Copy + 'static)
182    ///
183    /// Returns a tooltip token if dragging started, `None` otherwise.
184    #[inline]
185    pub fn begin_payload<P: Copy + 'static>(
186        self,
187        payload: P,
188    ) -> Option<DragDropSourceTooltip<'ui>> {
189        unsafe {
190            let payload = TypedPayload::new(payload);
191            self.begin_payload_unchecked(
192                &payload as *const _ as *const ffi::c_void,
193                std::mem::size_of::<TypedPayload<P>>(),
194            )
195        }
196    }
197
198    /// Begin drag source with raw payload data (unsafe)
199    ///
200    /// # Safety
201    /// The caller must ensure:
202    /// - `ptr` points to valid data of `size` bytes
203    /// - The data remains valid for the duration of the drag operation
204    /// - The data layout matches what targets expect
205    ///
206    /// # Arguments
207    /// * `ptr` - Pointer to payload data
208    /// * `size` - Size of payload data in bytes
209    pub unsafe fn begin_payload_unchecked(
210        &self,
211        ptr: *const ffi::c_void,
212        size: usize,
213    ) -> Option<DragDropSourceTooltip<'ui>> {
214        unsafe {
215            let should_begin = sys::igBeginDragDropSource(self.flags.bits() as i32);
216
217            if should_begin {
218                sys::igSetDragDropPayload(
219                    self.ui.scratch_txt(&self.name),
220                    ptr,
221                    size,
222                    self.cond as i32,
223                );
224
225                Some(DragDropSourceTooltip::new(self.ui))
226            } else {
227                None
228            }
229        }
230    }
231}
232
233/// Token representing an active drag source tooltip
234///
235/// While this token exists, you can add UI elements that will be shown
236/// as a tooltip during the drag operation.
237#[derive(Debug)]
238pub struct DragDropSourceTooltip<'ui> {
239    _ui: &'ui Ui,
240}
241
242impl<'ui> DragDropSourceTooltip<'ui> {
243    fn new(ui: &'ui Ui) -> Self {
244        Self { _ui: ui }
245    }
246
247    /// End the drag source tooltip manually
248    ///
249    /// This is called automatically when the token is dropped.
250    pub fn end(self) {
251        // Drop will handle cleanup
252    }
253}
254
255impl Drop for DragDropSourceTooltip<'_> {
256    fn drop(&mut self) {
257        unsafe {
258            sys::igEndDragDropSource();
259        }
260    }
261}
262
263/// Drag drop target for accepting payloads
264///
265/// This struct is created by [`Ui::drag_drop_target`] and provides
266/// methods for accepting different types of payloads.
267#[derive(Debug)]
268pub struct DragDropTarget<'ui>(&'ui Ui);
269
270impl<'ui> DragDropTarget<'ui> {
271    /// Accept an empty payload
272    ///
273    /// This is the safest option for drag and drop operations.
274    /// Use this when you only need to know that a drop occurred,
275    /// not transfer actual data.
276    ///
277    /// # Arguments
278    /// * `name` - Payload type name (must match source name)
279    /// * `flags` - Accept flags
280    ///
281    /// Returns payload info if accepted, `None` otherwise.
282    pub fn accept_payload_empty(
283        &self,
284        name: impl AsRef<str>,
285        flags: DragDropFlags,
286    ) -> Option<DragDropPayloadEmpty> {
287        self.accept_payload(name, flags)?
288            .ok()
289            .map(|payload_pod: DragDropPayloadPod<()>| DragDropPayloadEmpty {
290                preview: payload_pod.preview,
291                delivery: payload_pod.delivery,
292            })
293    }
294
295    /// Accept a typed payload
296    ///
297    /// Attempts to accept a payload with the specified type.
298    /// Returns `Ok(payload)` if the type matches, `Err(PayloadIsWrongType)` if not.
299    ///
300    /// # Arguments
301    /// * `name` - Payload type name (must match source name)
302    /// * `flags` - Accept flags
303    ///
304    /// Returns `Some(Result<payload, error>)` if payload exists, `None` otherwise.
305    pub fn accept_payload<T: 'static + Copy, Name: AsRef<str>>(
306        &self,
307        name: Name,
308        flags: DragDropFlags,
309    ) -> Option<Result<DragDropPayloadPod<T>, PayloadIsWrongType>> {
310        let output = unsafe { self.accept_payload_unchecked(name, flags) };
311
312        output.map(|payload| {
313            let typed_payload = unsafe { &*(payload.data as *const TypedPayload<T>) };
314
315            if typed_payload.type_id == any::TypeId::of::<T>() {
316                Ok(DragDropPayloadPod {
317                    data: typed_payload.data,
318                    preview: payload.preview,
319                    delivery: payload.delivery,
320                })
321            } else {
322                Err(PayloadIsWrongType)
323            }
324        })
325    }
326
327    /// Accept raw payload data (unsafe)
328    ///
329    /// # Safety
330    /// The returned pointer and size are managed by ImGui and may become
331    /// invalid at any time. The caller must not access the data after
332    /// the drag operation completes.
333    ///
334    /// # Arguments
335    /// * `name` - Payload type name
336    /// * `flags` - Accept flags
337    pub unsafe fn accept_payload_unchecked(
338        &self,
339        name: impl AsRef<str>,
340        flags: DragDropFlags,
341    ) -> Option<DragDropPayload> {
342        let inner =
343            unsafe { sys::igAcceptDragDropPayload(self.0.scratch_txt(name), flags.bits() as i32) };
344
345        if inner.is_null() {
346            None
347        } else {
348            let inner = unsafe { *inner };
349            Some(DragDropPayload {
350                data: inner.Data,
351                size: inner.DataSize as usize,
352                preview: inner.Preview,
353                delivery: inner.Delivery,
354            })
355        }
356    }
357
358    /// End the drag drop target
359    ///
360    /// This is called automatically when the token is dropped.
361    pub fn pop(self) {
362        // Drop will handle cleanup
363    }
364}
365
366impl Drop for DragDropTarget<'_> {
367    fn drop(&mut self) {
368        unsafe {
369            sys::igEndDragDropTarget();
370        }
371    }
372}
373
374// Payload types and utilities
375
376/// Wrapper for typed payloads with runtime type checking
377#[repr(C)]
378struct TypedPayload<T> {
379    type_id: any::TypeId,
380    data: T,
381}
382
383impl<T: 'static> TypedPayload<T> {
384    fn new(data: T) -> Self {
385        Self {
386            type_id: any::TypeId::of::<T>(),
387            data,
388        }
389    }
390}
391
392/// Empty payload (no data, just notification)
393#[derive(Debug, Clone, Copy)]
394pub struct DragDropPayloadEmpty {
395    /// True when hovering over target
396    pub preview: bool,
397    /// True when payload should be delivered
398    pub delivery: bool,
399}
400
401/// Typed payload with data
402#[derive(Debug, Clone, Copy)]
403pub struct DragDropPayloadPod<T> {
404    /// The payload data
405    pub data: T,
406    /// True when hovering over target
407    pub preview: bool,
408    /// True when payload should be delivered
409    pub delivery: bool,
410}
411
412/// Raw payload data
413#[derive(Debug)]
414pub struct DragDropPayload {
415    /// Pointer to payload data (managed by ImGui)
416    pub data: *const ffi::c_void,
417    /// Size of payload data in bytes
418    pub size: usize,
419    /// True when hovering over target
420    pub preview: bool,
421    /// True when payload should be delivered
422    pub delivery: bool,
423}
424
425/// Error type for payload type mismatches
426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427pub struct PayloadIsWrongType;
428
429impl std::fmt::Display for PayloadIsWrongType {
430    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431        write!(f, "drag drop payload has wrong type")
432    }
433}
434
435impl std::error::Error for PayloadIsWrongType {}