arcdps_imgui/
drag_drop.rs

1//! Structs to create a Drag and Drop sequence. Almost all structs are re-exported
2//! and can be accessed from the crate root; some additional utilities can be found in here.
3//!
4//! A DragDrop is a UI mechanism where users can appear to "drag"
5//! some data from one [source](DragDropSource) to one [target](DragDropTarget).
6//! A source and a target must both have some `name` identifier, which is declared when they
7//! are created. If these names are equal, then a `payload` of some kind
8//! will be given to the target caller whne the user releases their mouse button over
9//! the target (additionally, the UI will reflect that the payload *can* be deposited
10//! in the target).
11//!
12//! The complexity of this implementation is primarily in managing this payload. Users
13//! can provide three different kinds of payloads:
14//!
15//! 1.  Users can give an [empty payload](DragDropPayloadEmpty) with [begin](DragDropSource::begin).
16//!     This payload type is essentially just a notification system, but using some shared state,
17//!     this can be reasonably powerful, and is the safest way to transfer non-Copy data offered
18//!     right now.
19//! 2.  Users can give a [simple Copy payload](DragDropPayloadPod) with [begin](DragDropSource::begin_payload).
20//!     This allows users to copy data to Dear ImGui, which will take ownership over it, and then be given
21//!     it back to the Target. Please note: users are of course free to not drop any drag (cancel a drag),
22//!     so this data could easily be lost forever. Our `'static + Copy` bound is intended to keep users
23//!     to simplistic types.
24//! 3.  An unsafe implementation is provided which allows for any data to be unsafely copied. Note that once
25//!     you use this method, the safe implementations in #1 and #2 can create memory unsafety problems; notably,
26//!     they both assume that a payload has certain header information within it.
27//!
28//! For examples of each payload type, see [DragDropSource].
29use std::{any, ffi, marker::PhantomData};
30
31use crate::{sys, Condition, Ui};
32use bitflags::bitflags;
33
34bitflags!(
35    /// Flags for igBeginDragDropSource(), igAcceptDragDropPayload()
36    #[repr(transparent)]
37    pub struct DragDropFlags: u32 {
38        /// By default, a successful call to igBeginDragDropSource opens a tooltip so you can
39        /// display a preview or description of the source contents. This flag disable this
40        /// behavior.
41        const SOURCE_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_SourceNoPreviewTooltip;
42        /// By default, when dragging we clear data so that igIsItemHovered() will return false, to
43        /// avoid subsequent user code submitting tooltips. This flag disable this behavior so you
44        /// can still call igIsItemHovered() on the source item.
45        const SOURCE_NO_DISABLE_HOVER = sys::ImGuiDragDropFlags_SourceNoDisableHover;
46        /// Disable the behavior that allows to open tree nodes and collapsing header by holding
47        /// over them while dragging a source item.
48        const SOURCE_NO_HOLD_TO_OPEN_OTHERS = sys::ImGuiDragDropFlags_SourceNoHoldToOpenOthers;
49        /// Allow items such as igText(), igImage() that have no unique identifier to be used as
50        /// drag source, by manufacturing a temporary identifier based on their window-relative
51        /// position. This is extremely unusual within the dear imgui ecosystem and so we made it
52        /// explicit.
53        const SOURCE_ALLOW_NULL_ID = sys::ImGuiDragDropFlags_SourceAllowNullID;
54        /// External source (from outside of imgui), won't attempt to read current item/window
55        /// info. Will always return true. Only one Extern source can be active simultaneously.
56        const SOURCE_EXTERN = sys::ImGuiDragDropFlags_SourceExtern;
57        /// Automatically expire the payload if the source ceases to be submitted (otherwise
58        /// payloads are persisting while being dragged)
59        const SOURCE_AUTO_EXPIRE_PAYLOAD = sys::ImGuiDragDropFlags_SourceAutoExpirePayload;
60        /// igAcceptDragDropPayload() will returns true even before the mouse button is released.
61        /// You can then call igIsDelivery() to test if the payload needs to be delivered.
62        const ACCEPT_BEFORE_DELIVERY = sys::ImGuiDragDropFlags_AcceptBeforeDelivery;
63        /// Do not draw the default highlight rectangle when hovering over target.
64        const ACCEPT_NO_DRAW_DEFAULT_RECT = sys::ImGuiDragDropFlags_AcceptNoDrawDefaultRect;
65        /// Request hiding the igBeginDragDropSource tooltip from the igBeginDragDropTarget site.
66        const ACCEPT_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_AcceptNoPreviewTooltip;
67        /// For peeking ahead and inspecting the payload before delivery. This is just a convenience
68        /// flag for the intersection of `ACCEPT_BEFORE_DELIVERY` and `ACCEPT_NO_DRAW_DEFAULT_RECT`
69        const ACCEPT_PEEK_ONLY = sys::ImGuiDragDropFlags_AcceptPeekOnly;
70    }
71);
72
73/// Creates a source for drag drop data out of the last ID created.
74///
75/// ```no_run
76/// # use arcdps_imgui::*;
77/// fn show_ui(ui: &Ui<'_>) {
78///     ui.button("Hello, I am a drag source!");
79///     
80///     // Creates an empty DragSource with no tooltip
81///     DragDropSource::new("BUTTON_DRAG").begin(ui);
82/// }
83/// ```
84///
85/// Notice especially the `"BUTTON_DRAG"` name -- this is the identifier of this
86/// DragDropSource; [DragDropTarget]'s will specify an identifier to *receive*, and these
87/// names must match up. A single item should only have one [DragDropSource], though
88/// a target may have multiple different targets.
89///
90/// DropDropSources don't do anything until you use one of the three `begin_` methods
91/// on this struct. Each of these methods describes how you handle the Payload which ImGui
92/// will manage, and then give to a [DragDropTarget], which will received the payload. The
93/// simplest and safest Payload is the empty payload, created with [begin](Self::begin).
94#[derive(Debug)]
95pub struct DragDropSource<T> {
96    name: T,
97    flags: DragDropFlags,
98    cond: Condition,
99}
100
101impl<T: AsRef<str>> DragDropSource<T> {
102    /// Creates a new [DragDropSource] with no flags and the `Condition::Always` with the given name.
103    /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up
104    /// Source/Target for DragDrop.
105    pub fn new(name: T) -> Self {
106        Self {
107            name,
108            flags: DragDropFlags::empty(),
109            cond: Condition::Always,
110        }
111    }
112
113    /// Sets the flags on the [DragDropSource]. Only the flags `SOURCE_NO_PREVIEW_TOOLTIP`,
114    /// `SOURCE_NO_DISABLE_HOVER`, `SOURCE_NO_HOLD_TO_OPEN_OTHERS`, `SOURCE_ALLOW_NULL_ID`,
115    /// `SOURCE_EXTERN`, `SOURCE_AUTO_EXPIRE_PAYLOAD` make semantic sense, but any other flags will
116    /// be accepted without panic.
117    #[inline]
118    pub fn flags(mut self, flags: DragDropFlags) -> Self {
119        self.flags = flags;
120        self
121    }
122
123    /// Sets the condition on the [DragDropSource]. Defaults to [Always](Condition::Always).
124    #[inline]
125    pub fn condition(mut self, cond: Condition) -> Self {
126        self.cond = cond;
127        self
128    }
129
130    /// Creates the source of a drag and returns a handle on the tooltip.
131    /// This handle can be immediately dropped without binding it, in which case a default empty
132    /// circle will be used for the "blank" tooltip as this item is being dragged around.
133    ///
134    /// Otherwise, use this tooltip to add data which will display as this item is dragged.
135    /// If `SOURCE_NO_PREVIEW_TOOLTIP` is enabled, however, no preview will be displayed
136    /// and this returned token does nothing. Additionally, a given target may use the flag
137    /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown.
138    ///
139    /// This drag has no payload, but is still probably the most useful way in imgui-rs to handle payloads.
140    /// Using `once_cell` or some shared data, this pattern can be very powerful:
141    ///
142    /// ```no_run
143    /// # use arcdps_imgui::*;
144    /// fn show_ui(ui: &Ui<'_>, drop_message: &mut Option<String>) {
145    ///     ui.button("Drag me!");
146    ///
147    ///     let drag_drop_name = "Test Drag";
148    ///     
149    ///     // drag drop SOURCE
150    ///     if DragDropSource::new(drag_drop_name).begin(ui).is_some() {
151    ///         // warning -- this would allocate every frame if `DragDropSource` has
152    ///         // condition `Always`, which it does by default. We're okay with that for
153    ///         // this example, but real code probably wouldn't want to allocate so much.
154    ///         *drop_message = Some("Test Payload".to_string());
155    ///     }
156    ///
157    ///     ui.button("Target me!");
158    ///
159    ///     // drag drop TARGET
160    ///     if let Some(target) = arcdps_imgui::DragDropTarget::new(ui) {
161    ///         if target
162    ///             .accept_payload_empty(drag_drop_name, DragDropFlags::empty())
163    ///             .is_some()
164    ///         {
165    ///             let msg = drop_message.take().unwrap();
166    ///             assert_eq!(msg, "Test Payload");
167    ///         }
168    ///
169    ///         target.pop();
170    ///     }
171    /// }
172    /// ```
173    ///
174    /// In the above, you'll see how the payload is really just a message passing service.
175    /// If you want to pass a simple integer or other "plain old data", take a look at
176    /// [begin_payload](Self::begin_payload).
177    #[inline]
178    pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option<DragDropSourceToolTip<'ui>> {
179        self.begin_payload(ui, ())
180    }
181
182    /// Creates the source of a drag and returns a handle on the tooltip.
183    /// This handle can be immediately dropped without binding it, in which case a default empty
184    /// circle will be used for the "blank" tooltip as this item is being dragged around.
185    ///
186    /// Otherwise, use this tooltip to add data which will display as this item is dragged.
187    /// If `SOURCE_NO_PREVIEW_TOOLTIP` is enabled, however, no preview will be displayed
188    /// and this returned token does nothing. Additionally, a given target may use the flag
189    /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown.
190    ///
191    /// This function also takes a payload in the form of `T: Copy + 'static`. ImGui will
192    /// memcpy this data immediately to an internally held buffer, and will return the data
193    /// to [DragDropTarget].
194    ///
195    /// ```no_run
196    /// # use arcdps_imgui::*;
197    /// fn show_ui(ui: &Ui<'_>) {
198    ///     ui.button("Drag me!");
199    ///
200    ///     let drag_drop_name = "Test Drag";
201    ///     let msg_to_send = "hello there sailor";
202    ///     
203    ///     // drag drop SOURCE
204    ///     if let Some(tooltip) = DragDropSource::new(drag_drop_name).begin_payload(ui, msg_to_send) {
205    ///         ui.text("Sending message!");
206    ///         tooltip.end();
207    ///     }
208    ///
209    ///     ui.button("Target me!");
210    ///
211    ///     // drag drop TARGET
212    ///     if let Some(target) = arcdps_imgui::DragDropTarget::new(ui) {
213    ///         if let Some(Ok(payload_data)) = target
214    ///             .accept_payload::<&'static str, _>(drag_drop_name, DragDropFlags::empty())
215    ///         {
216    ///             let msg = payload_data.data;
217    ///             assert_eq!(msg, msg_to_send);
218    ///         }
219    ///
220    ///         target.pop();
221    ///     }
222    /// }
223    /// ```
224    #[inline]
225    pub fn begin_payload<'ui, P: Copy + 'static>(
226        self,
227        ui: &Ui<'ui>,
228        payload: P,
229    ) -> Option<DragDropSourceToolTip<'ui>> {
230        unsafe {
231            let payload = TypedPayload::new(payload);
232            self.begin_payload_unchecked(
233                ui,
234                &payload as *const _ as *const ffi::c_void,
235                std::mem::size_of::<TypedPayload<P>>(),
236            )
237        }
238    }
239
240    /// Creates the source of a drag and returns a handle on the tooltip.
241    /// This handle can be immediately dropped without binding it, in which case a default empty
242    /// circle will be used for the "blank" tooltip as this item is being dragged around.
243    ///
244    /// Otherwise, use this tooltip to add data which will display as this item is dragged.
245    /// If `SOURCE_NO_PREVIEW_TOOLTIP` is enabled, however, no preview will be displayed
246    /// and this returned token does nothing. Additionally, a given target may use the flag
247    /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown.
248    ///
249    /// This function also takes a payload of any `*const T`. ImGui will
250    /// memcpy this data immediately to an internally held buffer, and will return the data
251    /// to [DragDropTarget].
252    ///
253    /// ## Safety
254    /// This function itself will not cause a panic, but using it directly opts you into
255    /// managing the lifetime of the pointer provided yourself. Dear ImGui will execute a memcpy on
256    /// the data passed in with the size (in bytes) given, but this is, of course, just a copy,
257    /// so if you pass in an `&String`, for example, the underlying String data will not be cloned,
258    /// and could easily dangle if the `String` is dropped.
259    ///
260    /// Moreover, if `Condition::Always` is set (as it is by default), you will be copying in your data
261    /// every time this function is ran in your update loop, which if it involves an allocating and then
262    /// handing the allocation to ImGui, would result in a significant amount of data created.
263    ///
264    /// Overall, users should be very sure that this function is needed before they reach for it, and instead
265    /// should consider either [begin](Self::begin) or [begin_payload](Self::begin_payload).
266    #[inline]
267    pub unsafe fn begin_payload_unchecked<'ui>(
268        &self,
269        ui: &Ui<'ui>,
270        ptr: *const ffi::c_void,
271        size: usize,
272    ) -> Option<DragDropSourceToolTip<'ui>> {
273        let should_begin = sys::igBeginDragDropSource(self.flags.bits() as i32);
274
275        if should_begin {
276            sys::igSetDragDropPayload(ui.scratch_txt(&self.name), ptr, size, self.cond as i32);
277
278            Some(DragDropSourceToolTip::push())
279        } else {
280            None
281        }
282    }
283}
284
285/// A helper struct for RAII drap-drop support.
286pub struct DragDropSourceToolTip<'ui>(PhantomData<Ui<'ui>>);
287
288impl DragDropSourceToolTip<'_> {
289    /// Creates a new tooltip internally.
290    #[inline]
291    fn push() -> Self {
292        Self(PhantomData)
293    }
294
295    /// Ends the tooltip directly. You could choose to simply allow this to drop
296    /// by not calling this, which will also be fine.
297    #[inline]
298    pub fn end(self) {
299        // left empty to invoke drop...
300    }
301}
302
303impl Drop for DragDropSourceToolTip<'_> {
304    fn drop(&mut self) {
305        unsafe { sys::igEndDragDropSource() }
306    }
307}
308
309/// Creates a target for drag drop data out of the last ID created.
310///
311/// ```no_run
312/// # use arcdps_imgui::*;
313/// fn show_ui(ui: &Ui<'_>) {
314///     // Drop something on this button please!
315///     ui.button("Hello, I am a drag Target!");
316///     
317///     if let Some(target) = DragDropTarget::new(ui) {
318///         // accepting an empty payload (which is really just raising an event)
319///         if let Some(_payload_data) = target.accept_payload_empty("BUTTON_DRAG", DragDropFlags::empty()) {
320///             println!("Nice job getting on the payload!");
321///         }
322///    
323///         // and we can accept multiple, different types of payloads with one drop target.
324///         // this is a good pattern for handling different kinds of drag/drop situations with
325///         // different kinds of data in them.
326///         if let Some(Ok(payload_data)) = target.accept_payload::<usize, _>("BUTTON_ID", DragDropFlags::empty()) {
327///             println!("Our payload's data was {}", payload_data.data);
328///         }
329///     }
330/// }
331/// ```
332///
333/// Notice especially the `"BUTTON_DRAG"` and `"BUTTON_ID"` name -- this is the identifier of this
334/// DragDropTarget; [DragDropSource]s will specify an identifier when they send a payload, and these
335/// names must match up. Notice how a target can have multiple acceptances on them -- this is a good
336/// pattern to handle multiple kinds of data which could be passed around.
337///
338/// DropDropTargets don't do anything until you use one of the three `accept_` methods
339/// on this struct. Each of these methods will spit out a _Payload struct with an increasing
340/// amount of information on the Payload. The absolute safest solution is [accept_payload_empty](Self::accept_payload_empty).
341#[derive(Debug)]
342pub struct DragDropTarget<'ui>(&'ui Ui<'ui>);
343
344impl<'ui> DragDropTarget<'ui> {
345    /// Creates a new DragDropTarget, holding the [Ui]'s lifetime for the duration
346    /// of its existence. This is required since this struct runs some code on its Drop
347    /// to end the DragDropTarget code.
348    #[doc(alias = "BeginDragDropTarget")]
349    pub fn new(ui: &'ui Ui<'ui>) -> Option<Self> {
350        let should_begin = unsafe { sys::igBeginDragDropTarget() };
351        if should_begin {
352            Some(Self(ui))
353        } else {
354            None
355        }
356    }
357
358    /// Accepts an empty payload. This is the safest option for raising named events
359    /// in the DragDrop API. See [DragDropSource::begin] for more information on how you
360    /// might use this pattern.
361    ///
362    /// Note: If you began this operation with `begin_payload_unchecked` it always incorrect
363    /// to use this function. Use `accept_payload_unchecked` instead
364    pub fn accept_payload_empty(
365        &self,
366        name: impl AsRef<str>,
367        flags: DragDropFlags,
368    ) -> Option<DragDropPayloadEmpty> {
369        self.accept_payload(name, flags)?
370            .ok()
371            .map(|payload_pod: DragDropPayloadPod<()>| DragDropPayloadEmpty {
372                preview: payload_pod.preview,
373                delivery: payload_pod.delivery,
374            })
375    }
376
377    /// Accepts a payload with plain old data in it. This returns a Result, since you can specify any
378    /// type. The sent type must match the return type (via TypeId) to receive an `Ok`.
379    ///
380    /// Note: If you began this operation with `begin_payload_unchecked` it always incorrect
381    /// to use this function. Use `accept_payload_unchecked` instead
382    pub fn accept_payload<T: 'static + Copy, Name: AsRef<str>>(
383        &self,
384        name: Name,
385        flags: DragDropFlags,
386    ) -> Option<Result<DragDropPayloadPod<T>, PayloadIsWrongType>> {
387        let output = unsafe { self.accept_payload_unchecked(name, flags) };
388
389        // convert the unsafe payload to our Result
390        output.map(|unsafe_payload| {
391            // sheering off the typeid...
392            let received =
393                unsafe { (unsafe_payload.data as *const TypedPayloadHeader).read_unaligned() };
394            let expected = any::TypeId::of::<T>();
395
396            if received.type_id == expected {
397                let data =
398                    unsafe { (unsafe_payload.data as *const TypedPayload<T>).read_unaligned() }
399                        .data;
400                Ok(DragDropPayloadPod {
401                    data,
402                    preview: unsafe_payload.preview,
403                    delivery: unsafe_payload.delivery,
404                })
405            } else {
406                Err(PayloadIsWrongType {
407                    received,
408                    expected: TypedPayloadHeader::new::<T>(),
409                })
410            }
411        })
412    }
413
414    /// Accepts a drag and drop payload  which contains a raw pointer to [c_void](std::ffi::c_void)
415    /// and a size in bytes. Users should generally avoid using this function
416    /// if one of the safer variants is acceptable.
417    ///
418    /// ## Safety
419    ///
420    /// Because this pointer comes from ImGui, absolutely no promises can be made on its
421    /// contents, alignment, or lifetime. Interacting with it is therefore extremely unsafe.
422    /// **Important:** a special note needs to be made to the [ACCEPT_BEFORE_DELIVERY](DragDropFlags::ACCEPT_BEFORE_DELIVERY) flag --
423    /// passing this flag will make this function return `Some(DragDropPayload)` **even before
424    /// the user has actually "dropped" the payload by release their mouse button.**
425    ///
426    /// In safe functions, this works just fine, since the data can be freely copied
427    /// (or doesn't exist at all!). However, if you are working with your own data, you must
428    /// be extremely careful with this data, as you may, effectively, only have immutable access to it.
429    ///
430    /// Moreover, if the `DragDropSource` has also used `Condition::Once` or similar when they sent the data,
431    /// ImGui will assume its data is still valid even after your preview, so corrupting that data could
432    /// lead to all sorts of unsafe behvaior on ImGui's side. In summary, using this function for any data
433    /// which isn't truly `Copy` or "plain old data" is difficult, and requires substantial knowledge
434    /// of the various edge cases.
435    pub unsafe fn accept_payload_unchecked(
436        &self,
437        name: impl AsRef<str>,
438        flags: DragDropFlags,
439    ) -> Option<DragDropPayload> {
440        let inner = sys::igAcceptDragDropPayload(self.0.scratch_txt(name), flags.bits() as i32);
441        if inner.is_null() {
442            None
443        } else {
444            let inner = *inner;
445
446            // @fixme: there are actually other fields on `inner` which I have shorn -- they're
447            // considered internal to imgui (such as id of who sent this), so i've left it for
448            // now this way.
449            Some(DragDropPayload {
450                data: inner.Data,
451                size: inner.DataSize as usize,
452                preview: inner.Preview,
453                delivery: inner.Delivery,
454            })
455        }
456    }
457
458    /// Ends the current target. Ironically, this doesn't really do anything in ImGui
459    /// or in imgui-rs, but it might in the future.
460    pub fn pop(self) {
461        // omitted...exists just to run Drop.
462    }
463}
464
465impl Drop for DragDropTarget<'_> {
466    fn drop(&mut self) {
467        unsafe { sys::igEndDragDropTarget() }
468    }
469}
470
471/// An empty DragDropPayload. It has no data in it, and just includes
472/// two bools with status information.
473#[derive(Debug, Clone, Copy)]
474#[non_exhaustive]
475pub struct DragDropPayloadEmpty {
476    /// Set when [`accept_payload_empty`](DragDropTarget::accept_payload_empty) was called
477    /// and mouse has been hovering the target item.
478    pub preview: bool,
479
480    /// Set when [`accept_payload_empty`](DragDropTarget::accept_payload_empty) was
481    /// called and mouse button is released over the target item.
482    pub delivery: bool,
483}
484
485/// A DragDropPayload with status information and some POD, or plain old data,
486/// in it.
487#[derive(Debug, Copy, Clone)]
488#[non_exhaustive]
489pub struct DragDropPayloadPod<T: 'static + Copy> {
490    /// The kind data which was requested.
491    pub data: T,
492
493    /// Set when [`accept_payload`](DragDropTarget::accept_payload) was called
494    /// and mouse has been hovering the target item.
495    pub preview: bool,
496
497    /// Set when [`accept_payload`](DragDropTarget::accept_payload) was
498    /// called and mouse button is released over the target item.
499    pub delivery: bool,
500}
501
502#[derive(Debug)]
503#[non_exhaustive]
504pub struct DragDropPayload {
505    /// Data which is copied and owned by ImGui. If you have accepted the payload, you can
506    /// take ownership of the data; otherwise, view it immutably. Interacting with `data` is
507    /// very unsafe.
508    pub data: *const ffi::c_void,
509
510    /// The size of the data in bytes.
511    pub size: usize,
512
513    /// Set when [`accept_payload_unchecked`](DragDropTarget::accept_payload_unchecked) was called
514    /// and mouse has been hovering the target item.
515    pub preview: bool,
516
517    /// Set when [`accept_payload_unchecked`](DragDropTarget::accept_payload_unchecked) was
518    /// called and mouse button is released over the target item. If this is set to false, then you
519    /// set DragDropFlags::ACCEPT_BEFORE_DELIVERY and shouldn't mutate `data`.
520    pub delivery: bool,
521}
522
523/// A typed payload.
524#[derive(Debug, Clone, Copy)]
525#[repr(C)]
526struct TypedPayload<T> {
527    header: TypedPayloadHeader,
528    data: T,
529}
530
531impl<T: Copy + 'static> TypedPayload<T> {
532    /// Creates a new typed payload which contains this data.
533    fn new(data: T) -> Self {
534        Self {
535            header: TypedPayloadHeader::new::<T>(),
536            data,
537        }
538    }
539}
540
541/// A header for a typed payload.
542#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
543#[repr(C)]
544struct TypedPayloadHeader {
545    type_id: any::TypeId,
546    #[cfg(debug_assertions)]
547    type_name: &'static str,
548}
549
550impl TypedPayloadHeader {
551    #[cfg(debug_assertions)]
552    fn new<T: 'static>() -> Self {
553        Self {
554            type_id: any::TypeId::of::<T>(),
555            type_name: any::type_name::<T>(),
556        }
557    }
558
559    #[cfg(not(debug_assertions))]
560    fn new<T: 'static>() -> Self {
561        Self {
562            type_id: any::TypeId::of::<T>(),
563        }
564    }
565}
566
567/// Indicates that an incorrect payload type was received. It is opaque,
568/// but you can view useful information with Debug formatting when
569/// `debug_assertions` are enabled.
570#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
571pub struct PayloadIsWrongType {
572    expected: TypedPayloadHeader,
573    received: TypedPayloadHeader,
574}
575
576#[cfg(debug_assertions)]
577impl std::fmt::Display for PayloadIsWrongType {
578    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
579        write!(
580            f,
581            "Payload is {} -- expected {}",
582            self.received.type_name, self.expected.type_name
583        )
584    }
585}
586
587#[cfg(not(debug_assertions))]
588impl std::fmt::Display for PayloadIsWrongType {
589    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
590        f.pad("Payload is wrong type")
591    }
592}
593
594impl std::error::Error for PayloadIsWrongType {}