From c5536e49efb61d786ea7f5a07031eeca34e00ec1 Mon Sep 17 00:00:00 2001 From: omar Date: Sun, 29 Oct 2017 21:15:02 +0100 Subject: [PATCH] Drag and drop API experiment --- imgui.cpp | 172 ++++++++++++++++++++++++++++++++++++++++++++++- imgui_internal.h | 53 +++++++++++++++ 2 files changed, 223 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 0c2e72a59..ad4a1895f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2231,6 +2231,15 @@ void ImGui::NewFrame() if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) g.ScalarAsInputTextId = 0; + // Elapse drag & drop payload + if (g.DragDropActive && g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) + { + g.DragDropActive = false; + g.DragDropPayload.Clear(); + g.DragDropPayloadBufHeap.clear(); + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + } + // Update keyboard input state memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) @@ -5868,7 +5877,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool { if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease)) if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps - pressed = true; + if (!g.DragDropActive) + pressed = true; ClearActiveID(); } } @@ -9426,7 +9436,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl else window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border - if (hovered && !(flags & ImGuiColorEditFlags_NoTooltip)) + if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); return pressed; @@ -10584,6 +10594,164 @@ void ImGui::Value(const char* prefix, float v, const char* float_format) } } +//----------------------------------------------------------------------------- +// DRAG AND DROP +//----------------------------------------------------------------------------- + +// Call when current ID is active. +// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() +bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags, int mouse_button) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.IO.MouseDown[mouse_button] == false) + return false; + if (g.ActiveId != window->DC.LastItemId) + return false; + + if (IsMouseDragging(mouse_button)) + { + if (!g.DragDropActive) + { + ImGuiPayload& payload = g.DragDropPayload; + payload.Clear(); + payload.SourceId = g.ActiveId; + payload.SourceParentId = window->IDStack.back(); + g.DragDropActive = true; + g.DragDropSourceFlags = flags; + g.DragDropMouseButton = mouse_button; + } + + if (!(flags & ImGuiDragDropFlags_SourceNoAutoTooltip)) + { + // FIXME-DRAG + //SetNextWindowPos(g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding); + //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This is better but e.g ColorButton with checkboard has issue with transparent colors :( + SetNextWindowPos(g.IO.MousePos); + PushStyleColor(ImGuiCol_PopupBg, GetStyleColorVec4(ImGuiCol_PopupBg) * ImVec4(1.0f, 1.0f, 1.0f, 0.6f)); + BeginTooltipEx(ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_ShowBorders); + } + return true; + } + return false; +} + +void ImGui::EndDragDropSource() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + IM_ASSERT(g.DragDropPayload.DataFrameCount != -1); // Forgot to call SetDragDropSourcePayload(), at least once on the first frame of a successful BeginDragDropSource() + + if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoAutoTooltip)) + { + EndTooltip(); + PopStyleColor(); + //PopStyleVar(); + } +} + +// Use 'cond' to choose to submit paypload on drag start or every frame +bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + ImGuiPayload& payload = g.DragDropPayload; + if (cond == 0) + cond = ImGuiCond_Always; + + IM_ASSERT(type != NULL); + IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType)); // Payload type can be at most 8 characters longs + IM_ASSERT(data != NULL && data_size > 0); + IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + + if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) + { + // Copy payload + ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + g.DragDropPayloadBufHeap.resize(0); + if (data_size > sizeof(g.DragDropPayloadBufLocal)) + { + // Store in heap + g.DragDropPayloadBufHeap.resize((int)data_size); + payload.Data = g.DragDropPayloadBufHeap.Data; + memcpy((void*)payload.Data, data, data_size); + } + else if (data_size > 0) + { + // Store locally + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + payload.Data = g.DragDropPayloadBufLocal; + memcpy((void*)payload.Data, data, data_size); + } + else + { + payload.Data = NULL; + } + payload.DataSize = (int)data_size; + } + payload.DataFrameCount = g.FrameCount; + + return (payload.AcceptFrameCount == g.FrameCount) || (payload.AcceptFrameCount == g.FrameCount - 1); +} + +bool ImGui::BeginDragDropTarget() +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + //if (!window->DC.LastItemRectHoveredRect || (g.ActiveId == g.DragDropPayload.SourceId || g.ActiveIdPreviousFrame == g.DragDropPayload.SourceId)) + if (!window->DC.LastItemRectHoveredRect || (window->DC.LastItemId && window->DC.LastItemId == g.DragDropPayload.SourceId)) + return false; + if (window->RootWindow != g.HoveredWindow->RootWindow) + return false; + + return true; +} + +const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiPayload& payload = g.DragDropPayload; + IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + IM_ASSERT(window->DC.LastItemRectHoveredRect); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + IM_ASSERT(payload.DataFrameCount != -1); // Internal/usage error, please report! + if (type != NULL && !payload.IsDataType(type)) + return NULL; + + if (payload.AcceptId == 0) + payload.AcceptId = window->DC.LastItemId; + + bool was_accepted_previously = (payload.AcceptFrameCount == g.FrameCount - 1); + if (payload.AcceptFrameCount != g.FrameCount) + { + // Render drop visuals + if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && was_accepted_previously) + { + ImRect r = window->DC.LastItemRect; + r.Expand(4.0f); + window->DrawList->AddRectFilled(r.Min, r.Max, IM_COL32(255, 255, 0, 20), 0.0f); // FIXME-DRAG FIXME-STYLE + window->DrawList->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); // FIXME-DRAG FIXME-STYLE + } + payload.AcceptFrameCount = g.FrameCount; + } + + payload.Delivery = was_accepted_previously && IsMouseReleased(g.DragDropMouseButton); + if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) + return NULL; + + return &payload; +} + +// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. +void ImGui::EndDragDropTarget() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); +} + //----------------------------------------------------------------------------- // PLATFORM DEPENDENT HELPERS //----------------------------------------------------------------------------- diff --git a/imgui_internal.h b/imgui_internal.h index 1cb723ca7..5c82ea1d8 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -43,9 +43,11 @@ struct ImGuiIniData; struct ImGuiMouseCursorData; struct ImGuiPopupRef; struct ImGuiWindow; +struct ImGuiPayload; // User data payload for drag and drop operations typedef int ImGuiLayoutType; // enum: horizontal or vertical // enum ImGuiLayoutType_ typedef int ImGuiButtonFlags; // flags: for ButtonEx(), ButtonBehavior() // enum ImGuiButtonFlags_ +typedef int ImGuiDragDropFlags; // flags: for *DragDrop*() // enum ImGuiDragDropFlags_ typedef int ImGuiItemFlags; // flags: for PushItemFlag() // enum ImGuiItemFlags_ typedef int ImGuiSeparatorFlags; // flags: for Separator() - internal // enum ImGuiSeparatorFlags_ typedef int ImGuiSliderFlags; // flags: for SliderBehavior() // enum ImGuiSliderFlags_ @@ -215,6 +217,15 @@ enum ImGuiSeparatorFlags_ ImGuiSeparatorFlags_Vertical = 1 << 1 }; +// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() +enum ImGuiDragDropFlags_ +{ + ImGuiDragDropFlags_SourceNoAutoTooltip = 1 << 0, + ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 1, // AcceptDragDropPayload() returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered. + ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 2, // Do not draw the default highlight rectangle when hovering over target. + ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect // For peeking ahead and inspecting the payload before delivery. +}; + // FIXME: this is in development, not exposed/functional as a generic feature yet. enum ImGuiLayoutType_ { @@ -404,6 +415,28 @@ struct ImGuiPopupRef ImGuiPopupRef(ImGuiID id, ImGuiWindow* parent_window, ImGuiID parent_menu_set, const ImVec2& mouse_pos) { PopupId = id; Window = NULL; ParentWindow = parent_window; ParentMenuSet = parent_menu_set; MousePosOnOpen = mouse_pos; } }; +// Data payload for Drag and Drop operations +struct ImGuiPayload +{ + // Members + const void* Data; // Data (copied and owned by dear imgui) + int DataSize; // Data size + + // [Internal] + ImGuiID SourceId; // Source item id + ImGuiID SourceParentId; // Source parent id (if available) + ImGuiID AcceptId; // Target item id (set at the time of accepting the payload) + int AcceptFrameCount; // Last time a target expressed a desire to accept the source + int DataFrameCount; // Data timestamp + char DataType[8 + 1]; // Data type tag (short user-supplied string) + bool Delivery; // Set when AcceptDragDropPayload() was called and the mouse button is released over the target item + + ImGuiPayload() { Clear(); } + void Clear() { SourceId = SourceParentId = AcceptId = 0; AcceptFrameCount = -1; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Delivery = false; } + bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; } + bool IsDelivery() const { return Delivery; } +}; + // Main state for ImGui struct ImGuiContext { @@ -472,6 +505,14 @@ struct ImGuiContext ImGuiMouseCursor MouseCursor; ImGuiMouseCursorData MouseCursorData[ImGuiMouseCursor_Count_]; + // Drag and Drop + bool DragDropActive; + ImGuiDragDropFlags DragDropSourceFlags; + int DragDropMouseButton; + ImGuiPayload DragDropPayload; + ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly + unsigned char DragDropPayloadBufLocal[8]; + // Widget state ImGuiTextEditState InputTextState; ImFont InputTextPasswordFont; @@ -547,6 +588,10 @@ struct ImGuiContext SetNextTreeNodeOpenVal = false; SetNextTreeNodeOpenCond = 0; + DragDropActive = false; + DragDropSourceFlags = 0; + DragDropMouseButton = -1; + ScalarAsInputTextId = 0; ColorEditOptions = ImGuiColorEditFlags__OptionsDefault; DragCurrentValue = 0.0f; @@ -810,6 +855,14 @@ namespace ImGui IMGUI_API void Scrollbar(ImGuiLayoutType direction); IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). not exposed because it is misleading what it doesn't have an effect on regular layout. + // FIXME-WIP: New Drag and Drop API + IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0, int mouse_button = 0); // Call when the current item is active. If this return true, you can call SetDragDropPayload() + EndDragDropSource() + IMGUI_API bool SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond = 0); // Type is a user defined string of maximum 8 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui. + IMGUI_API void EndDragDropSource(); + IMGUI_API bool BeginDragDropTarget(); // Call after submitting an item that may receive an item. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget() + IMGUI_API const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0); // Accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released. + IMGUI_API void EndDragDropTarget(); + // FIXME-WIP: New Columns API IMGUI_API void BeginColumns(const char* id, int count, ImGuiColumnsFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). IMGUI_API void EndColumns(); // close columns