Updated for pod data

This commit is contained in:
Jonathan Spira 2021-01-25 14:28:05 -08:00
parent 07e0580d2d
commit a8975b95c0
2 changed files with 194 additions and 23 deletions

View File

@ -16,6 +16,7 @@ exclude = ["/resources"]
bitflags = "1" bitflags = "1"
imgui-sys = { version = "0.6.0", path = "../imgui-sys" } imgui-sys = { version = "0.6.0", path = "../imgui-sys" }
parking_lot = "0.11" parking_lot = "0.11"
bytemuck = "1.5"
[features] [features]
wasm = ["imgui-sys/wasm"] wasm = ["imgui-sys/wasm"]

View File

@ -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<String>) {
/// 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)] #[derive(Debug)]
pub struct DragDropSource<'a> { pub struct DragDropSource<'a> {
name: &'a ImStr, name: &'a ImStr,
flags: DragDropFlags, flags: DragDropFlags,
payload: *const ffi::c_void,
size: usize,
cond: Condition, cond: Condition,
} }
@ -59,29 +100,27 @@ impl<'a> DragDropSource<'a> {
Self { Self {
name, name,
flags: DragDropFlags::empty(), flags: DragDropFlags::empty(),
payload: ptr::null(),
size: 0,
cond: Condition::Always, cond: Condition::Always,
} }
} }
/// Creates a new [DragDropSource] with no flags and the `Condition::Always` with the given name. // /// 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 // /// ImGui refers to this `name` field as a `type`, but really it's just an identifier to match up
/// Source/Target for DragDrop. // /// Source/Target for DragDrop.
/// // ///
/// This payload will be passed to ImGui, which will provide it to // /// 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). // /// a target when it runs [accept_drag_drop_payload](DragDropTarget::accept_drag_drop_payload).
/// // ///
/// ## Safety // /// ## Safety
/// This function is not inherently unsafe, and won't panic itself, but using it opts you into // /// 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), // /// 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. // /// you can easily create memory safety problems.
pub unsafe fn payload<T>(name: &'a ImStr, payload: *const T) -> Self { // pub unsafe fn payload<T>(name: &'a ImStr, payload: *const T) -> Self {
let mut output = Self::new(name); // let mut output = Self::new(name);
output.payload = payload as *const ffi::c_void; // output.payload = payload as *const ffi::c_void;
output.size = std::mem::size_of::<T>(); // output.size = std::mem::size_of::<T>();
output // output
} // }
/// Sets the flags on the [DragDropSource]. Only the flags `SOURCE_NO_PREVIEW_TOOLTIP`, /// 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_NO_DISABLE_HOVER`, `SOURCE_NO_HOLD_TO_OPEN_OTHERS`, `SOURCE_ALLOW_NULL_ID`,
@ -98,6 +137,72 @@ impl<'a> DragDropSource<'a> {
self 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<DragDropSourceToolTip<'ui>> {
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() /// 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<DragDropSourceToolTip<'ui>> { pub fn begin<'ui>(self, _ui: &'ui Ui) -> Option<DragDropSourceToolTip<'ui>> {
let should_begin = unsafe { sys::igBeginDragDropSource(self.flags.bits() as i32) }; let should_begin = unsafe { sys::igBeginDragDropSource(self.flags.bits() as i32) };
@ -106,8 +211,8 @@ impl<'a> DragDropSource<'a> {
unsafe { unsafe {
sys::igSetDragDropPayload( sys::igSetDragDropPayload(
self.name.as_ptr(), self.name.as_ptr(),
self.payload, self.payload as *const _ as *const ffi::c_void,
self.size, std::mem::size_of::<T>(),
self.cond as i32, self.cond as i32,
); );
@ -163,6 +268,27 @@ pub struct DragDropPayload {
pub delivery: bool, 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<T: bytemuck::Pod> {
/// The kind data which was requested.
pub data: T,
/// @fixme add docs.
pub preview: bool,
// @fixme add docs
pub delivery: bool,
}
#[derive(Debug)] #[derive(Debug)]
pub struct DragDropTarget<'ui>(PhantomData<Ui<'ui>>); pub struct DragDropTarget<'ui>(PhantomData<Ui<'ui>>);
@ -176,6 +302,50 @@ impl<'ui> DragDropTarget<'ui> {
} }
} }
pub fn accept_empty_payload(
&self,
name: &ImStr,
flags: DragDropFlags,
) -> Option<DragDropEmptyPayload> {
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<T: bytemuck::Pod>(
&self,
name: &ImStr,
flags: DragDropFlags,
) -> Option<Result<DragDropPodPayload<T>, 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 /// 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. /// 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? /// How do we possibly handle communicating that this data is somewhat immutable?