mirror of
https://github.com/Drezil/imgui.git
synced 2024-11-22 11:57:00 +00:00
Refactor: Moved Combo/ListBox functions from imgui.cpp to imgui_widgets.cpp (#2036)
This commit is contained in:
parent
6468a3c0ce
commit
f26b8c1e07
309
imgui.cpp
309
imgui.cpp
@ -10732,213 +10732,6 @@ bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_fla
|
|||||||
return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", extra_flags);
|
return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", extra_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static float CalcMaxPopupHeightFromItemCount(int items_count)
|
|
||||||
{
|
|
||||||
ImGuiContext& g = *GImGui;
|
|
||||||
if (items_count <= 0)
|
|
||||||
return FLT_MAX;
|
|
||||||
return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
|
|
||||||
{
|
|
||||||
// Always consume the SetNextWindowSizeConstraint() call in our early return paths
|
|
||||||
ImGuiContext& g = *GImGui;
|
|
||||||
ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
|
|
||||||
g.NextWindowData.SizeConstraintCond = 0;
|
|
||||||
|
|
||||||
ImGuiWindow* window = GetCurrentWindow();
|
|
||||||
if (window->SkipItems)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
|
|
||||||
|
|
||||||
const ImGuiStyle& style = g.Style;
|
|
||||||
const ImGuiID id = window->GetID(label);
|
|
||||||
|
|
||||||
const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
|
|
||||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
|
||||||
const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
|
|
||||||
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
|
|
||||||
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
|
||||||
ItemSize(total_bb, style.FramePadding.y);
|
|
||||||
if (!ItemAdd(total_bb, id, &frame_bb))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool hovered, held;
|
|
||||||
bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
|
|
||||||
bool popup_open = IsPopupOpen(id);
|
|
||||||
|
|
||||||
const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
|
|
||||||
const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
|
|
||||||
RenderNavHighlight(frame_bb, id);
|
|
||||||
if (!(flags & ImGuiComboFlags_NoPreview))
|
|
||||||
window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
|
|
||||||
if (!(flags & ImGuiComboFlags_NoArrowButton))
|
|
||||||
{
|
|
||||||
window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
|
|
||||||
RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
|
|
||||||
}
|
|
||||||
RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
|
|
||||||
if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
|
|
||||||
RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
|
|
||||||
if (label_size.x > 0)
|
|
||||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
|
||||||
|
|
||||||
if ((pressed || g.NavActivateId == id) && !popup_open)
|
|
||||||
{
|
|
||||||
if (window->DC.NavLayerCurrent == 0)
|
|
||||||
window->NavLastIds[0] = id;
|
|
||||||
OpenPopupEx(id);
|
|
||||||
popup_open = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!popup_open)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (backup_next_window_size_constraint)
|
|
||||||
{
|
|
||||||
g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
|
|
||||||
g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((flags & ImGuiComboFlags_HeightMask_) == 0)
|
|
||||||
flags |= ImGuiComboFlags_HeightRegular;
|
|
||||||
IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
|
|
||||||
int popup_max_height_in_items = -1;
|
|
||||||
if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
|
|
||||||
else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
|
|
||||||
else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
|
|
||||||
SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
|
|
||||||
}
|
|
||||||
|
|
||||||
char name[16];
|
|
||||||
ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
|
|
||||||
|
|
||||||
// Peak into expected window size so we can position it
|
|
||||||
if (ImGuiWindow* popup_window = FindWindowByName(name))
|
|
||||||
if (popup_window->WasActive)
|
|
||||||
{
|
|
||||||
ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
|
|
||||||
if (flags & ImGuiComboFlags_PopupAlignLeft)
|
|
||||||
popup_window->AutoPosLastDirection = ImGuiDir_Left;
|
|
||||||
ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
|
|
||||||
ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
|
|
||||||
SetNextWindowPos(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Horizontally align ourselves with the framed text
|
|
||||||
ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
|
|
||||||
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
|
|
||||||
bool ret = Begin(name, NULL, window_flags);
|
|
||||||
PopStyleVar();
|
|
||||||
if (!ret)
|
|
||||||
{
|
|
||||||
EndPopup();
|
|
||||||
IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImGui::EndCombo()
|
|
||||||
{
|
|
||||||
EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getter for the old Combo() API: const char*[]
|
|
||||||
static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
|
|
||||||
{
|
|
||||||
const char* const* items = (const char* const*)data;
|
|
||||||
if (out_text)
|
|
||||||
*out_text = items[idx];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getter for the old Combo() API: "item1\0item2\0item3\0"
|
|
||||||
static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
|
|
||||||
{
|
|
||||||
// FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
|
|
||||||
const char* items_separated_by_zeros = (const char*)data;
|
|
||||||
int items_count = 0;
|
|
||||||
const char* p = items_separated_by_zeros;
|
|
||||||
while (*p)
|
|
||||||
{
|
|
||||||
if (idx == items_count)
|
|
||||||
break;
|
|
||||||
p += strlen(p) + 1;
|
|
||||||
items_count++;
|
|
||||||
}
|
|
||||||
if (!*p)
|
|
||||||
return false;
|
|
||||||
if (out_text)
|
|
||||||
*out_text = p;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old API, prefer using BeginCombo() nowadays if you can.
|
|
||||||
bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
|
|
||||||
{
|
|
||||||
ImGuiContext& g = *GImGui;
|
|
||||||
|
|
||||||
// Call the getter to obtain the preview string which is a parameter to BeginCombo()
|
|
||||||
const char* preview_value = NULL;
|
|
||||||
if (*current_item >= 0 && *current_item < items_count)
|
|
||||||
items_getter(data, *current_item, &preview_value);
|
|
||||||
|
|
||||||
// The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
|
|
||||||
if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
|
|
||||||
SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
|
|
||||||
|
|
||||||
if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Display items
|
|
||||||
// FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
|
|
||||||
bool value_changed = false;
|
|
||||||
for (int i = 0; i < items_count; i++)
|
|
||||||
{
|
|
||||||
PushID((void*)(intptr_t)i);
|
|
||||||
const bool item_selected = (i == *current_item);
|
|
||||||
const char* item_text;
|
|
||||||
if (!items_getter(data, i, &item_text))
|
|
||||||
item_text = "*Unknown item*";
|
|
||||||
if (Selectable(item_text, item_selected))
|
|
||||||
{
|
|
||||||
value_changed = true;
|
|
||||||
*current_item = i;
|
|
||||||
}
|
|
||||||
if (item_selected)
|
|
||||||
SetItemDefaultFocus();
|
|
||||||
PopID();
|
|
||||||
}
|
|
||||||
|
|
||||||
EndCombo();
|
|
||||||
return value_changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combo box helper allowing to pass an array of strings.
|
|
||||||
bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
|
|
||||||
{
|
|
||||||
const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
|
|
||||||
return value_changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
|
|
||||||
bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
|
|
||||||
{
|
|
||||||
int items_count = 0;
|
|
||||||
const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
|
|
||||||
while (*p)
|
|
||||||
{
|
|
||||||
p += strlen(p) + 1;
|
|
||||||
items_count++;
|
|
||||||
}
|
|
||||||
bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
|
|
||||||
return value_changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
|
// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
|
||||||
// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
|
// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
|
||||||
bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
|
bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
|
||||||
@ -11042,108 +10835,6 @@ bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Rename to BeginListBox()
|
|
||||||
// Helper to calculate the size of a listbox and display a label on the right.
|
|
||||||
// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
|
|
||||||
bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
|
|
||||||
{
|
|
||||||
ImGuiWindow* window = GetCurrentWindow();
|
|
||||||
if (window->SkipItems)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const ImGuiStyle& style = GetStyle();
|
|
||||||
const ImGuiID id = GetID(label);
|
|
||||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
|
||||||
|
|
||||||
// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
|
|
||||||
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
|
|
||||||
ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
|
|
||||||
ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
|
|
||||||
ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
|
||||||
window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
|
|
||||||
|
|
||||||
BeginGroup();
|
|
||||||
if (label_size.x > 0)
|
|
||||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
|
||||||
|
|
||||||
BeginChildFrame(id, frame_bb.GetSize());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Rename to BeginListBox()
|
|
||||||
bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
|
|
||||||
{
|
|
||||||
// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
|
|
||||||
// We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
|
|
||||||
// I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
|
|
||||||
if (height_in_items < 0)
|
|
||||||
height_in_items = ImMin(items_count, 7);
|
|
||||||
float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
|
|
||||||
|
|
||||||
// We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
|
|
||||||
ImVec2 size;
|
|
||||||
size.x = 0.0f;
|
|
||||||
size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
|
|
||||||
return ListBoxHeader(label, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Rename to EndListBox()
|
|
||||||
void ImGui::ListBoxFooter()
|
|
||||||
{
|
|
||||||
ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
|
|
||||||
const ImRect bb = parent_window->DC.LastItemRect;
|
|
||||||
const ImGuiStyle& style = GetStyle();
|
|
||||||
|
|
||||||
EndChildFrame();
|
|
||||||
|
|
||||||
// Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
|
|
||||||
// We call SameLine() to restore DC.CurrentLine* data
|
|
||||||
SameLine();
|
|
||||||
parent_window->DC.CursorPos = bb.Min;
|
|
||||||
ItemSize(bb, style.FramePadding.y);
|
|
||||||
EndGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
|
|
||||||
{
|
|
||||||
const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
|
|
||||||
return value_changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
|
|
||||||
{
|
|
||||||
if (!ListBoxHeader(label, items_count, height_in_items))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
|
|
||||||
ImGuiContext& g = *GImGui;
|
|
||||||
bool value_changed = false;
|
|
||||||
ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
|
|
||||||
while (clipper.Step())
|
|
||||||
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
|
||||||
{
|
|
||||||
const bool item_selected = (i == *current_item);
|
|
||||||
const char* item_text;
|
|
||||||
if (!items_getter(data, i, &item_text))
|
|
||||||
item_text = "*Unknown item*";
|
|
||||||
|
|
||||||
PushID(i);
|
|
||||||
if (Selectable(item_text, item_selected))
|
|
||||||
{
|
|
||||||
*current_item = i;
|
|
||||||
value_changed = true;
|
|
||||||
}
|
|
||||||
if (item_selected)
|
|
||||||
SetItemDefaultFocus();
|
|
||||||
PopID();
|
|
||||||
}
|
|
||||||
ListBoxFooter();
|
|
||||||
if (value_changed)
|
|
||||||
MarkItemEdited(g.CurrentWindow->DC.LastItemId);
|
|
||||||
|
|
||||||
return value_changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
|
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
|
||||||
{
|
{
|
||||||
ImGuiWindow* window = GetCurrentWindow();
|
ImGuiWindow* window = GetCurrentWindow();
|
||||||
|
@ -892,6 +892,212 @@ void ImGui::Bullet()
|
|||||||
// - Combo()
|
// - Combo()
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static float CalcMaxPopupHeightFromItemCount(int items_count)
|
||||||
|
{
|
||||||
|
ImGuiContext& g = *GImGui;
|
||||||
|
if (items_count <= 0)
|
||||||
|
return FLT_MAX;
|
||||||
|
return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
|
||||||
|
{
|
||||||
|
// Always consume the SetNextWindowSizeConstraint() call in our early return paths
|
||||||
|
ImGuiContext& g = *GImGui;
|
||||||
|
ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
|
||||||
|
g.NextWindowData.SizeConstraintCond = 0;
|
||||||
|
|
||||||
|
ImGuiWindow* window = GetCurrentWindow();
|
||||||
|
if (window->SkipItems)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
|
||||||
|
|
||||||
|
const ImGuiStyle& style = g.Style;
|
||||||
|
const ImGuiID id = window->GetID(label);
|
||||||
|
|
||||||
|
const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
|
||||||
|
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||||
|
const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
|
||||||
|
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
|
||||||
|
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
||||||
|
ItemSize(total_bb, style.FramePadding.y);
|
||||||
|
if (!ItemAdd(total_bb, id, &frame_bb))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool hovered, held;
|
||||||
|
bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
|
||||||
|
bool popup_open = IsPopupOpen(id);
|
||||||
|
|
||||||
|
const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
|
||||||
|
const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
|
||||||
|
RenderNavHighlight(frame_bb, id);
|
||||||
|
if (!(flags & ImGuiComboFlags_NoPreview))
|
||||||
|
window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
|
||||||
|
if (!(flags & ImGuiComboFlags_NoArrowButton))
|
||||||
|
{
|
||||||
|
window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
|
||||||
|
RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
|
||||||
|
}
|
||||||
|
RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
|
||||||
|
if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
|
||||||
|
RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
|
||||||
|
if (label_size.x > 0)
|
||||||
|
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
||||||
|
|
||||||
|
if ((pressed || g.NavActivateId == id) && !popup_open)
|
||||||
|
{
|
||||||
|
if (window->DC.NavLayerCurrent == 0)
|
||||||
|
window->NavLastIds[0] = id;
|
||||||
|
OpenPopupEx(id);
|
||||||
|
popup_open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!popup_open)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (backup_next_window_size_constraint)
|
||||||
|
{
|
||||||
|
g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
|
||||||
|
g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((flags & ImGuiComboFlags_HeightMask_) == 0)
|
||||||
|
flags |= ImGuiComboFlags_HeightRegular;
|
||||||
|
IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
|
||||||
|
int popup_max_height_in_items = -1;
|
||||||
|
if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
|
||||||
|
else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
|
||||||
|
else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
|
||||||
|
SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
|
||||||
|
}
|
||||||
|
|
||||||
|
char name[16];
|
||||||
|
ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
|
||||||
|
|
||||||
|
// Peak into expected window size so we can position it
|
||||||
|
if (ImGuiWindow* popup_window = FindWindowByName(name))
|
||||||
|
if (popup_window->WasActive)
|
||||||
|
{
|
||||||
|
ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
|
||||||
|
if (flags & ImGuiComboFlags_PopupAlignLeft)
|
||||||
|
popup_window->AutoPosLastDirection = ImGuiDir_Left;
|
||||||
|
ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
|
||||||
|
ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
|
||||||
|
SetNextWindowPos(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontally align ourselves with the framed text
|
||||||
|
ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
|
||||||
|
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
|
||||||
|
bool ret = Begin(name, NULL, window_flags);
|
||||||
|
PopStyleVar();
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
EndPopup();
|
||||||
|
IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGui::EndCombo()
|
||||||
|
{
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter for the old Combo() API: const char*[]
|
||||||
|
static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
|
||||||
|
{
|
||||||
|
const char* const* items = (const char* const*)data;
|
||||||
|
if (out_text)
|
||||||
|
*out_text = items[idx];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter for the old Combo() API: "item1\0item2\0item3\0"
|
||||||
|
static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
|
||||||
|
{
|
||||||
|
// FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
|
||||||
|
const char* items_separated_by_zeros = (const char*)data;
|
||||||
|
int items_count = 0;
|
||||||
|
const char* p = items_separated_by_zeros;
|
||||||
|
while (*p)
|
||||||
|
{
|
||||||
|
if (idx == items_count)
|
||||||
|
break;
|
||||||
|
p += strlen(p) + 1;
|
||||||
|
items_count++;
|
||||||
|
}
|
||||||
|
if (!*p)
|
||||||
|
return false;
|
||||||
|
if (out_text)
|
||||||
|
*out_text = p;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old API, prefer using BeginCombo() nowadays if you can.
|
||||||
|
bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
|
||||||
|
{
|
||||||
|
ImGuiContext& g = *GImGui;
|
||||||
|
|
||||||
|
// Call the getter to obtain the preview string which is a parameter to BeginCombo()
|
||||||
|
const char* preview_value = NULL;
|
||||||
|
if (*current_item >= 0 && *current_item < items_count)
|
||||||
|
items_getter(data, *current_item, &preview_value);
|
||||||
|
|
||||||
|
// The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
|
||||||
|
if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
|
||||||
|
SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
|
||||||
|
|
||||||
|
if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Display items
|
||||||
|
// FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
|
||||||
|
bool value_changed = false;
|
||||||
|
for (int i = 0; i < items_count; i++)
|
||||||
|
{
|
||||||
|
PushID((void*)(intptr_t)i);
|
||||||
|
const bool item_selected = (i == *current_item);
|
||||||
|
const char* item_text;
|
||||||
|
if (!items_getter(data, i, &item_text))
|
||||||
|
item_text = "*Unknown item*";
|
||||||
|
if (Selectable(item_text, item_selected))
|
||||||
|
{
|
||||||
|
value_changed = true;
|
||||||
|
*current_item = i;
|
||||||
|
}
|
||||||
|
if (item_selected)
|
||||||
|
SetItemDefaultFocus();
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndCombo();
|
||||||
|
return value_changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combo box helper allowing to pass an array of strings.
|
||||||
|
bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
|
||||||
|
{
|
||||||
|
const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
|
||||||
|
return value_changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
|
||||||
|
bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
|
||||||
|
{
|
||||||
|
int items_count = 0;
|
||||||
|
const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
|
||||||
|
while (*p)
|
||||||
|
{
|
||||||
|
p += strlen(p) + 1;
|
||||||
|
items_count++;
|
||||||
|
}
|
||||||
|
bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
|
||||||
|
return value_changed;
|
||||||
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// WIDGETS: Data Type and Data Formatting Helpers [Internal]
|
// WIDGETS: Data Type and Data Formatting Helpers [Internal]
|
||||||
@ -1048,6 +1254,107 @@ void ImGui::Bullet()
|
|||||||
// - ListBoxFooter()
|
// - ListBoxFooter()
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// FIXME: Rename to BeginListBox()
|
||||||
|
// Helper to calculate the size of a listbox and display a label on the right.
|
||||||
|
// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
|
||||||
|
bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
|
||||||
|
{
|
||||||
|
ImGuiWindow* window = GetCurrentWindow();
|
||||||
|
if (window->SkipItems)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const ImGuiStyle& style = GetStyle();
|
||||||
|
const ImGuiID id = GetID(label);
|
||||||
|
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||||
|
|
||||||
|
// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
|
||||||
|
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
|
||||||
|
ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
|
||||||
|
ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
|
||||||
|
ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
||||||
|
window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
|
||||||
|
|
||||||
|
BeginGroup();
|
||||||
|
if (label_size.x > 0)
|
||||||
|
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
||||||
|
|
||||||
|
BeginChildFrame(id, frame_bb.GetSize());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Rename to BeginListBox()
|
||||||
|
bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
|
||||||
|
{
|
||||||
|
// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
|
||||||
|
// We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
|
||||||
|
// I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
|
||||||
|
if (height_in_items < 0)
|
||||||
|
height_in_items = ImMin(items_count, 7);
|
||||||
|
float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
|
||||||
|
|
||||||
|
// We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
|
||||||
|
ImVec2 size;
|
||||||
|
size.x = 0.0f;
|
||||||
|
size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
|
||||||
|
return ListBoxHeader(label, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Rename to EndListBox()
|
||||||
|
void ImGui::ListBoxFooter()
|
||||||
|
{
|
||||||
|
ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
|
||||||
|
const ImRect bb = parent_window->DC.LastItemRect;
|
||||||
|
const ImGuiStyle& style = GetStyle();
|
||||||
|
|
||||||
|
EndChildFrame();
|
||||||
|
|
||||||
|
// Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
|
||||||
|
// We call SameLine() to restore DC.CurrentLine* data
|
||||||
|
SameLine();
|
||||||
|
parent_window->DC.CursorPos = bb.Min;
|
||||||
|
ItemSize(bb, style.FramePadding.y);
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
|
||||||
|
{
|
||||||
|
const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
|
||||||
|
return value_changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
|
||||||
|
{
|
||||||
|
if (!ListBoxHeader(label, items_count, height_in_items))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
|
||||||
|
ImGuiContext& g = *GImGui;
|
||||||
|
bool value_changed = false;
|
||||||
|
ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
|
||||||
|
while (clipper.Step())
|
||||||
|
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
||||||
|
{
|
||||||
|
const bool item_selected = (i == *current_item);
|
||||||
|
const char* item_text;
|
||||||
|
if (!items_getter(data, i, &item_text))
|
||||||
|
item_text = "*Unknown item*";
|
||||||
|
|
||||||
|
PushID(i);
|
||||||
|
if (Selectable(item_text, item_selected))
|
||||||
|
{
|
||||||
|
*current_item = i;
|
||||||
|
value_changed = true;
|
||||||
|
}
|
||||||
|
if (item_selected)
|
||||||
|
SetItemDefaultFocus();
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
ListBoxFooter();
|
||||||
|
if (value_changed)
|
||||||
|
MarkItemEdited(g.CurrentWindow->DC.LastItemId);
|
||||||
|
|
||||||
|
return value_changed;
|
||||||
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// WIDGETS: Data Plotting
|
// WIDGETS: Data Plotting
|
||||||
|
Loading…
Reference in New Issue
Block a user