diff --git a/imgui.cpp b/imgui.cpp index 338248a31..dbf8e691e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -270,7 +270,8 @@ - columns: user specify columns size - combo: turn child handling code into pop up helper - combo: contents should extends to fit label if combo widget is small - - list selection, concept of a selectable "block" (that can be multiple widgets) + - listbox: multiple selection + - listbox: user may want to initial scroll to focus on the one selected value? ! menubar, menus - tabs - gauge: various forms of gauge/loading bars widgets @@ -5796,6 +5797,85 @@ bool ImGui::Selectable(const char* label, bool* p_selected, const ImVec2& size_a return false; } +// 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 = ImGui::GetStyle(); + const ImGuiID id = ImGui::GetID(label); + const ImVec2 label_size = ImGui::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; + size.x = (size_arg.x != 0.0f) ? size_arg.x : ImGui::CalcItemWidth() + style.FramePadding.x * 2.0f; + size.y = (size_arg.y != 0.0f) ? size_arg.y : ImGui::GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y; + const ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + const ImGuiAabb frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + ImGui::BeginChildFrame(id, frame_bb.GetSize()); + return true; +} + +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. + // However 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 = ImGui::GetTextLineHeightWithSpacing() * height_in_items_f + ImGui::GetStyle().ItemSpacing.y; + return ImGui::ListBoxHeader(label, size); +} + +void ImGui::ListBoxFooter() +{ + ImGui::EndChildFrame(); +} + +bool ImGui::ListBox(const char* label, int* current_item, const char** 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 (!ImGui::ListBoxHeader(label, items_count, height_in_items)) + return false; + + bool value_changed = false; + for (int i = 0; i < items_count; i++) + { + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + + ImGui::PushID(i); + if (ImGui::Selectable(item_text, item_selected)) + { + *current_item = i; + value_changed = true; + } + ImGui::PopID(); + } + + ImGui::ListBoxFooter(); + return value_changed; +} + // A little colored square. Return true when clicked. bool ImGui::ColorButton(const ImVec4& col, bool small_height, bool outline_border) { @@ -8306,6 +8386,14 @@ void ImGui::ShowTestWindow(bool* opened) static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; ImGui::ColorEdit3("color 1", col1); ImGui::ColorEdit4("color 2", col2); + + const char* listbox_items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; + static int listbox_item_current = 1, listbox_item_current2 = 2; + ImGui::ListBox("listbox\n(single select)", &listbox_item_current, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + + //ImGui::PushItemWidth(-1); + //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + //ImGui::PopItemWidth(); } if (ImGui::CollapsingHeader("Graphs widgets")) diff --git a/imgui.h b/imgui.h index 56cd592a8..3842a60cd 100644 --- a/imgui.h +++ b/imgui.h @@ -284,8 +284,6 @@ namespace ImGui IMGUI_API bool Combo(const char* label, int* current_item, const char** items, int items_count, int height_in_items = -1); IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items = -1); // separate items with \0, end item-list with \0\0 IMGUI_API bool Combo(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1); - IMGUI_API bool Selectable(const char* label, bool selected, const ImVec2& size = ImVec2(0,0)); - IMGUI_API bool Selectable(const char* label, bool* p_selected, const ImVec2& size = ImVec2(0,0)); IMGUI_API bool ColorButton(const ImVec4& col, bool small_height = false, bool outline_border = true); IMGUI_API bool ColorEdit3(const char* label, float col[3]); IMGUI_API bool ColorEdit4(const char* label, float col[4], bool show_alpha = true); @@ -300,6 +298,15 @@ namespace ImGui IMGUI_API void TreePop(); IMGUI_API void OpenNextNode(bool open); // force open/close the next TreeNode or CollapsingHeader + // Selectable / Lists + IMGUI_API bool Selectable(const char* label, bool selected, const ImVec2& size = ImVec2(0,0)); + IMGUI_API bool Selectable(const char* label, bool* p_selected, const ImVec2& size = ImVec2(0,0)); + IMGUI_API bool ListBox(const char* label, int* current_item, const char** items, int items_count, int height_in_items = -1); + IMGUI_API bool ListBox(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1); + IMGUI_API bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0,0)); // use if you want to reimplement ListBox() will custom data or interactions. make sure to call ListBoxFooter() afterwards. + IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // " + IMGUI_API void ListBoxFooter(); // terminate the scrolling region + // Value() Helpers: output single value in "name: value" format. Tip: freely declare your own within the ImGui namespace! IMGUI_API void Value(const char* prefix, bool b); IMGUI_API void Value(const char* prefix, int v);