Internals/experimental: BeginComboPreview(), EndComboPreview(). (#4168, #1658)
(amended)
diff --git a/docs/TODO.txt b/docs/TODO.txt
index 146f697..268b9b8 100644
--- a/docs/TODO.txt
+++ b/docs/TODO.txt
@@ -184,7 +184,7 @@
- combo: use clipper: make it easier to disable clipper with a single flag.
- combo: flag for BeginCombo to not return true when unchanged (#1182)
- - combo: a way/helper to customize the combo preview (#1658)
+ - combo: a way/helper to customize the combo preview (#1658) -> exeperimental BeginComboPreview()
- combo/listbox: keyboard control. need InputText-like non-active focus + key handling. considering keyboard for custom listbox (pr #203)
- listbox: multiple selection.
- listbox: unselect option (#1208)
diff --git a/imgui.h b/imgui.h
index 7465559..10ffcb6 100644
--- a/imgui.h
+++ b/imgui.h
@@ -2463,6 +2463,7 @@
IMGUI_API void _ResetForNewFrame();
IMGUI_API void _ClearFreeMemory();
IMGUI_API void _PopUnusedDrawCmd();
+ IMGUI_API void _TryMergeDrawCmds();
IMGUI_API void _OnChangedClipRect();
IMGUI_API void _OnChangedTextureID();
IMGUI_API void _OnChangedVtxOffset();
diff --git a/imgui_demo.cpp b/imgui_demo.cpp
index 0c9b92b..513ba66 100644
--- a/imgui_demo.cpp
+++ b/imgui_demo.cpp
@@ -1026,8 +1026,8 @@
// stored in the object itself, etc.)
const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };
static int item_current_idx = 0; // Here we store our selection data as an index.
- const char* combo_label = items[item_current_idx]; // Label to preview before opening the combo (technically it could be anything)
- if (ImGui::BeginCombo("combo 1", combo_label, flags))
+ const char* combo_preview_value = items[item_current_idx]; // Pass in the preview value visible before opening the combo (it could be anything)
+ if (ImGui::BeginCombo("combo 1", combo_preview_value, flags))
{
for (int n = 0; n < IM_ARRAYSIZE(items); n++)
{
@@ -1043,10 +1043,12 @@
}
// Simplified one-liner Combo() API, using values packed in a single constant string
+ // This is a convenience for when the selection set is small and known at compile-time.
static int item_current_2 = 0;
ImGui::Combo("combo 2 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0");
// Simplified one-liner Combo() using an array of const char*
+ // This is not very useful (may obsolete): prefer using BeginCombo()/EndCombo() for full control.
static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview
ImGui::Combo("combo 3 (array)", &item_current_3, items, IM_ARRAYSIZE(items));
diff --git a/imgui_draw.cpp b/imgui_draw.cpp
index ca435bb..15d738d 100644
--- a/imgui_draw.cpp
+++ b/imgui_draw.cpp
@@ -491,6 +491,18 @@
#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TextureId, VtxOffset
#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TextureId, VtxOffset
+// Try to merge two last draw commands
+void ImDrawList::_TryMergeDrawCmds()
+{
+ ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];
+ ImDrawCmd* prev_cmd = curr_cmd - 1;
+ if (ImDrawCmd_HeaderCompare(curr_cmd, prev_cmd) == 0 && curr_cmd->UserCallback == NULL && prev_cmd->UserCallback == NULL)
+ {
+ prev_cmd->ElemCount += curr_cmd->ElemCount;
+ CmdBuffer.pop_back();
+ }
+}
+
// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack.
// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only.
void ImDrawList::_OnChangedClipRect()
diff --git a/imgui_internal.h b/imgui_internal.h
index 2554eed..9caf594 100644
--- a/imgui_internal.h
+++ b/imgui_internal.h
@@ -808,6 +808,12 @@
ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease
};
+// Extend ImGuiComboFlags_
+enum ImGuiComboFlagsPrivate_
+{
+ ImGuiComboFlags_CustomPreview = 1 << 20 // enable BeginComboPreview()
+};
+
// Extend ImGuiSliderFlags_
enum ImGuiSliderFlagsPrivate_
{
@@ -996,6 +1002,19 @@
ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; }
};
+// Storage data for BeginComboPreview()/EndComboPreview()
+struct IMGUI_API ImGuiComboPreviewData
+{
+ ImRect PreviewRect;
+ ImVec2 BackupCursorPos;
+ ImVec2 BackupCursorMaxPos;
+ ImVec2 BackupCursorPosPrevLine;
+ float BackupPrevLineTextBaseOffset;
+ ImGuiLayoutType BackupLayout;
+
+ ImGuiComboPreviewData() { memset(this, 0, sizeof(*this)); }
+};
+
// Stacked storage data for BeginGroup()/EndGroup()
struct IMGUI_API ImGuiGroupData
{
@@ -1552,6 +1571,7 @@
float ColorEditLastSat; // Backup of last Saturation associated to LastColor[3], so we can restore Saturation in lossy RGB<>HSV round trips
float ColorEditLastColor[3];
ImVec4 ColorPickerRef; // Initial/reference color at the time of opening the color picker.
+ ImGuiComboPreviewData ComboPreviewData;
float SliderCurrentAccum; // Accumulated slider delta when using navigation controls.
bool SliderCurrentAccumDirty; // Has the accumulated slider delta changed since last time we tried to apply it?
bool DragCurrentAccumDirty;
@@ -2416,6 +2436,8 @@
// Combos
IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags);
+ IMGUI_API bool BeginComboPreview();
+ IMGUI_API void EndComboPreview();
// Gamepad/Keyboard Navigation
IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit);
diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp
index 5e7b1c8..593c8ef 100644
--- a/imgui_widgets.cpp
+++ b/imgui_widgets.cpp
@@ -1541,6 +1541,8 @@
// - BeginCombo()
// - BeginComboPopup() [Internal]
// - EndCombo()
+// - BeginComboPreview() [Internal]
+// - EndComboPreview() [Internal]
// - Combo()
//-------------------------------------------------------------------------
@@ -1602,6 +1604,14 @@
}
RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);
+ // Custom preview
+ if (flags & ImGuiComboFlags_CustomPreview)
+ {
+ g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
+ IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
+ preview_value = NULL;
+ }
+
// Render preview and label
if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
{
@@ -1683,6 +1693,57 @@
EndPopup();
}
+// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
+// (Experimental, see GitHub issues: #1658, #4168)
+bool ImGui::BeginComboPreview()
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
+
+ if (window->SkipItems || !window->ClipRect.Overlaps(window->DC.LastItemRect)) // FIXME: Because we don't have a ImGuiItemStatusFlags_Visible flag to test last ItemAdd() result
+ return false;
+ IM_ASSERT(window->DC.LastItemRect.Min.x == preview_data->PreviewRect.Min.x && window->DC.LastItemRect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag?
+ if (!window->ClipRect.Contains(preview_data->PreviewRect)) // Narrower test (optional)
+ return false;
+
+ // FIXME: This could be contained in a PushWorkRect() api
+ preview_data->BackupCursorPos = window->DC.CursorPos;
+ preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
+ preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
+ preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
+ preview_data->BackupLayout = window->DC.LayoutType;
+ window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
+ window->DC.CursorMaxPos = window->DC.CursorPos;
+ window->DC.LayoutType = ImGuiLayoutType_Horizontal;
+ PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
+
+ return true;
+}
+
+void ImGui::EndComboPreview()
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
+
+ // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
+ ImDrawList* draw_list = window->DrawList;
+ if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
+ if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
+ {
+ draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
+ draw_list->_TryMergeDrawCmds();
+ }
+ PopClipRect();
+ window->DC.CursorPos = preview_data->BackupCursorPos;
+ window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);
+ window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
+ window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
+ window->DC.LayoutType = preview_data->BackupLayout;
+ preview_data->PreviewRect = ImRect();
+}
+
// Getter for the old Combo() API: const char*[]
static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
{