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,