From 35c0cf0e6f555118162b2886b5eeabf9b54718f4 Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Sun, 24 Jan 2021 14:55:03 -0800 Subject: [PATCH 1/9] drag and drop experimental implementation --- imgui/src/drag_drop.rs | 189 +++++++++++++++++++++++++++++++++++++++++ imgui/src/lib.rs | 2 + 2 files changed, 191 insertions(+) create mode 100644 imgui/src/drag_drop.rs diff --git a/imgui/src/drag_drop.rs b/imgui/src/drag_drop.rs new file mode 100644 index 0000000..2ffbe4b --- /dev/null +++ b/imgui/src/drag_drop.rs @@ -0,0 +1,189 @@ +use std::{ffi, marker::PhantomData, ptr}; + +use crate::{sys, Condition, ImStr, Ui}; +use bitflags::bitflags; + +bitflags!( + /// Flags for igBeginDragDropSource(), igAcceptDragDropPayload() + #[repr(transparent)] + pub struct DragDropFlags: u32 { + /// By default, a successful call to igBeginDragDropSource opens a tooltip so you can + /// display a preview or description of the source contents. This flag disable this + /// behavior. + const SOURCE_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_SourceNoPreviewTooltip; + /// By default, when dragging we clear data so that igIsItemHovered() will return false, to + /// avoid subsequent user code submitting tooltips. This flag disable this behavior so you + /// can still call igIsItemHovered() on the source item. + const SOURCE_NO_DISABLE_HOVER = sys::ImGuiDragDropFlags_SourceNoDisableHover; + /// Disable the behavior that allows to open tree nodes and collapsing header by holding + /// over them while dragging a source item. + const SOURCE_NO_HOLD_TO_OPEN_OTHERS = sys::ImGuiDragDropFlags_SourceNoHoldToOpenOthers; + /// Allow items such as igText(), igImage() that have no unique identifier to be used as + /// drag source, by manufacturing a temporary identifier based on their window-relative + /// position. This is extremely unusual within the dear imgui ecosystem and so we made it + /// explicit. + const SOURCE_ALLOW_NULL_ID = sys::ImGuiDragDropFlags_SourceAllowNullID; + /// External source (from outside of imgui), won't attempt to read current item/window + /// info. Will always return true. Only one Extern source can be active simultaneously. + const SOURCE_EXTERN = sys::ImGuiDragDropFlags_SourceExtern; + /// Automatically expire the payload if the source ceases to be submitted (otherwise + /// payloads are persisting while being dragged) + const SOURCE_AUTO_EXPIRE_PAYLOAD = sys::ImGuiDragDropFlags_SourceAutoExpirePayload; + /// igAcceptDragDropPayload() will returns true even before the mouse button is released. + /// You can then call igIsDelivery() to test if the payload needs to be delivered. + const ACCEPT_BEFORE_DELIVERY = sys::ImGuiDragDropFlags_AcceptBeforeDelivery; + /// Do not draw the default highlight rectangle when hovering over target. + const ACCEPT_NO_DRAW_DEFAULT_RECT = sys::ImGuiDragDropFlags_AcceptNoDrawDefaultRect; + /// Request hiding the igBeginDragDropSource tooltip from the igBeginDragDropTarget site. + const ACCEPT_NO_PREVIEW_TOOLTIP = sys::ImGuiDragDropFlags_AcceptNoPreviewTooltip; + /// For peeking ahead and inspecting the payload before delivery. This is just a convenience + /// flag for the intersection of `ACCEPT_BEFORE_DELIVERY` and `ACCEPT_NO_DRAW_DEFAULT_RECT` + const ACCEPT_PEEK_ONLY = sys::ImGuiDragDropFlags_AcceptPeekOnly; + } +); + +#[derive(Debug)] +pub struct DragDropSource<'a> { + name: &'a ImStr, + flags: DragDropFlags, + payload: *const ffi::c_void, + size: usize, + cond: Condition, +} + +impl<'a> DragDropSource<'a> { + pub fn new(name: &'a ImStr) -> Self { + Self { + name, + flags: DragDropFlags::empty(), + payload: ptr::null(), + size: 0, + cond: Condition::Always, + } + } + + /// Sets the flags on the [DragDropSource]. Only the flags `SOURCE_NO_PREVIEW_TOOLTIP`, + /// `SOURCE_NO_DISABLE_HOVER`, `SOURCE_NO_HOLD_TO_OPEN_OTHERS`, `SOURCE_ALLOW_NULL_ID`, + /// `SOURCE_EXTERN`, `SOURCE_AUTO_EXPIRE_PAYLOAD` make semantic sense, but any other flags will + /// be accepted without panic. + pub fn flags(mut self, flags: DragDropFlags) -> Self { + self.flags = flags; + self + } + + /// Sets the condition on the [DragDropSource]. Defaults to [Always](Condition::Always). + pub fn condition(mut self, cond: Condition) -> Self { + self.cond = cond; + self + } + + /// Sets a payload. This payload will be passed to ImGui, which will provide it to + /// a target when it runs [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload). + /// + /// ## Safety + /// This function is not inherently unsafe, and won't panic itself, but using it opts you into + /// managing the lifetime yourself. When you dereference the pointer given in [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload), + /// you can easily create memory safety problems. + pub unsafe fn payload(mut self, payload: *const T) -> Self { + self.payload = payload as *const ffi::c_void; + self.size = std::mem::size_of::(); + self + } + + /// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() + pub fn begin<'ui>(self, _ui: &'ui Ui) -> Option> { + let should_begin = unsafe { sys::igBeginDragDropSource(self.flags.bits() as i32) }; + + if should_begin { + unsafe { + sys::igSetDragDropPayload(self.name.as_ptr(), self.payload, self.size, self.cond as i32); + + Some(DragDropSourceToolTip::push()) + } + } else { + None + } + } +} + +/// A helper struct for RAII drap-drop support. +pub struct DragDropSourceToolTip<'ui>(PhantomData>); + +impl DragDropSourceToolTip<'_> { + /// Creates a new tooltip internally. + fn push() -> Self { + Self(PhantomData) + } + + /// Ends the tooltip directly. You could choose to simply allow this to drop + /// by not calling this, which will also be fine. + pub fn pop(self) { + // left empty to invoke drop... + } +} + +impl Drop for DragDropSourceToolTip<'_> { + fn drop(&mut self) { + unsafe { sys::igEndDragDropSource() } + } +} + +#[derive(Debug)] +pub struct DragDropPayload { + /// Data which is copied and owned by ImGui. If you have accepted the payload, you can + /// take ownership of the data; otherwise, view it immutably. Interacting with `data` is + /// very unsafe. + pub data: *const ffi::c_void, + /// Set when [`accept_drag_drop_payload`](Self::accept_drag_drop_payload) was called + /// and mouse has been hovering the target item (nb: handle overlapping drag targets). + /// @fixme: literally what does this mean + pub preview: bool, + + /// Set when AcceptDragDropPayload() was called and mouse button is released over the target item. + pub delivery: bool, +} + +#[derive(Debug)] +pub struct DragDropTarget<'ui>(PhantomData>); + +impl<'ui> DragDropTarget<'ui> { + pub fn new(_ui: &Ui<'_>) -> Option { + let should_begin = unsafe { sys::igBeginDragDropTarget() }; + if should_begin { + Some(Self(PhantomData)) + } else { + None + } + } + + /// Accepts, popping the drag_drop payload, if it exists. If `DragDropFlags::ACCEPT_BEFORE_DELIVERY` is + /// set, this function will return `Some` even if the type is wrong as long as there is a payload to accept. + pub fn accept_drag_drop_payload(&self, name: &ImStr, flags: DragDropFlags) -> Option { + unsafe { + let inner = sys::igAcceptDragDropPayload(name.as_ptr(), flags.bits() as i32); + if inner.is_null() { + None + } else { + let inner = *inner; + + Some(DragDropPayload { + data: inner.Data, + preview: inner.Preview, + delivery: inner.Delivery, + }) + } + } + } + + /// Ends the current target. Ironically, this doesn't really do + /// anything in ImGui or in imgui-rs, but it might in the future. + pub fn pop(self) { + // omitted...exists just to run Drop. + } +} + +impl Drop for DragDropTarget<'_> { + fn drop(&mut self) { + unsafe { sys::igEndDragDropTarget() } + } +} diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 5b84be6..31f4c19 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -10,6 +10,7 @@ use std::thread; pub use self::clipboard::*; pub use self::context::*; +pub use self::drag_drop::*; pub use self::draw_list::{ChannelsSplit, DrawListMut, ImColor}; pub use self::fonts::atlas::*; pub use self::fonts::font::*; @@ -52,6 +53,7 @@ use internal::RawCast; mod clipboard; mod columns; mod context; +mod drag_drop; mod draw_list; mod fonts; mod input; From b03e7d7a16f1e91f6ecb36e8dbdca2a58fa033de Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Sun, 24 Jan 2021 15:00:54 -0800 Subject: [PATCH 2/9] clarified some doc comments and made the unsafe variant simpler to understand --- imgui/src/drag_drop.rs | 47 +++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/imgui/src/drag_drop.rs b/imgui/src/drag_drop.rs index 2ffbe4b..d896fec 100644 --- a/imgui/src/drag_drop.rs +++ b/imgui/src/drag_drop.rs @@ -52,6 +52,9 @@ pub struct DragDropSource<'a> { } impl<'a> DragDropSource<'a> { + /// Creates a new [DragDropSource] with no flags and the `Condition::Always` with the given name. + /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up + /// Source/Target for DragDrop. pub fn new(name: &'a ImStr) -> Self { Self { name, @@ -62,6 +65,24 @@ impl<'a> DragDropSource<'a> { } } + /// Creates a new [DragDropSource] with no flags and the `Condition::Always` with the given name. + /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up + /// Source/Target for DragDrop. + /// + /// This payload will be passed to ImGui, which will provide it to + /// a target when it runs [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload). + /// + /// ## Safety + /// This function is not inherently unsafe, and won't panic itself, but using it opts you into + /// managing the lifetime yourself. When you dereference the pointer given in [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload), + /// you can easily create memory safety problems. + pub unsafe fn payload(name: &'a ImStr, payload: *const T) -> Self { + let mut output = Self::new(name); + output.payload = payload as *const ffi::c_void; + output.size = std::mem::size_of::(); + output + } + /// Sets the flags on the [DragDropSource]. Only the flags `SOURCE_NO_PREVIEW_TOOLTIP`, /// `SOURCE_NO_DISABLE_HOVER`, `SOURCE_NO_HOLD_TO_OPEN_OTHERS`, `SOURCE_ALLOW_NULL_ID`, /// `SOURCE_EXTERN`, `SOURCE_AUTO_EXPIRE_PAYLOAD` make semantic sense, but any other flags will @@ -77,19 +98,6 @@ impl<'a> DragDropSource<'a> { self } - /// Sets a payload. This payload will be passed to ImGui, which will provide it to - /// a target when it runs [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload). - /// - /// ## Safety - /// This function is not inherently unsafe, and won't panic itself, but using it opts you into - /// managing the lifetime yourself. When you dereference the pointer given in [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload), - /// you can easily create memory safety problems. - pub unsafe fn payload(mut self, payload: *const T) -> Self { - self.payload = payload as *const ffi::c_void; - self.size = std::mem::size_of::(); - self - } - /// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() pub fn begin<'ui>(self, _ui: &'ui Ui) -> Option> { let should_begin = unsafe { sys::igBeginDragDropSource(self.flags.bits() as i32) }; @@ -133,13 +141,20 @@ pub struct DragDropPayload { /// Data which is copied and owned by ImGui. If you have accepted the payload, you can /// take ownership of the data; otherwise, view it immutably. Interacting with `data` is /// very unsafe. + /// @fixme: this doesn't make a ton of sense. pub data: *const ffi::c_void, /// Set when [`accept_drag_drop_payload`](Self::accept_drag_drop_payload) was called /// and mouse has been hovering the target item (nb: handle overlapping drag targets). - /// @fixme: literally what does this mean + /// @fixme: literally what does this mean -- I believe this is false on the first + /// frame when source hovers over target and then is subsequently true? but I'm not sure + /// when this matters. If DragDropFlags::ACCEPT_NO_PREVIEW is set, it doesn't make a difference + /// to this flag. pub preview: bool, /// Set when AcceptDragDropPayload() was called and mouse button is released over the target item. + /// If this is set to false, then you set DragDropFlags::ACCEPT_BEFORE_DELIVERY and shouldn't + /// mess with `data` + /// @fixme: obviously this isn't an impressive implementation of ffi data mutability. pub delivery: bool, } @@ -158,6 +173,7 @@ impl<'ui> DragDropTarget<'ui> { /// Accepts, popping the drag_drop payload, if it exists. If `DragDropFlags::ACCEPT_BEFORE_DELIVERY` is /// set, this function will return `Some` even if the type is wrong as long as there is a payload to accept. + /// How do we possibly handle communicating that this data is somewhat immutable? pub fn accept_drag_drop_payload(&self, name: &ImStr, flags: DragDropFlags) -> Option { unsafe { let inner = sys::igAcceptDragDropPayload(name.as_ptr(), flags.bits() as i32); @@ -166,6 +182,9 @@ impl<'ui> DragDropTarget<'ui> { } else { let inner = *inner; + // @fixme: there are actually other fields on `inner` which I have shorn -- they're + // considered internal to imgui (such as id of who sent this), so i've left it for + // now this way. Some(DragDropPayload { data: inner.Data, preview: inner.Preview, From 07e0580d2d9fc097548fa05868726347d694a9ff Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Sun, 24 Jan 2021 15:16:17 -0800 Subject: [PATCH 3/9] fixed formatting issues --- imgui/src/drag_drop.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/imgui/src/drag_drop.rs b/imgui/src/drag_drop.rs index d896fec..5d1b3da 100644 --- a/imgui/src/drag_drop.rs +++ b/imgui/src/drag_drop.rs @@ -104,7 +104,12 @@ impl<'a> DragDropSource<'a> { if should_begin { unsafe { - sys::igSetDragDropPayload(self.name.as_ptr(), self.payload, self.size, self.cond as i32); + sys::igSetDragDropPayload( + self.name.as_ptr(), + self.payload, + self.size, + self.cond as i32, + ); Some(DragDropSourceToolTip::push()) } @@ -174,7 +179,11 @@ impl<'ui> DragDropTarget<'ui> { /// Accepts, popping the drag_drop payload, if it exists. If `DragDropFlags::ACCEPT_BEFORE_DELIVERY` is /// set, this function will return `Some` even if the type is wrong as long as there is a payload to accept. /// How do we possibly handle communicating that this data is somewhat immutable? - pub fn accept_drag_drop_payload(&self, name: &ImStr, flags: DragDropFlags) -> Option { + pub fn accept_drag_drop_payload( + &self, + name: &ImStr, + flags: DragDropFlags, + ) -> Option { unsafe { let inner = sys::igAcceptDragDropPayload(name.as_ptr(), flags.bits() as i32); if inner.is_null() { From a8975b95c07e5def6ee5edf326862175cd16cfb4 Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Mon, 25 Jan 2021 14:28:05 -0800 Subject: [PATCH 4/9] Updated for pod data --- imgui/Cargo.toml | 1 + imgui/src/drag_drop.rs | 216 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 194 insertions(+), 23 deletions(-) diff --git a/imgui/Cargo.toml b/imgui/Cargo.toml index 4d3370e..1c1e743 100644 --- a/imgui/Cargo.toml +++ b/imgui/Cargo.toml @@ -16,6 +16,7 @@ exclude = ["/resources"] bitflags = "1" imgui-sys = { version = "0.6.0", path = "../imgui-sys" } parking_lot = "0.11" +bytemuck = "1.5" [features] wasm = ["imgui-sys/wasm"] diff --git a/imgui/src/drag_drop.rs b/imgui/src/drag_drop.rs index 5d1b3da..0334fd2 100644 --- a/imgui/src/drag_drop.rs +++ b/imgui/src/drag_drop.rs @@ -42,12 +42,53 @@ bitflags!( } ); +/// A drag-drop source without any payload. Typically, when dragging and dropping data in Dear ImGui, +/// a user will attach a payload to that drag and drop, so the accepter of the drop can read or +/// otherwise react to the drop. This struct attaches no data to the drag-drop, which means that an +/// accepter will simply be notified when the payload of a given type has been dropped. +/// +/// This is still probably the most useful way in imgui-rs to handle payloads. +/// Using `once_cell` or some shared data, this pattern can be very powerful: +/// +/// ```no_run +/// fn show_ui(ui: &Ui<'_>, drop_message: &mut Option) { +/// ui.button(im_str!("Drag me!")); +/// +/// let drag_drop_name = im_str!("Test Drag"); +/// +/// // drag drop SOURCE +/// if DragDropSource::new(drag_drop_name).begin(ui).is_some() { +/// // warning -- this would allocate every frame if `DragDropSource` has +/// // condition `Always`, which it does by default. We're okay with that for +/// // this example, but real code probably wouldn't want to allocate so much. +/// *drop_message = Some("Test Payload".to_string()); +/// } +/// +/// ui.button(im_str!("Target me!")); +/// +/// // drag drop TARGET +/// if let Some(target) = imgui::DragDropTarget::new(ui) { +/// if target +/// .accept_drag_drop_payload(drag_drop_name, DragDropFlags::empty()) +/// .is_some() +/// { +/// let msg = drop_message.take().unwrap(); +/// assert_eq!(msg, "Test Payload"); +/// } +/// +/// target.pop(); +/// } +/// } +/// ``` +/// +/// In the above, you'll see how the payload is really just a message passing service. +/// This method will give a user an easier time than using [DragDropSourcePayloadPod] or +/// the unsafe [DragDropSourcePayloadUnsafe], as the data for a user can be kept entirely in +/// Rust and never has to do a roundtrip into C++, which can cause unexpected issues. #[derive(Debug)] pub struct DragDropSource<'a> { name: &'a ImStr, flags: DragDropFlags, - payload: *const ffi::c_void, - size: usize, cond: Condition, } @@ -59,29 +100,27 @@ impl<'a> DragDropSource<'a> { Self { name, flags: DragDropFlags::empty(), - payload: ptr::null(), - size: 0, cond: Condition::Always, } } - /// Creates a new [DragDropSource] with no flags and the `Condition::Always` with the given name. - /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up - /// Source/Target for DragDrop. - /// - /// This payload will be passed to ImGui, which will provide it to - /// a target when it runs [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload). - /// - /// ## Safety - /// This function is not inherently unsafe, and won't panic itself, but using it opts you into - /// managing the lifetime yourself. When you dereference the pointer given in [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload), - /// you can easily create memory safety problems. - pub unsafe fn payload(name: &'a ImStr, payload: *const T) -> Self { - let mut output = Self::new(name); - output.payload = payload as *const ffi::c_void; - output.size = std::mem::size_of::(); - output - } + // /// Creates a new [DragDropSource] with no flags and the `Condition::Always` with the given name. + // /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up + // /// Source/Target for DragDrop. + // /// + // /// This payload will be passed to ImGui, which will provide it to + // /// a target when it runs [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload). + // /// + // /// ## Safety + // /// This function is not inherently unsafe, and won't panic itself, but using it opts you into + // /// managing the lifetime yourself. When you dereference the pointer given in [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload), + // /// you can easily create memory safety problems. + // pub unsafe fn payload(name: &'a ImStr, payload: *const T) -> Self { + // let mut output = Self::new(name); + // output.payload = payload as *const ffi::c_void; + // output.size = std::mem::size_of::(); + // output + // } /// Sets the flags on the [DragDropSource]. Only the flags `SOURCE_NO_PREVIEW_TOOLTIP`, /// `SOURCE_NO_DISABLE_HOVER`, `SOURCE_NO_HOLD_TO_OPEN_OTHERS`, `SOURCE_ALLOW_NULL_ID`, @@ -98,6 +137,72 @@ impl<'a> DragDropSource<'a> { self } + /// Creates the source of a drag and returns a handle on the tooltip. + /// This handle can be immediately dropped without binding it, in which case a default empty + /// circle will be used for the "blank" tooltip as this item is being dragged around. + /// + /// Otherwise, use this tooltip to add data which will display as this item is dragged. + /// If `SOURCE_NO_PREVIEW_TOOLTIP` is enabled, however, no preview will be displayed + /// and this returned token does nothing. Additionally, a given target may use the flag + /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown. + /// + /// For more information on how to use payload-less drag/drops, please see [DragDropSource]'s + /// documentation. + pub fn begin<'ui>(self, _ui: &'ui Ui) -> Option> { + let should_begin = unsafe { sys::igBeginDragDropSource(self.flags.bits() as i32) }; + + if should_begin { + unsafe { + sys::igSetDragDropPayload(self.name.as_ptr(), ptr::null(), 0, self.cond as i32); + + Some(DragDropSourceToolTip::push()) + } + } else { + None + } + } +} + +#[derive(Debug)] +pub struct DragDropSourcePayloadPod<'a, T> { + name: &'a ImStr, + payload: &'a T, + flags: DragDropFlags, + cond: Condition, +} + +impl<'a, T: bytemuck::Pod> DragDropSourcePayloadPod<'a, T> { + /// Creates a new [DragDropSourcePayloadPod] with no flags and the `Condition::Always` with the given name. + /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up + /// Source/Target for DragDrop. + pub fn new(name: &'a ImStr, payload: &'a T) -> Self { + Self { + name, + flags: DragDropFlags::empty(), + payload, + cond: Condition::Always, + } + } + + /// Sets the flags on the [DragDropSourcePayloadPod]. Only the flags `SOURCE_NO_PREVIEW_TOOLTIP`, + /// `SOURCE_NO_DISABLE_HOVER`, `SOURCE_NO_HOLD_TO_OPEN_OTHERS`, `SOURCE_ALLOW_NULL_ID`, + /// `SOURCE_EXTERN`, `SOURCE_AUTO_EXPIRE_PAYLOAD` make semantic sense, but any other flags will + /// be accepted without panic. + /// + /// Defaults to empty. + pub fn flags(mut self, flags: DragDropFlags) -> Self { + self.flags = flags; + self + } + + /// Sets the condition on the [DragDropSourcePayloadPod]. + /// + /// Defaults to [Always](Condition::Always). + pub fn condition(mut self, cond: Condition) -> Self { + self.cond = cond; + self + } + /// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() pub fn begin<'ui>(self, _ui: &'ui Ui) -> Option> { let should_begin = unsafe { sys::igBeginDragDropSource(self.flags.bits() as i32) }; @@ -106,8 +211,8 @@ impl<'a> DragDropSource<'a> { unsafe { sys::igSetDragDropPayload( self.name.as_ptr(), - self.payload, - self.size, + self.payload as *const _ as *const ffi::c_void, + std::mem::size_of::(), self.cond as i32, ); @@ -163,6 +268,27 @@ pub struct DragDropPayload { pub delivery: bool, } +#[derive(Debug)] +pub struct DragDropEmptyPayload { + /// @fixme add docs. + pub preview: bool, + + // @fixme add docs + pub delivery: bool, +} + +#[derive(Debug)] +pub struct DragDropPodPayload { + /// The kind data which was requested. + pub data: T, + + /// @fixme add docs. + pub preview: bool, + + // @fixme add docs + pub delivery: bool, +} + #[derive(Debug)] pub struct DragDropTarget<'ui>(PhantomData>); @@ -176,6 +302,50 @@ impl<'ui> DragDropTarget<'ui> { } } + pub fn accept_empty_payload( + &self, + name: &ImStr, + flags: DragDropFlags, + ) -> Option { + unsafe { + let inner = sys::igAcceptDragDropPayload(name.as_ptr(), flags.bits() as i32); + if inner.is_null() { + None + } else { + let inner = *inner; + + Some(DragDropEmptyPayload { + preview: inner.Preview, + delivery: inner.Delivery, + }) + } + } + } + + pub fn accept_pod_payload( + &self, + name: &ImStr, + flags: DragDropFlags, + ) -> Option, bytemuck::PodCastError>> { + unsafe { + let inner = sys::igAcceptDragDropPayload(name.as_ptr(), flags.bits() as i32); + if inner.is_null() { + None + } else { + let inner = *inner; + let data = std::slice::from_raw_parts(inner.Data as *const u8, 1); + + Some( + bytemuck::try_from_bytes(data).map(|data| DragDropPodPayload { + data: *data, + preview: inner.Preview, + delivery: inner.Delivery, + }), + ) + } + } + } + /// Accepts, popping the drag_drop payload, if it exists. If `DragDropFlags::ACCEPT_BEFORE_DELIVERY` is /// set, this function will return `Some` even if the type is wrong as long as there is a payload to accept. /// How do we possibly handle communicating that this data is somewhat immutable? From 2e6956a3ce25b2561e2ee70bb47464ac0d0ec721 Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Mon, 25 Jan 2021 14:38:08 -0800 Subject: [PATCH 5/9] correct bytesize from imgui --- imgui/src/drag_drop.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imgui/src/drag_drop.rs b/imgui/src/drag_drop.rs index 0334fd2..78b3590 100644 --- a/imgui/src/drag_drop.rs +++ b/imgui/src/drag_drop.rs @@ -333,7 +333,8 @@ impl<'ui> DragDropTarget<'ui> { None } else { let inner = *inner; - let data = std::slice::from_raw_parts(inner.Data as *const u8, 1); + let data = + std::slice::from_raw_parts(inner.Data as *const u8, inner.DataSize as usize); Some( bytemuck::try_from_bytes(data).map(|data| DragDropPodPayload { From 72007c06b67d750a05bc43003144ca4bb28d5b14 Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Tue, 26 Jan 2021 00:31:51 -0800 Subject: [PATCH 6/9] final version --- imgui/src/drag_drop.rs | 478 +++++++++++++++++++++++------------------ imgui/src/lib.rs | 2 +- 2 files changed, 266 insertions(+), 214 deletions(-) diff --git a/imgui/src/drag_drop.rs b/imgui/src/drag_drop.rs index 78b3590..e084849 100644 --- a/imgui/src/drag_drop.rs +++ b/imgui/src/drag_drop.rs @@ -42,49 +42,27 @@ bitflags!( } ); -/// A drag-drop source without any payload. Typically, when dragging and dropping data in Dear ImGui, -/// a user will attach a payload to that drag and drop, so the accepter of the drop can read or -/// otherwise react to the drop. This struct attaches no data to the drag-drop, which means that an -/// accepter will simply be notified when the payload of a given type has been dropped. -/// -/// This is still probably the most useful way in imgui-rs to handle payloads. -/// Using `once_cell` or some shared data, this pattern can be very powerful: +/// Creates a source for drag drop data out of the last ID created. /// /// ```no_run -/// fn show_ui(ui: &Ui<'_>, drop_message: &mut Option) { -/// ui.button(im_str!("Drag me!")); -/// -/// let drag_drop_name = im_str!("Test Drag"); +/// # use imgui::*; +/// fn show_ui(ui: &Ui<'_>) { +/// ui.button(im_str!("Hello, I am a drag source!"), [0.0, 0.0]); /// -/// // drag drop SOURCE -/// if DragDropSource::new(drag_drop_name).begin(ui).is_some() { -/// // warning -- this would allocate every frame if `DragDropSource` has -/// // condition `Always`, which it does by default. We're okay with that for -/// // this example, but real code probably wouldn't want to allocate so much. -/// *drop_message = Some("Test Payload".to_string()); -/// } -/// -/// ui.button(im_str!("Target me!")); -/// -/// // drag drop TARGET -/// if let Some(target) = imgui::DragDropTarget::new(ui) { -/// if target -/// .accept_drag_drop_payload(drag_drop_name, DragDropFlags::empty()) -/// .is_some() -/// { -/// let msg = drop_message.take().unwrap(); -/// assert_eq!(msg, "Test Payload"); -/// } -/// -/// target.pop(); -/// } +/// // Creates an empty DragSource with no tooltip +/// DragDropSource::new(im_str!("BUTTON_DRAG")).begin(ui); /// } /// ``` /// -/// In the above, you'll see how the payload is really just a message passing service. -/// This method will give a user an easier time than using [DragDropSourcePayloadPod] or -/// the unsafe [DragDropSourcePayloadUnsafe], as the data for a user can be kept entirely in -/// Rust and never has to do a roundtrip into C++, which can cause unexpected issues. +/// Notice especially the `"BUTTON_DRAG"` name -- this is the identifier of this +/// DragDropSource; [DragDropTarget]'s will specify an identifier to *receive*, and these +/// names must match up. A single item should only have one [DragDropSource], though +/// a target may have multiple different targets. +/// +/// DropDropSources don't do anything until you use one of the three `begin_` methods +/// on this struct. Each of these methods describes how you handle the Payload which ImGui +/// will manage, and then give to a [DragDropTarget], which will received the payload. The +/// simplest and safest Payload is the empty payload, created with [begin](Self::begin). #[derive(Debug)] pub struct DragDropSource<'a> { name: &'a ImStr, @@ -104,24 +82,6 @@ impl<'a> DragDropSource<'a> { } } - // /// Creates a new [DragDropSource] with no flags and the `Condition::Always` with the given name. - // /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up - // /// Source/Target for DragDrop. - // /// - // /// This payload will be passed to ImGui, which will provide it to - // /// a target when it runs [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload). - // /// - // /// ## Safety - // /// This function is not inherently unsafe, and won't panic itself, but using it opts you into - // /// managing the lifetime yourself. When you dereference the pointer given in [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload), - // /// you can easily create memory safety problems. - // pub unsafe fn payload(name: &'a ImStr, payload: *const T) -> Self { - // let mut output = Self::new(name); - // output.payload = payload as *const ffi::c_void; - // output.size = std::mem::size_of::(); - // output - // } - /// Sets the flags on the [DragDropSource]. Only the flags `SOURCE_NO_PREVIEW_TOOLTIP`, /// `SOURCE_NO_DISABLE_HOVER`, `SOURCE_NO_HOLD_TO_OPEN_OTHERS`, `SOURCE_ALLOW_NULL_ID`, /// `SOURCE_EXTERN`, `SOURCE_AUTO_EXPIRE_PAYLOAD` make semantic sense, but any other flags will @@ -146,78 +106,112 @@ impl<'a> DragDropSource<'a> { /// and this returned token does nothing. Additionally, a given target may use the flag /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown. /// - /// For more information on how to use payload-less drag/drops, please see [DragDropSource]'s - /// documentation. - pub fn begin<'ui>(self, _ui: &'ui Ui) -> Option> { - let should_begin = unsafe { sys::igBeginDragDropSource(self.flags.bits() as i32) }; - - if should_begin { - unsafe { - sys::igSetDragDropPayload(self.name.as_ptr(), ptr::null(), 0, self.cond as i32); - - Some(DragDropSourceToolTip::push()) - } - } else { - None - } + /// This drag has no payload, but is still probably the most useful way in imgui-rs to handle payloads. + /// Using `once_cell` or some shared data, this pattern can be very powerful: + /// + /// ```no_run + /// # use imgui::*; + /// fn show_ui(ui: &Ui<'_>, drop_message: &mut Option) { + /// ui.button(im_str!("Drag me!"), [0.0, 0.0]); + /// + /// let drag_drop_name = im_str!("Test Drag"); + /// + /// // drag drop SOURCE + /// if DragDropSource::new(drag_drop_name).begin(ui).is_some() { + /// // warning -- this would allocate every frame if `DragDropSource` has + /// // condition `Always`, which it does by default. We're okay with that for + /// // this example, but real code probably wouldn't want to allocate so much. + /// *drop_message = Some("Test Payload".to_string()); + /// } + /// + /// ui.button(im_str!("Target me!"), [0.0, 0.0]); + /// + /// // drag drop TARGET + /// if let Some(target) = imgui::DragDropTarget::new(ui) { + /// if target + /// .accept_payload_empty(drag_drop_name, DragDropFlags::empty()) + /// .is_some() + /// { + /// let msg = drop_message.take().unwrap(); + /// assert_eq!(msg, "Test Payload"); + /// } + /// + /// target.pop(); + /// } + /// } + /// ``` + /// + /// In the above, you'll see how the payload is really just a message passing service. + /// If you want to pass a simple integer or other "plain old data", take a look at + /// [begin_payload_pod](Self::begin_payload_pod). + pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { + unsafe { self.begin_payload_unchecked(ui, ptr::null(), 0) } } -} -#[derive(Debug)] -pub struct DragDropSourcePayloadPod<'a, T> { - name: &'a ImStr, - payload: &'a T, - flags: DragDropFlags, - cond: Condition, -} - -impl<'a, T: bytemuck::Pod> DragDropSourcePayloadPod<'a, T> { - /// Creates a new [DragDropSourcePayloadPod] with no flags and the `Condition::Always` with the given name. - /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up - /// Source/Target for DragDrop. - pub fn new(name: &'a ImStr, payload: &'a T) -> Self { - Self { - name, - flags: DragDropFlags::empty(), - payload, - cond: Condition::Always, + /// Creates the source of a drag and returns a handle on the tooltip. + /// This handle can be immediately dropped without binding it, in which case a default empty + /// circle will be used for the "blank" tooltip as this item is being dragged around. + /// + /// Otherwise, use this tooltip to add data which will display as this item is dragged. + /// If `SOURCE_NO_PREVIEW_TOOLTIP` is enabled, however, no preview will be displayed + /// and this returned token does nothing. Additionally, a given target may use the flag + /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown. + /// + /// This function also takes a payload in the form of `T: bytemuck::Pod`. We use this bound to + /// ensure that we can safely send and receive the given type from C++. Integers are natively + /// supported by this operation already, but you'll need to implement `bytemuck::Pod` for your own + /// types to use this method. + pub fn begin_payload_pod<'ui, T: bytemuck::Pod>( + self, + ui: &Ui<'ui>, + payload: &T, + ) -> Option> { + unsafe { + self.begin_payload_unchecked( + ui, + payload as *const _ as *const ffi::c_void, + std::mem::size_of::(), + ) } } - /// Sets the flags on the [DragDropSourcePayloadPod]. Only the flags `SOURCE_NO_PREVIEW_TOOLTIP`, - /// `SOURCE_NO_DISABLE_HOVER`, `SOURCE_NO_HOLD_TO_OPEN_OTHERS`, `SOURCE_ALLOW_NULL_ID`, - /// `SOURCE_EXTERN`, `SOURCE_AUTO_EXPIRE_PAYLOAD` make semantic sense, but any other flags will - /// be accepted without panic. + /// Creates the source of a drag and returns a handle on the tooltip. + /// This handle can be immediately dropped without binding it, in which case a default empty + /// circle will be used for the "blank" tooltip as this item is being dragged around. /// - /// Defaults to empty. - pub fn flags(mut self, flags: DragDropFlags) -> Self { - self.flags = flags; - self - } - - /// Sets the condition on the [DragDropSourcePayloadPod]. + /// Otherwise, use this tooltip to add data which will display as this item is dragged. + /// If `SOURCE_NO_PREVIEW_TOOLTIP` is enabled, however, no preview will be displayed + /// and this returned token does nothing. Additionally, a given target may use the flag + /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown. /// - /// Defaults to [Always](Condition::Always). - pub fn condition(mut self, cond: Condition) -> Self { - self.cond = cond; - self - } - - /// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() - pub fn begin<'ui>(self, _ui: &'ui Ui) -> Option> { - let should_begin = unsafe { sys::igBeginDragDropSource(self.flags.bits() as i32) }; + /// This function also takes a payload of any `*const T`. Please avoid directly using it + /// if you can. + /// + /// ## Safety + /// This function itself will not cause a panic, but using it directly opts you into + /// managing the lifetime of the pointer provided yourself. Dear ImGui will execute a memcpy on + /// the data passed in with the size (in bytes) given, but this is, of course, just a copy, + /// so if you pass in an `&String`, for example, the underlying String data will not be cloned, + /// and could easily dangle if the `String` is dropped. + /// + /// Moreover, if `Condition::Always` is set (as it is by default), you will be copying in your data + /// every time this function is ran in your update loop, which if it involves an allocating and then + /// handing the allocation to ImGui, would result in a significant amount of data created. + /// + /// Overall, users should be very sure that this function is needed before they reach for it, and instead + /// should consider either [begin_payload](Self::begin_payload) or [begin_payload_pod](Self::begin_payload_pod). + pub unsafe fn begin_payload_unchecked<'ui>( + &self, + _ui: &Ui<'ui>, + ptr: *const ffi::c_void, + size: usize, + ) -> Option> { + let should_begin = sys::igBeginDragDropSource(self.flags.bits() as i32); if should_begin { - unsafe { - sys::igSetDragDropPayload( - self.name.as_ptr(), - self.payload as *const _ as *const ffi::c_void, - std::mem::size_of::(), - self.cond as i32, - ); + sys::igSetDragDropPayload(self.name.as_ptr(), ptr, size, self.cond as i32); - Some(DragDropSourceToolTip::push()) - } + Some(DragDropSourceToolTip::push()) } else { None } @@ -246,53 +240,45 @@ impl Drop for DragDropSourceToolTip<'_> { } } -#[derive(Debug)] -pub struct DragDropPayload { - /// Data which is copied and owned by ImGui. If you have accepted the payload, you can - /// take ownership of the data; otherwise, view it immutably. Interacting with `data` is - /// very unsafe. - /// @fixme: this doesn't make a ton of sense. - pub data: *const ffi::c_void, - /// Set when [`accept_drag_drop_payload`](Self::accept_drag_drop_payload) was called - /// and mouse has been hovering the target item (nb: handle overlapping drag targets). - /// @fixme: literally what does this mean -- I believe this is false on the first - /// frame when source hovers over target and then is subsequently true? but I'm not sure - /// when this matters. If DragDropFlags::ACCEPT_NO_PREVIEW is set, it doesn't make a difference - /// to this flag. - pub preview: bool, - - /// Set when AcceptDragDropPayload() was called and mouse button is released over the target item. - /// If this is set to false, then you set DragDropFlags::ACCEPT_BEFORE_DELIVERY and shouldn't - /// mess with `data` - /// @fixme: obviously this isn't an impressive implementation of ffi data mutability. - pub delivery: bool, -} - -#[derive(Debug)] -pub struct DragDropEmptyPayload { - /// @fixme add docs. - pub preview: bool, - - // @fixme add docs - pub delivery: bool, -} - -#[derive(Debug)] -pub struct DragDropPodPayload { - /// The kind data which was requested. - pub data: T, - - /// @fixme add docs. - pub preview: bool, - - // @fixme add docs - pub delivery: bool, -} - +/// Creates a target for drag drop data out of the last ID created. +/// +/// ```no_run +/// # use imgui::*; +/// fn show_ui(ui: &Ui<'_>) { +/// // Drop something on this button please! +/// ui.button(im_str!("Hello, I am a drag Target!"), [0.0, 0.0]); +/// +/// if let Some(target) = DragDropTarget::new(ui) { +/// // accepting an empty payload (which is really just raising an event) +/// if let Some(_payload_data) = target.accept_payload_empty(im_str!("BUTTON_DRAG"), DragDropFlags::empty()) { +/// println!("Nice job getting on the payload!"); +/// } +/// +/// // and we can accept multiple, different types of payloads with one drop target. +/// // this is a good pattern for handling different kinds of drag/drop situations with +/// // different kinds of data in them. +/// if let Some(Ok(payload_data)) = target.accept_payload_pod::(im_str!("BUTTON_ID"), DragDropFlags::empty()) { +/// println!("Our payload's data was {}", payload_data.data); +/// } +/// } +/// } +/// ``` +/// +/// Notice especially the `"BUTTON_DRAG"` and `"BUTTON_ID"` name -- this is the identifier of this +/// DragDropTarget; [DragDropSource]s will specify an identifier when they send a payload, and these +/// names must match up. Notice how a target can have multiple acceptances on them -- this is a good +/// pattern to handle multiple kinds of data which could be passed around. +/// +/// DropDropTargets don't do anything until you use one of the three `accept_` methods +/// on this struct. Each of these methods will spit out a _Payload struct with an increasing +/// amount of information on the Payload. The absolute safest solution is [accept_payload_empty](Self::accept_payload_empty). #[derive(Debug)] pub struct DragDropTarget<'ui>(PhantomData>); impl<'ui> DragDropTarget<'ui> { + /// Creates a new DragDropTarget, holding the [Ui]'s lifetime for the duration + /// of its existence. This is required since this struct runs some code on its Drop + /// to end the DragDropTarget code. pub fn new(_ui: &Ui<'_>) -> Option { let should_begin = unsafe { sys::igBeginDragDropTarget() }; if should_begin { @@ -302,80 +288,97 @@ impl<'ui> DragDropTarget<'ui> { } } - pub fn accept_empty_payload( + /// Accepts an empty payload. This is the safest option for raising named events + /// in the DragDrop API. See [DragDropSource::begin] for more information on how you + /// might use this pattern. + pub fn accept_payload_empty( &self, name: &ImStr, flags: DragDropFlags, - ) -> Option { - unsafe { - let inner = sys::igAcceptDragDropPayload(name.as_ptr(), flags.bits() as i32); - if inner.is_null() { - None - } else { - let inner = *inner; + ) -> Option { + let output = unsafe { self.accept_payload_unchecked(name, flags) }; - Some(DragDropEmptyPayload { - preview: inner.Preview, - delivery: inner.Delivery, - }) - } - } + output.map(|unsafe_pod| DragDropPayloadEmpty { + preview: unsafe_pod.preview, + delivery: unsafe_pod.delivery, + }) } - pub fn accept_pod_payload( + /// Accepts an payload with POD in it. This returns a Result, since you can specify any + /// type, which we will try to cast the data in, and give you a failure enum if it could + /// not be cast to it. Your data must implement `bytemuck::Pod` to use this method. + pub fn accept_payload_pod( &self, name: &ImStr, flags: DragDropFlags, - ) -> Option, bytemuck::PodCastError>> { - unsafe { - let inner = sys::igAcceptDragDropPayload(name.as_ptr(), flags.bits() as i32); - if inner.is_null() { - None - } else { - let inner = *inner; - let data = - std::slice::from_raw_parts(inner.Data as *const u8, inner.DataSize as usize); + ) -> Option, bytemuck::PodCastError>> { + let output = unsafe { self.accept_payload_unchecked(name, flags) }; - Some( - bytemuck::try_from_bytes(data).map(|data| DragDropPodPayload { - data: *data, - preview: inner.Preview, - delivery: inner.Delivery, - }), + // convert the unsafe payload to our Result + output.map(|unsafe_payload| { + let data = unsafe { + std::slice::from_raw_parts( + unsafe_payload.data as *const u8, + unsafe_payload.size as usize, ) - } - } + }; + + // if we succeed, convert to PayloadPod + bytemuck::try_from_bytes(data).map(|data| DragDropPayloadPod { + data: *data, + preview: unsafe_payload.preview, + delivery: unsafe_payload.delivery, + }) + }) } - /// Accepts, popping the drag_drop payload, if it exists. If `DragDropFlags::ACCEPT_BEFORE_DELIVERY` is - /// set, this function will return `Some` even if the type is wrong as long as there is a payload to accept. - /// How do we possibly handle communicating that this data is somewhat immutable? - pub fn accept_drag_drop_payload( + /// Accepts a drag and drop payload, and returns a [DragDropPayload] which + /// contains a raw pointer to [c_void](std::ffi::c_void) and a size in bytes. + /// Users should generally avoid using this function if one of the safer variants + /// is acceptable. + /// + /// ## Safety + /// + /// Because this pointer comes from ImGui, absolutely no promises can be made on its + /// contents, alignment, or lifetime. Interacting with it is therefore extremely unsafe. + /// **Important:** a special note needs to be made to the [ACCEPT_BEFORE_DELIVERY] flag -- + /// passing this flag will make this function return `Some(DragDropPayload)` **even before + /// the user has actually "dropped" the payload by release their mouse button.** + /// + /// In safe functions, this works just fine, since the data can be freely copied + /// (or doesn't exist at all!). However, if you are working with your own data, you must + /// be extremely careful with this data, as you may, effectively, only have immutable access to it. + /// + /// Moreover, if the `DragDropSource` has also used `Condition::Once` or similar when they sent the data, + /// ImGui will assume its data is still valid even after your preview, so corrupting that data could + /// lead to all sorts of unsafe behvaior on ImGui's side. In summary, using this function for any data + /// which isn't truly `Copy` or "plain old data" is difficult, and requires substantial knowledge + /// of the various edge cases. + pub unsafe fn accept_payload_unchecked( &self, name: &ImStr, flags: DragDropFlags, ) -> Option { - unsafe { - let inner = sys::igAcceptDragDropPayload(name.as_ptr(), flags.bits() as i32); - if inner.is_null() { - None - } else { - let inner = *inner; + let inner = sys::igAcceptDragDropPayload(name.as_ptr(), flags.bits() as i32); + if inner.is_null() { + None + } else { + let inner = *inner; - // @fixme: there are actually other fields on `inner` which I have shorn -- they're - // considered internal to imgui (such as id of who sent this), so i've left it for - // now this way. - Some(DragDropPayload { - data: inner.Data, - preview: inner.Preview, - delivery: inner.Delivery, - }) - } + // @fixme: there are actually other fields on `inner` which I have shorn -- they're + // considered internal to imgui (such as id of who sent this), so i've left it for + // now this way. + Some(DragDropPayload { + data: inner.Data, + size: inner.DataSize, + preview: inner.Preview, + delivery: inner.Delivery, + }) } } - /// Ends the current target. Ironically, this doesn't really do - /// anything in ImGui or in imgui-rs, but it might in the future. + /// Ends the current target. Ironically, this doesn't really do anything in ImGui + /// or in imgui-rs, but it might in the future. pub fn pop(self) { // omitted...exists just to run Drop. } @@ -386,3 +389,52 @@ impl Drop for DragDropTarget<'_> { unsafe { sys::igEndDragDropTarget() } } } + +/// An empty DragDropPayload. It has no data in it, and just includes +/// two bools with status information. +#[derive(Debug)] +pub struct DragDropPayloadEmpty { + /// Set when [`accept_payload_empty`](Self::accept_payload_empty) was called + /// and mouse has been hovering the target item. + pub preview: bool, + + /// Set when [`accept_payload_empty`](Self::accept_payload_empty) was + /// called and mouse button is released over the target item. + pub delivery: bool, +} + +/// A DragDropPayload with status information and some POD, or plain old data, +/// in it. +#[derive(Debug)] +pub struct DragDropPayloadPod { + /// The kind data which was requested. + pub data: T, + + /// Set when [`accept_payload_pod`](Self::accept_payload_pod) was called + /// and mouse has been hovering the target item. + pub preview: bool, + + /// Set when [`accept_payload_pod`](Self::accept_payload_pod) was + /// called and mouse button is released over the target item. + pub delivery: bool, +} + +#[derive(Debug)] +pub struct DragDropPayload { + /// Data which is copied and owned by ImGui. If you have accepted the payload, you can + /// take ownership of the data; otherwise, view it immutably. Interacting with `data` is + /// very unsafe. + pub data: *const ffi::c_void, + + /// The size of the data in bytes. + pub size: i32, + + /// Set when [`accept_payload_unchecked`](Self::accept_payload_unchecked) was called + /// and mouse has been hovering the target item. + pub preview: bool, + + /// Set when [`accept_payload_unchecked`](Self::accept_payload_unchecked) was + /// called and mouse button is released over the target item. If this is set to false, then you + /// set DragDropFlags::ACCEPT_BEFORE_DELIVERY and shouldn't mutate `data`. + pub delivery: bool, +} diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 31f4c19..7702dea 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -10,7 +10,7 @@ use std::thread; pub use self::clipboard::*; pub use self::context::*; -pub use self::drag_drop::*; +pub use self::drag_drop::{DragDropFlags, DragDropSource, DragDropTarget}; pub use self::draw_list::{ChannelsSplit, DrawListMut, ImColor}; pub use self::fonts::atlas::*; pub use self::fonts::font::*; From a027c89125667eaef5752520a0740143cc6dc416 Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Tue, 26 Jan 2021 13:53:09 -0800 Subject: [PATCH 7/9] removed bytemuck, made this nice. --- imgui/Cargo.toml | 1 - imgui/src/drag_drop.rs | 123 ++++++++++++++++++++++++++--------------- 2 files changed, 79 insertions(+), 45 deletions(-) diff --git a/imgui/Cargo.toml b/imgui/Cargo.toml index 1c1e743..4d3370e 100644 --- a/imgui/Cargo.toml +++ b/imgui/Cargo.toml @@ -16,7 +16,6 @@ exclude = ["/resources"] bitflags = "1" imgui-sys = { version = "0.6.0", path = "../imgui-sys" } parking_lot = "0.11" -bytemuck = "1.5" [features] wasm = ["imgui-sys/wasm"] diff --git a/imgui/src/drag_drop.rs b/imgui/src/drag_drop.rs index e084849..0d507df 100644 --- a/imgui/src/drag_drop.rs +++ b/imgui/src/drag_drop.rs @@ -1,4 +1,4 @@ -use std::{ffi, marker::PhantomData, ptr}; +use std::{any, ffi, marker::PhantomData}; use crate::{sys, Condition, ImStr, Ui}; use bitflags::bitflags; @@ -143,9 +143,9 @@ impl<'a> DragDropSource<'a> { /// /// In the above, you'll see how the payload is really just a message passing service. /// If you want to pass a simple integer or other "plain old data", take a look at - /// [begin_payload_pod](Self::begin_payload_pod). + /// [begin_payload](Self::begin_payload). pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { - unsafe { self.begin_payload_unchecked(ui, ptr::null(), 0) } + self.begin_payload(ui, ()) } /// Creates the source of a drag and returns a handle on the tooltip. @@ -157,20 +157,20 @@ impl<'a> DragDropSource<'a> { /// and this returned token does nothing. Additionally, a given target may use the flag /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown. /// - /// This function also takes a payload in the form of `T: bytemuck::Pod`. We use this bound to - /// ensure that we can safely send and receive the given type from C++. Integers are natively - /// supported by this operation already, but you'll need to implement `bytemuck::Pod` for your own - /// types to use this method. - pub fn begin_payload_pod<'ui, T: bytemuck::Pod>( + /// This function also takes a payload in the form of `T: Copy + 'static`. We use this bound to + /// ensure that we can safely send and receive the given type from C++ without worrying about + /// handling drops. + pub fn begin_payload<'ui, T: Copy + 'static>( self, ui: &Ui<'ui>, - payload: &T, + payload: T, ) -> Option> { unsafe { + let payload = TypedPayload::new(payload); self.begin_payload_unchecked( ui, - payload as *const _ as *const ffi::c_void, - std::mem::size_of::(), + &payload as *const _ as *const ffi::c_void, + std::mem::size_of::>(), ) } } @@ -199,7 +199,7 @@ impl<'a> DragDropSource<'a> { /// handing the allocation to ImGui, would result in a significant amount of data created. /// /// Overall, users should be very sure that this function is needed before they reach for it, and instead - /// should consider either [begin_payload](Self::begin_payload) or [begin_payload_pod](Self::begin_payload_pod). + /// should consider either [begin](Self::begin) or [begin_payload](Self::begin_payload). pub unsafe fn begin_payload_unchecked<'ui>( &self, _ui: &Ui<'ui>, @@ -257,7 +257,7 @@ impl Drop for DragDropSourceToolTip<'_> { /// // and we can accept multiple, different types of payloads with one drop target. /// // this is a good pattern for handling different kinds of drag/drop situations with /// // different kinds of data in them. -/// if let Some(Ok(payload_data)) = target.accept_payload_pod::(im_str!("BUTTON_ID"), DragDropFlags::empty()) { +/// if let Some(Ok(payload_data)) = target.accept_payload::(im_str!("BUTTON_ID"), DragDropFlags::empty()) { /// println!("Our payload's data was {}", payload_data.data); /// } /// } @@ -296,52 +296,54 @@ impl<'ui> DragDropTarget<'ui> { name: &ImStr, flags: DragDropFlags, ) -> Option { - let output = unsafe { self.accept_payload_unchecked(name, flags) }; - - output.map(|unsafe_pod| DragDropPayloadEmpty { - preview: unsafe_pod.preview, - delivery: unsafe_pod.delivery, - }) + self.accept_payload::<()>(name, flags)? + .ok() + .map(|payload_pod| DragDropPayloadEmpty { + preview: payload_pod.preview, + delivery: payload_pod.delivery, + }) } - /// Accepts an payload with POD in it. This returns a Result, since you can specify any - /// type, which we will try to cast the data in, and give you a failure enum if it could - /// not be cast to it. Your data must implement `bytemuck::Pod` to use this method. - pub fn accept_payload_pod( + /// Accepts a payload with plain old data in it. This returns a Result, since you can specify any + /// type. The sent type must match the return type (via TypeId) to receive an `Ok`. + pub fn accept_payload( &self, name: &ImStr, flags: DragDropFlags, - ) -> Option, bytemuck::PodCastError>> { + ) -> Option, PayloadIsWrongType>> { let output = unsafe { self.accept_payload_unchecked(name, flags) }; // convert the unsafe payload to our Result output.map(|unsafe_payload| { - let data = unsafe { - std::slice::from_raw_parts( - unsafe_payload.data as *const u8, - unsafe_payload.size as usize, - ) - }; + // sheering off the typeid... + let received = unsafe { *(unsafe_payload.data as *const any::TypeId) }; + let requested = any::TypeId::of::(); - // if we succeed, convert to PayloadPod - bytemuck::try_from_bytes(data).map(|data| DragDropPayloadPod { - data: *data, - preview: unsafe_payload.preview, - delivery: unsafe_payload.delivery, - }) + if received == requested { + let data = unsafe { *(unsafe_payload.data as *const TypedPayload) }.data; + Ok(DragDropPayloadPod { + data, + preview: unsafe_payload.preview, + delivery: unsafe_payload.delivery, + }) + } else { + Err(PayloadIsWrongType { + requested, + received, + }) + } }) } - /// Accepts a drag and drop payload, and returns a [DragDropPayload] which - /// contains a raw pointer to [c_void](std::ffi::c_void) and a size in bytes. - /// Users should generally avoid using this function if one of the safer variants - /// is acceptable. + /// Accepts a drag and drop payload which contains a raw pointer to [c_void](std::ffi::c_void) + /// and a size in bytes. Users should generally avoid using this function + /// if one of the safer variants is acceptable. /// /// ## Safety /// /// Because this pointer comes from ImGui, absolutely no promises can be made on its /// contents, alignment, or lifetime. Interacting with it is therefore extremely unsafe. - /// **Important:** a special note needs to be made to the [ACCEPT_BEFORE_DELIVERY] flag -- + /// **Important:** a special note needs to be made to the [ACCEPT_BEFORE_DELIVERY](DragDropFlags::ACCEPT_BEFORE_DELIVERY) flag -- /// passing this flag will make this function return `Some(DragDropPayload)` **even before /// the user has actually "dropped" the payload by release their mouse button.** /// @@ -406,15 +408,15 @@ pub struct DragDropPayloadEmpty { /// A DragDropPayload with status information and some POD, or plain old data, /// in it. #[derive(Debug)] -pub struct DragDropPayloadPod { +pub struct DragDropPayloadPod { /// The kind data which was requested. pub data: T, - /// Set when [`accept_payload_pod`](Self::accept_payload_pod) was called + /// Set when [`accept_payload`](Self::accept_payload) was called /// and mouse has been hovering the target item. pub preview: bool, - /// Set when [`accept_payload_pod`](Self::accept_payload_pod) was + /// Set when [`accept_payload`](Self::accept_payload) was /// called and mouse button is released over the target item. pub delivery: bool, } @@ -438,3 +440,36 @@ pub struct DragDropPayload { /// set DragDropFlags::ACCEPT_BEFORE_DELIVERY and shouldn't mutate `data`. pub delivery: bool, } + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +struct TypedPayload { + type_id: any::TypeId, + data: T, +} + +impl TypedPayload { + /// Creates a new typed payload which contains this data. + pub fn new(data: T) -> Self { + Self { + type_id: any::TypeId::of::(), + data, + } + } +} + +/// Indicates that an incorrect payload type was received. It is opaque, +/// but you can view questionably useful debug information with Debug formatting. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] +pub struct PayloadIsWrongType { + requested: any::TypeId, + received: any::TypeId, +} + +impl std::fmt::Display for PayloadIsWrongType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Payload is wrong type") + } +} + +impl std::error::Error for PayloadIsWrongType {} From 2a1ced631a39d20d02e3b23929ad8981cecbea16 Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Wed, 3 Feb 2021 00:40:10 -0800 Subject: [PATCH 8/9] Updated --- imgui/src/drag_drop.rs | 105 ++++++++++++++++++++++++++++++++--------- imgui/src/lib.rs | 2 +- 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/imgui/src/drag_drop.rs b/imgui/src/drag_drop.rs index 0d507df..4687e29 100644 --- a/imgui/src/drag_drop.rs +++ b/imgui/src/drag_drop.rs @@ -74,7 +74,8 @@ impl<'a> DragDropSource<'a> { /// Creates a new [DragDropSource] with no flags and the `Condition::Always` with the given name. /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up /// Source/Target for DragDrop. - pub fn new(name: &'a ImStr) -> Self { + #[inline] + pub const fn new(name: &'a ImStr) -> Self { Self { name, flags: DragDropFlags::empty(), @@ -86,13 +87,15 @@ impl<'a> DragDropSource<'a> { /// `SOURCE_NO_DISABLE_HOVER`, `SOURCE_NO_HOLD_TO_OPEN_OTHERS`, `SOURCE_ALLOW_NULL_ID`, /// `SOURCE_EXTERN`, `SOURCE_AUTO_EXPIRE_PAYLOAD` make semantic sense, but any other flags will /// be accepted without panic. - pub fn flags(mut self, flags: DragDropFlags) -> Self { + #[inline] + pub const fn flags(mut self, flags: DragDropFlags) -> Self { self.flags = flags; self } /// Sets the condition on the [DragDropSource]. Defaults to [Always](Condition::Always). - pub fn condition(mut self, cond: Condition) -> Self { + #[inline] + pub const fn condition(mut self, cond: Condition) -> Self { self.cond = cond; self } @@ -144,6 +147,7 @@ impl<'a> DragDropSource<'a> { /// In the above, you'll see how the payload is really just a message passing service. /// If you want to pass a simple integer or other "plain old data", take a look at /// [begin_payload](Self::begin_payload). + #[inline] pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { self.begin_payload(ui, ()) } @@ -157,9 +161,10 @@ impl<'a> DragDropSource<'a> { /// and this returned token does nothing. Additionally, a given target may use the flag /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown. /// - /// This function also takes a payload in the form of `T: Copy + 'static`. We use this bound to - /// ensure that we can safely send and receive the given type from C++ without worrying about - /// handling drops. + /// This function also takes a payload in the form of `T: Copy + 'static`. ImGui will + /// memcpy this data immediately to an internally held buffer, and will return the data + /// to [DragDropTarget]. + #[inline] pub fn begin_payload<'ui, T: Copy + 'static>( self, ui: &Ui<'ui>, @@ -184,8 +189,9 @@ impl<'a> DragDropSource<'a> { /// and this returned token does nothing. Additionally, a given target may use the flag /// `ACCEPT_NO_PREVIEW_TOOLTIP`, which will also prevent this tooltip from being shown. /// - /// This function also takes a payload of any `*const T`. Please avoid directly using it - /// if you can. + /// This function also takes a payload of any `*const T`. ImGui will + /// memcpy this data immediately to an internally held buffer, and will return the data + /// to [DragDropTarget]. /// /// ## Safety /// This function itself will not cause a panic, but using it directly opts you into @@ -200,6 +206,7 @@ impl<'a> DragDropSource<'a> { /// /// Overall, users should be very sure that this function is needed before they reach for it, and instead /// should consider either [begin](Self::begin) or [begin_payload](Self::begin_payload). + #[inline] pub unsafe fn begin_payload_unchecked<'ui>( &self, _ui: &Ui<'ui>, @@ -223,12 +230,14 @@ pub struct DragDropSourceToolTip<'ui>(PhantomData>); impl DragDropSourceToolTip<'_> { /// Creates a new tooltip internally. + #[inline] fn push() -> Self { Self(PhantomData) } /// Ends the tooltip directly. You could choose to simply allow this to drop /// by not calling this, which will also be fine. + #[inline] pub fn pop(self) { // left empty to invoke drop... } @@ -291,6 +300,9 @@ impl<'ui> DragDropTarget<'ui> { /// Accepts an empty payload. This is the safest option for raising named events /// in the DragDrop API. See [DragDropSource::begin] for more information on how you /// might use this pattern. + /// + /// Note: If you began this operation with `begin_payload_unchecked` it always incorrect + /// to use this function. Use `accept_payload_unchecked` instead pub fn accept_payload_empty( &self, name: &ImStr, @@ -306,6 +318,9 @@ impl<'ui> DragDropTarget<'ui> { /// Accepts a payload with plain old data in it. This returns a Result, since you can specify any /// type. The sent type must match the return type (via TypeId) to receive an `Ok`. + /// + /// Note: If you began this operation with `begin_payload_unchecked` it always incorrect + /// to use this function. Use `accept_payload_unchecked` instead pub fn accept_payload( &self, name: &ImStr, @@ -316,11 +331,14 @@ impl<'ui> DragDropTarget<'ui> { // convert the unsafe payload to our Result output.map(|unsafe_payload| { // sheering off the typeid... - let received = unsafe { *(unsafe_payload.data as *const any::TypeId) }; - let requested = any::TypeId::of::(); + let received = + unsafe { (unsafe_payload.data as *const TypedPayloadHeader).read_unaligned() }; + let expected = any::TypeId::of::(); - if received == requested { - let data = unsafe { *(unsafe_payload.data as *const TypedPayload) }.data; + if received.type_id == expected { + let data = + unsafe { (unsafe_payload.data as *const TypedPayload).read_unaligned() } + .data; Ok(DragDropPayloadPod { data, preview: unsafe_payload.preview, @@ -328,8 +346,8 @@ impl<'ui> DragDropTarget<'ui> { }) } else { Err(PayloadIsWrongType { - requested, received, + expected: TypedPayloadHeader::new::(), }) } }) @@ -372,7 +390,7 @@ impl<'ui> DragDropTarget<'ui> { // now this way. Some(DragDropPayload { data: inner.Data, - size: inner.DataSize, + size: inner.DataSize as usize, preview: inner.Preview, delivery: inner.Delivery, }) @@ -394,7 +412,8 @@ impl Drop for DragDropTarget<'_> { /// An empty DragDropPayload. It has no data in it, and just includes /// two bools with status information. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub struct DragDropPayloadEmpty { /// Set when [`accept_payload_empty`](Self::accept_payload_empty) was called /// and mouse has been hovering the target item. @@ -407,8 +426,9 @@ pub struct DragDropPayloadEmpty { /// A DragDropPayload with status information and some POD, or plain old data, /// in it. -#[derive(Debug)] -pub struct DragDropPayloadPod { +#[derive(Debug, Copy, Clone)] +#[non_exhaustive] +pub struct DragDropPayloadPod { /// The kind data which was requested. pub data: T, @@ -422,6 +442,7 @@ pub struct DragDropPayloadPod { } #[derive(Debug)] +#[non_exhaustive] pub struct DragDropPayload { /// Data which is copied and owned by ImGui. If you have accepted the payload, you can /// take ownership of the data; otherwise, view it immutably. Interacting with `data` is @@ -429,7 +450,7 @@ pub struct DragDropPayload { pub data: *const ffi::c_void, /// The size of the data in bytes. - pub size: i32, + pub size: usize, /// Set when [`accept_payload_unchecked`](Self::accept_payload_unchecked) was called /// and mouse has been hovering the target item. @@ -444,15 +465,41 @@ pub struct DragDropPayload { #[derive(Debug, Clone, Copy)] #[repr(C)] struct TypedPayload { - type_id: any::TypeId, + header: TypedPayloadHeader, data: T, } +/// We have this struct separately to easily read it off in unsafe code. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] +#[repr(C)] +struct TypedPayloadHeader { + type_id: any::TypeId, + #[cfg(debug_assertions)] + type_name: &'static str, +} + +impl TypedPayloadHeader { + #[cfg(debug_assertions)] + fn new() -> Self { + Self { + type_id: any::TypeId::of::(), + type_name: any::type_name::(), + } + } + + #[cfg(not(debug_assertions))] + fn new() -> Self { + Self { + type_id: any::TypeId::of::(), + } + } +} + impl TypedPayload { /// Creates a new typed payload which contains this data. pub fn new(data: T) -> Self { Self { - type_id: any::TypeId::of::(), + header: TypedPayloadHeader::new::(), data, } } @@ -462,13 +509,25 @@ impl TypedPayload { /// but you can view questionably useful debug information with Debug formatting. #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] pub struct PayloadIsWrongType { - requested: any::TypeId, - received: any::TypeId, + expected: TypedPayloadHeader, + received: TypedPayloadHeader, } +#[cfg(debug_assertions)] impl std::fmt::Display for PayloadIsWrongType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Payload is wrong type") + write!( + f, + "Payload is {} -- expected {}", + self.received.type_name, self.expected.type_name + ) + } +} + +#[cfg(not(debug_assertions))] +impl std::fmt::Display for PayloadIsWrongType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.pad("Payload is wrong type") } } diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 7702dea..d0248e0 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -53,7 +53,7 @@ use internal::RawCast; mod clipboard; mod columns; mod context; -mod drag_drop; +pub mod drag_drop; mod draw_list; mod fonts; mod input; From 1983f4794149a4d98c4d93169128e35095ce7bbf Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Thu, 4 Feb 2021 00:36:47 -0800 Subject: [PATCH 9/9] final --- CHANGELOG.markdown | 6 +++ imgui/src/drag_drop.rs | 98 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 85 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 4998488..2eea968 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -22,6 +22,12 @@ - `Ui::get_background_draw_list()` has been fixed when used outside of a window context, and now has an example. - `Ui::get_foreground_draw_list()` has been added, analogous to `Ui::get_background_draw_list()`. +- Added drag drop support, with a safe and an unsafe variant: https://github.com/imgui-rs/imgui-rs/pull/428 + - `DragDropSource` allows users to create a dragdrop payload which is either empty, of `'static + Copy` data, + or `unsafe`, allowing for theoretically arbitrary payloads. + - `DragDropTarget` allows users to accept any of the above payloads. + - Extensive documentation has been made on all of these features, hopefully as a target for future features. + ## [0.6.1] - 2020-12-16 - Support for winit 0.24.x diff --git a/imgui/src/drag_drop.rs b/imgui/src/drag_drop.rs index 4687e29..8c468d0 100644 --- a/imgui/src/drag_drop.rs +++ b/imgui/src/drag_drop.rs @@ -1,3 +1,31 @@ +//! Structs to create a Drag and Drop sequence. Almost all structs are re-exported +//! and can be accessed from the crate root; some additional utilities can be found in here. +//! +//! A DragDrop is a UI mechanism where users can appear to "drag" +//! some data from one [source](DragDropSource) to one [target](DragDropTarget). +//! A source and a target must both have some `name` identifier, which is declared when they +//! are created. If these names are equal, then a `payload` of some kind +//! will be given to the target caller whne the user releases their mouse button over +//! the target (additionally, the UI will reflect that the payload *can* be deposited +//! in the target). +//! +//! The complexity of this implementation is primarily in managing this payload. Users +//! can provide three different kinds of payloads: +//! +//! 1. Users can give an [empty payload](DragDropPayloadEmpty) with [begin](DragDropSource::begin). +//! This payload type is essentially just a notification system, but using some shared state, +//! this can be reasonably powerful, and is the safest way to transfer non-Copy data offered +//! right now. +//! 2. Users can give a [simple Copy payload](DragDropPayloadPod) with [begin](DragDropSource::begin_payload). +//! This allows users to copy data to Dear ImGui, which will take ownership over it, and then be given +//! it back to the Target. Please note: users are of course free to not drop any drag (cancel a drag), +//! so this data could easily be lost forever. Our `'static + Copy` bound is intended to keep users +//! to simplistic types. +//! 3. An unsafe implementation is provided which allows for any data to be unsafely copied. Note that once +//! you use this method, the safe implementations in #1 and #2 can create memory unsafety problems; notably, +//! they both assume that a payload has certain header information within it. +//! +//! For examples of each payload type, see [DragDropSource]. use std::{any, ffi, marker::PhantomData}; use crate::{sys, Condition, ImStr, Ui}; @@ -164,6 +192,36 @@ impl<'a> DragDropSource<'a> { /// This function also takes a payload in the form of `T: Copy + 'static`. ImGui will /// memcpy this data immediately to an internally held buffer, and will return the data /// to [DragDropTarget]. + /// + /// ```no_run + /// # use imgui::*; + /// fn show_ui(ui: &Ui<'_>) { + /// ui.button(im_str!("Drag me!"), [0.0, 0.0]); + /// + /// let drag_drop_name = im_str!("Test Drag"); + /// let msg_to_send = "hello there sailor"; + /// + /// // drag drop SOURCE + /// if let Some(tooltip) = DragDropSource::new(drag_drop_name).begin_payload(ui, msg_to_send) { + /// ui.text("Sending message!"); + /// tooltip.end(); + /// } + /// + /// ui.button(im_str!("Target me!"), [0.0, 0.0]); + /// + /// // drag drop TARGET + /// if let Some(target) = imgui::DragDropTarget::new(ui) { + /// if let Some(Ok(payload_data)) = target + /// .accept_payload::<&'static str>(drag_drop_name, DragDropFlags::empty()) + /// { + /// let msg = payload_data.data; + /// assert_eq!(msg, msg_to_send); + /// } + /// + /// target.pop(); + /// } + /// } + /// ``` #[inline] pub fn begin_payload<'ui, T: Copy + 'static>( self, @@ -238,7 +296,7 @@ impl DragDropSourceToolTip<'_> { /// Ends the tooltip directly. You could choose to simply allow this to drop /// by not calling this, which will also be fine. #[inline] - pub fn pop(self) { + pub fn end(self) { // left empty to invoke drop... } } @@ -415,11 +473,11 @@ impl Drop for DragDropTarget<'_> { #[derive(Debug, Clone, Copy)] #[non_exhaustive] pub struct DragDropPayloadEmpty { - /// Set when [`accept_payload_empty`](Self::accept_payload_empty) was called + /// Set when [`accept_payload_empty`](DragDropTarget::accept_payload_empty) was called /// and mouse has been hovering the target item. pub preview: bool, - /// Set when [`accept_payload_empty`](Self::accept_payload_empty) was + /// Set when [`accept_payload_empty`](DragDropTarget::accept_payload_empty) was /// called and mouse button is released over the target item. pub delivery: bool, } @@ -432,11 +490,11 @@ pub struct DragDropPayloadPod { /// The kind data which was requested. pub data: T, - /// Set when [`accept_payload`](Self::accept_payload) was called + /// Set when [`accept_payload`](DragDropTarget::accept_payload) was called /// and mouse has been hovering the target item. pub preview: bool, - /// Set when [`accept_payload`](Self::accept_payload) was + /// Set when [`accept_payload`](DragDropTarget::accept_payload) was /// called and mouse button is released over the target item. pub delivery: bool, } @@ -452,16 +510,17 @@ pub struct DragDropPayload { /// The size of the data in bytes. pub size: usize, - /// Set when [`accept_payload_unchecked`](Self::accept_payload_unchecked) was called + /// Set when [`accept_payload_unchecked`](DragDropTarget::accept_payload_unchecked) was called /// and mouse has been hovering the target item. pub preview: bool, - /// Set when [`accept_payload_unchecked`](Self::accept_payload_unchecked) was + /// Set when [`accept_payload_unchecked`](DragDropTarget::accept_payload_unchecked) was /// called and mouse button is released over the target item. If this is set to false, then you /// set DragDropFlags::ACCEPT_BEFORE_DELIVERY and shouldn't mutate `data`. pub delivery: bool, } +/// A typed payload. #[derive(Debug, Clone, Copy)] #[repr(C)] struct TypedPayload { @@ -469,7 +528,17 @@ struct TypedPayload { data: T, } -/// We have this struct separately to easily read it off in unsafe code. +impl TypedPayload { + /// Creates a new typed payload which contains this data. + fn new(data: T) -> Self { + Self { + header: TypedPayloadHeader::new::(), + data, + } + } +} + +/// A header for a typed payload. #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] #[repr(C)] struct TypedPayloadHeader { @@ -495,18 +564,9 @@ impl TypedPayloadHeader { } } -impl TypedPayload { - /// Creates a new typed payload which contains this data. - pub fn new(data: T) -> Self { - Self { - header: TypedPayloadHeader::new::(), - data, - } - } -} - /// Indicates that an incorrect payload type was received. It is opaque, -/// but you can view questionably useful debug information with Debug formatting. +/// but you can view useful information with Debug formatting when +/// `debug_assertions` are enabled. #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] pub struct PayloadIsWrongType { expected: TypedPayloadHeader,