From 35c0cf0e6f555118162b2886b5eeabf9b54718f4 Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Sun, 24 Jan 2021 14:55:03 -0800 Subject: [PATCH] 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;