mirror of
				https://github.com/Drezil/imgui.git
				synced 2025-10-30 20:51:06 +01:00 
			
		
		
		
	Refactor: Moved Combo/ListBox functions from imgui.cpp to imgui_widgets.cpp (#2036)
This commit is contained in:
		
							
								
								
									
										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); | ||||
| } | ||||
|  | ||||
| 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. | ||||
| // 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) | ||||
| @@ -11042,108 +10835,6 @@ bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags | ||||
|     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) | ||||
| { | ||||
|     ImGuiWindow* window = GetCurrentWindow(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user