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 {}