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