#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif #include "imgui.h" #ifndef IMGUI_DISABLE #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif #include "imgui_internal.h" #include #if defined(_MSC_VER) && _MSC_VER <= 1500 #include #else #include #endif #ifdef _MSC_VER #pragma warning (disable: 4127) #pragma warning (disable: 4996) #if defined(_MSC_VER) && _MSC_VER >= 1922 #pragma warning (disable: 5054) #endif #endif #if defined(__clang__) #if __has_warning("-Wunknown-warning-option") #pragma clang diagnostic ignored "-Wunknown-warning-option" #endif #pragma clang diagnostic ignored "-Wunknown-pragmas" #pragma clang diagnostic ignored "-Wold-style-cast" #pragma clang diagnostic ignored "-Wfloat-equal" #pragma clang diagnostic ignored "-Wformat-nonliteral" #pragma clang diagnostic ignored "-Wsign-conversion" #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" #pragma clang diagnostic ignored "-Wdouble-promotion" #pragma clang diagnostic ignored "-Wenum-enum-conversion" #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion" #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" #pragma GCC diagnostic ignored "-Wformat-nonliteral" #pragma GCC diagnostic ignored "-Wclass-memaccess" #endif static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; static const float DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f; static const signed char IM_S8_MIN = -128; static const signed char IM_S8_MAX = 127; static const unsigned char IM_U8_MIN = 0; static const unsigned char IM_U8_MAX = 0xFF; static const signed short IM_S16_MIN = -32768; static const signed short IM_S16_MAX = 32767; static const unsigned short IM_U16_MIN = 0; static const unsigned short IM_U16_MAX = 0xFFFF; static const ImS32 IM_S32_MIN = INT_MIN; static const ImS32 IM_S32_MAX = INT_MAX; static const ImU32 IM_U32_MIN = 0; static const ImU32 IM_U32_MAX = UINT_MAX; #ifdef LLONG_MIN static const ImS64 IM_S64_MIN = LLONG_MIN; static const ImS64 IM_S64_MAX = LLONG_MAX; #else static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1; static const ImS64 IM_S64_MAX = 9223372036854775807LL; #endif static const ImU64 IM_U64_MIN = 0; #ifdef ULLONG_MAX static const ImU64 IM_U64_MAX = ULLONG_MAX; #else static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); #endif static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data); static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); const ImU32 colWhite = 0xFFC1C1C1; const ImU32 colBlack = 0xFF585858; const ImU32 colWhiteText = 0xFFD9D9D9; void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; IM_ASSERT(text != NULL); const char* text_begin = text; if (text_end == NULL) text_end = text + strlen(text); const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); const float wrap_pos_x = window->DC.TextWrapPos; const bool wrap_enabled = (wrap_pos_x >= 0.0f); if (text_end - text > 2000 && !wrap_enabled) { const char* line = text; float line_height = GetTextLineHeight(); ImVec2 text_size(0, 0); ImVec2 pos = text_pos; if (!g.LogEnabled) { int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height); if (lines_skippable > 0) { int lines_skipped = 0; while (line < text_end && lines_skipped < lines_skippable) { const char* line_end = (const char*)memchr(line, '\n', text_end - line); if (!line_end) line_end = text_end; if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); line = line_end + 1; lines_skipped++; } pos.y += lines_skipped * line_height; } } if (line < text_end) { ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); while (line < text_end) { if (IsClippedEx(line_rect, 0, false)) break; const char* line_end = (const char*)memchr(line, '\n', text_end - line); if (!line_end) line_end = text_end; text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); RenderText(pos, line, line_end, false); line = line_end + 1; line_rect.Min.y += line_height; line_rect.Max.y += line_height; pos.y += line_height; } int lines_skipped = 0; while (line < text_end) { const char* line_end = (const char*)memchr(line, '\n', text_end - line); if (!line_end) line_end = text_end; if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); line = line_end + 1; lines_skipped++; } pos.y += lines_skipped * line_height; } text_size.y = (pos - text_pos).y; ImRect bb(text_pos, text_pos + text_size); ItemSize(text_size, 0.0f); ItemAdd(bb, 0); } else { float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); ImRect bb(text_pos, text_pos + text_size); ItemSize(text_size, 0.0f); if (!ItemAdd(bb, 0)) return; RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); } } void ImGui::TextUnformatted(const char* text, const char* text_end) { TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); } void ImGui::Text(const char* fmt, ...) { va_list args; va_start(args, fmt); TextV(fmt, args); va_end(args); } void ImGui::TextV(const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); } void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) { va_list args; va_start(args, fmt); TextColoredV(col, fmt, args); va_end(args); } void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) { PushStyleColor(ImGuiCol_Text, col); if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); else TextV(fmt, args); PopStyleColor(); } void ImGui::TextDisabled(const char* fmt, ...) { va_list args; va_start(args, fmt); TextDisabledV(fmt, args); va_end(args); } void ImGui::TextDisabledV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); else TextV(fmt, args); PopStyleColor(); } void ImGui::TextWrapped(const char* fmt, ...) { va_list args; va_start(args, fmt); TextWrappedV(fmt, args); va_end(args); } void ImGui::TextWrappedV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); if (need_backup) PushTextWrapPos(0.0f); if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); else TextV(fmt, args); if (need_backup) PopTextWrapPos(); } void ImGui::LabelText(const char* label, const char* fmt, ...) { va_list args; va_start(args, fmt); LabelTextV(label, fmt, args); va_end(args); } void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const float w = CalcItemWidth(); const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2)); const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y * 2) + label_size); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, 0)) return; const char* value_text_begin = &g.TempBuffer[0]; const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f, 0.5f)); if (label_size.x > 0.0f) RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); } void ImGui::BulletText(const char* fmt, ...) { va_list args; va_start(args, fmt); BulletTextV(fmt, args); va_end(args); } void ImGui::BulletTextV(const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const char* text_begin = g.TempBuffer; const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); ImVec2 pos = window->DC.CursorPos; pos.y += window->DC.CurrLineTextBaseOffset; ItemSize(total_size, 0.0f); const ImRect bb(pos, pos + total_size); if (!ItemAdd(bb, 0)) return; ImU32 text_col = GetColorU32(ImGuiCol_Text); RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col); RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false); } bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (flags & ImGuiButtonFlags_Disabled) { if (out_hovered) *out_hovered = false; if (out_held) *out_held = false; if (g.ActiveId == id) ClearActiveID(); return false; } if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) flags |= ImGuiButtonFlags_MouseButtonDefault_; if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) flags |= ImGuiButtonFlags_PressedOnDefault_; ImGuiWindow* backup_hovered_window = g.HoveredWindow; const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window; if (flatten_hovered_children) g.HoveredWindow = window; #ifdef IMGUI_ENABLE_TEST_ENGINE if (id != 0 && window->DC.LastItemId != id) IMGUI_TEST_ENGINE_ITEM_ADD(bb, id); #endif bool pressed = false; bool hovered = ItemHoverable(bb, id); if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover)) hovered = false; if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { hovered = true; SetHoveredID(id); if (CalcTypematicRepeatAmount(g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, g.HoveredIdTimer + 0.0001f, DRAGDROP_HOLD_TO_OPEN_TIMER, 0.00f)) { pressed = true; g.DragDropHoldJustPressedId = id; FocusWindow(window); } } if (flatten_hovered_children) g.HoveredWindow = backup_hovered_window; if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) hovered = false; if (hovered) { if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) { int mouse_button_clicked = -1; int mouse_button_released = -1; if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseClicked[0]) { mouse_button_clicked = 0; } else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseClicked[1]) { mouse_button_clicked = 1; } else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseClicked[2]) { mouse_button_clicked = 2; } if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseReleased[0]) { mouse_button_released = 0; } else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseReleased[1]) { mouse_button_released = 1; } else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseReleased[2]) { mouse_button_released = 2; } if (mouse_button_clicked != -1 && g.ActiveId != id) { if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere)) { SetActiveID(id, window); g.ActiveIdMouseButton = mouse_button_clicked; if (!(flags & ImGuiButtonFlags_NoNavFocus)) SetFocusID(id, window); FocusWindow(window); } if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[mouse_button_clicked])) { pressed = true; if (flags & ImGuiButtonFlags_NoHoldingActiveId) ClearActiveID(); else SetActiveID(id, window); g.ActiveIdMouseButton = mouse_button_clicked; FocusWindow(window); } } if ((flags & ImGuiButtonFlags_PressedOnRelease) && mouse_button_released != -1) { const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; if (!has_repeated_at_least_once) pressed = true; ClearActiveID(); } if (g.ActiveId == id && (flags & ImGuiButtonFlags_Repeat)) if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, true)) pressed = true; } if (pressed) g.NavDisableHighlight = true; } if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) hovered = true; if (g.NavActivateDownId == id) { bool nav_activated_by_code = (g.NavActivateId == id); bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed); if (nav_activated_by_code || nav_activated_by_inputs) pressed = true; if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id) { g.NavActivateId = id; SetActiveID(id, window); if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus)) SetFocusID(id, window); } } bool held = false; if (g.ActiveId == id) { if (g.ActiveIdSource == ImGuiInputSource_Mouse) { if (g.ActiveIdIsJustActivated) g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; const int mouse_button = g.ActiveIdMouseButton; IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); if (g.IO.MouseDown[mouse_button]) { held = true; } else { bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0; bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0; if ((release_in || release_anywhere) && !g.DragDropActive) { bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDownWasDoubleClick[mouse_button]; bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; if (!is_double_click_release && !is_repeating_already) pressed = true; } ClearActiveID(); } if (!(flags & ImGuiButtonFlags_NoNavFocus)) g.NavDisableHighlight = true; } else if (g.ActiveIdSource == ImGuiInputSource_Nav) { if (g.NavActivateDownId != id) ClearActiveID(); } if (pressed) g.ActiveIdHasBeenPressedBefore = true; } if (out_hovered) *out_hovered = hovered; if (out_held) *out_held = held; return pressed; } bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); ImVec2 pos = window->DC.CursorPos; if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y; ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); ImRect bb(pos, pos + size); ItemSize(size, style.FramePadding.y); if (!ItemAdd(bb, id)) return false; if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); if (!held) { RenderFrame(bb.Min - ImVec2(1, 1), bb.Max - ImVec2(0, 0), colWhite, true, style.FrameRounding); RenderFrame(bb.Min + ImVec2(0, 0), bb.Max + ImVec2(1, 1), colBlack, true, style.FrameRounding); } else { RenderFrame(bb.Min - ImVec2(1, 1), bb.Max - ImVec2(0, 0), colBlack, true, style.FrameRounding); RenderFrame(bb.Min + ImVec2(0, 0), bb.Max + ImVec2(1, 1), colWhite, true, style.FrameRounding); } const ImU32 col = GetColorU32(ImGuiCol_Button); RenderNavHighlight(bb, id); RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); if (g.LogEnabled) LogSetNextTextDecoration("[", "]"); if (!held) { RenderTextClipped(bb.Min + style.FramePadding - ImVec2(1,1), bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); } else { RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding + ImVec2(2.0f, 2.0f), label, NULL, &label_size, style.ButtonTextAlign, &bb); } IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); ImVec2 newpos = window->DC.CursorPos; window->DC.CursorPos = newpos + ImVec2(0, 8.0f); return pressed; } bool ImGui::Button(const char* label, const ImVec2& size_arg) { return ButtonEx(label, size_arg, ImGuiButtonFlags_None); } bool ImGui::SmallButton(const char* label) { ImGuiContext& g = *GImGui; float backup_padding_y = g.Style.FramePadding.y; g.Style.FramePadding.y = 0.0f; bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine); g.Style.FramePadding.y = backup_padding_y; return pressed; } bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f); const ImGuiID id = window->GetID(str_id); ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(size); if (!ItemAdd(bb, id)) return false; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); return pressed; } bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiID id = window->GetID(str_id); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); const float default_size = GetFrameHeight(); ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); if (!ItemAdd(bb, id)) return false; if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); ImU32 bg_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); ImU32 text_col = GetColorU32(ImGuiCol_Text); RenderNavHighlight(bb, id); RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir); window->DrawList->AddLine(ImVec2(bb.Min.x - 1.0f, bb.Min.y), ImVec2(bb.Max.x - 1.0f, bb.Min.y), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(bb.Min.x - 1.0f, bb.Min.y), ImVec2(bb.Min.x - 1.0f, bb.Max.y), 0xFFA2A2A2, 1.0f); window->DrawList->AddLine(ImVec2(bb.Max.x - 1.0f, bb.Min.y), ImVec2(bb.Max.x - 1.0f, bb.Max.y), colWhite, 1.0f); window->DrawList->AddLine(ImVec2(bb.Min.x - 1.0f, bb.Max.y), ImVec2(bb.Max.x - 1.0f, bb.Max.y), colWhite, 1.0f); return pressed; } bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) { float sz = GetFrameHeight(); return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None); } bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f); bool is_clipped = !ItemAdd(bb, id); bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held); if (is_clipped) return pressed; ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); ImVec2 center = bb.GetCenter(); if (hovered) window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12); float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; ImU32 cross_col = GetColorU32(ImGuiCol_Text); center -= ImVec2(0.5f, 0.5f); window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); return pressed; } bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f); ItemAdd(bb, id); bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); ImU32 text_col = GetColorU32(ImGuiCol_Text); ImVec2 center = bb.GetCenter(); if (hovered || held) window->DrawList->AddCircleFilled(center , g.FontSize * 0.5f + 1.0f, bg_col, 12); RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); if (IsItemActive() && IsMouseDragging(0)) StartMouseMovingWindow(window); return pressed; } ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis) { return window->GetIDNoKeepAlive(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY"); } ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) { const ImRect outer_rect = window->Rect(); const ImRect inner_rect = window->InnerRect; const float border_size = window->WindowBorderSize; const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; IM_ASSERT(scrollbar_size > 0.0f); if (axis == ImGuiAxis_X) return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x, outer_rect.Max.y); else return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x, inner_rect.Max.y); } void ImGui::Scrollbar(ImGuiAxis axis) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; const ImGuiID id = GetWindowScrollbarID(window, axis); KeepAliveID(id); ImRect bb = GetWindowScrollbarRect(window, axis); ImDrawCornerFlags rounding_corners = 0; if (axis == ImGuiAxis_X) { rounding_corners |= ImDrawCornerFlags_BotLeft; if (!window->ScrollbarY) rounding_corners |= ImDrawCornerFlags_BotRight; } else { if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) rounding_corners |= ImDrawCornerFlags_TopRight; if (!window->ScrollbarX) rounding_corners |= ImDrawCornerFlags_BotRight; } float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; ScrollbarEx(bb, id, axis, &window->Scroll[axis], size_avail, size_contents, rounding_corners); } bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float size_avail_v, float size_contents_v, ImDrawCornerFlags rounding_corners) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; const float bb_frame_width = bb_frame.GetWidth(); const float bb_frame_height = bb_frame.GetHeight(); if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f) return false; float alpha = 1.0f; if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f) alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f)); if (alpha <= 0.0f) return false; const ImGuiStyle& style = g.Style; const bool allow_interaction = (alpha >= 1.0f); ImRect bb = bb_frame; bb.Expand(ImVec2(-ImClamp(IM_FLOOR((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_FLOOR((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f))); const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight(); IM_ASSERT(ImMax(size_contents_v, size_avail_v) > 0.0f); const float win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), 1.0f); const float grab_h_pixels = ImClamp(scrollbar_size_v * (size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v); const float grab_h_norm = grab_h_pixels / scrollbar_size_v; bool held = false; bool hovered = false; ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); float scroll_max = ImMax(1.0f, size_contents_v - size_avail_v); float scroll_ratio = ImSaturate(*p_scroll_v / scroll_max); float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; if (held && allow_interaction && grab_h_norm < 1.0f) { float scrollbar_pos_v = bb.Min[axis]; float mouse_pos_v = g.IO.MousePos[axis]; const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); SetHoveredID(id); bool seek_absolute = false; if (g.ActiveIdIsJustActivated) { seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm); if (seek_absolute) g.ScrollbarClickDeltaToGrabCenter = 0.0f; else g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; } const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm)); *p_scroll_v = IM_ROUND(scroll_v_norm * scroll_max); scroll_ratio = ImSaturate(*p_scroll_v / scroll_max); grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; if (seek_absolute) g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; } const ImU32 grab_col = GetColorU32(ImGuiCol_ScrollbarGrabActive) ; const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg); window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, rounding_corners); ImRect grab_rect; if (axis == ImGuiAxis_X) grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y); else grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels); RenderFrame(grab_rect.Min - ImVec2(1, 1), grab_rect.Max - ImVec2(0, 0), colWhite, true, style.FrameRounding); RenderFrame(grab_rect.Min + ImVec2(0, 0), grab_rect.Max + ImVec2(1, 1), colBlack, true, style.FrameRounding); window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); return held; } void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); if (border_col.w > 0.0f) bb.Max += ImVec2(2, 2); ItemSize(bb); if (!ItemAdd(bb, 0)) return; if (border_col.w > 0.0f) { window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col)); } else { window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); } } bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); ItemSize(bb); if (!ItemAdd(bb, id)) return false; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held); const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); RenderNavHighlight(bb, id); RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); return pressed; } bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; PushID((void*)(intptr_t)user_texture_id); const ImGuiID id = window->GetID("#image"); PopID(); const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding; return ImageButtonEx(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col); } bool ImGui::Checkbox(const char* label, bool* v) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); total_bb.Max.y += 10.0f; ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id)) { IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); return false; } bool hovered, held; bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); if (pressed) { *v = !(*v); MarkItemEdited(id); } ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); RenderNavHighlight(total_bb, id); check_bb.Max.x -= 10.0f; check_bb.Max.y -= 10.0f; RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); window->DrawList->AddLine(ImVec2(check_bb.Min.x - 1.0f, check_bb.Min.y), ImVec2(check_bb.Max.x - 1.0f, check_bb.Min.y ), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(check_bb.Min.x - 1.0f, check_bb.Min.y), ImVec2(check_bb.Min.x - 1.0f, check_bb.Max.y), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(check_bb.Max.x - 1.0f, check_bb.Min.y), ImVec2(check_bb.Max.x - 1.0f, check_bb.Max.y), colWhite, 1.0f); window->DrawList->AddLine(ImVec2(check_bb.Min.x - 1.0f, check_bb.Max.y), ImVec2(check_bb.Max.x - 1.0f, check_bb.Max.y), colWhite, 1.0f); ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); bool mixed_value = (window->DC.ItemFlags & ImGuiItemFlags_MixedValue) != 0; if (mixed_value) { ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f))); window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); } else if (*v) { const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad) - ImVec2(2.0f,1.0f), check_col, square_sz - pad * 2.0f - 7.0f); } ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); label_pos.y -= 5.0f; label_pos.x += 3.0f; if (g.LogEnabled) LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); if (label_size.x > 0.0f) { if (*v) RenderText(label_pos, label, 0, 1); else RenderText(label_pos, label, 0, 1, colWhiteText); } IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); return pressed; } template bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) { bool all_on = (*flags & flags_value) == flags_value; bool any_on = (*flags & flags_value) != 0; bool pressed; if (!all_on && any_on) { ImGuiWindow* window = GetCurrentWindow(); ImGuiItemFlags backup_item_flags = window->DC.ItemFlags; window->DC.ItemFlags |= ImGuiItemFlags_MixedValue; pressed = Checkbox(label, &all_on); window->DC.ItemFlags = backup_item_flags; } else { pressed = Checkbox(label, &all_on); } if (pressed) { if (all_on) *flags |= flags_value; else *flags &= ~flags_value; } return pressed; } bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value) { return CheckboxFlagsT(label, flags, flags_value); } bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) { return CheckboxFlagsT(label, flags, flags_value); } bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value) { return CheckboxFlagsT(label, flags, flags_value); } bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value) { return CheckboxFlagsT(label, flags, flags_value); } bool ImGui::RadioButton(const char* label, bool active) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id)) return false; ImVec2 center = check_bb.GetCenter(); center.x = IM_ROUND(center.x); center.y = IM_ROUND(center.y); const float radius = (square_sz - 1.0f) * 0.5f; bool hovered, held; bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); if (pressed) MarkItemEdited(id); RenderNavHighlight(total_bb, id); window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); if (active) { const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16); } if (style.FrameBorderSize > 0.0f) { window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); } ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); if (g.LogEnabled) LogRenderedText(&label_pos, active ? "(x)" : "( )"); if (label_size.x > 0.0f) RenderText(label_pos, label); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); return pressed; } bool ImGui::RadioButton(const char* label, int* v, int v_button) { const bool pressed = RadioButton(label, *v == v_button); if (pressed) *v = v_button; return pressed; } void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; ImVec2 pos = window->DC.CursorPos; ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f); ImRect bb(pos, pos + size); ItemSize(size, style.FramePadding.y); if (!ItemAdd(bb, 0)) return; fraction = ImSaturate(fraction); RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); char overlay_buf[32]; if (!overlay) { ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f); overlay = overlay_buf; } ImVec2 overlay_size = CalcTextSize(overlay, NULL); if (overlay_size.x > 0.0f) RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb); } void ImGui::Bullet() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); ItemSize(bb); if (!ItemAdd(bb, 0)) { SameLine(0, style.FramePadding.x * 2); return; } ImU32 text_col = GetColorU32(ImGuiCol_Text); RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col); SameLine(0, style.FramePadding.x * 2.0f); } void ImGui::Spacing() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ItemSize(ImVec2(0, 0)); } void ImGui::Dummy(const ImVec2& size) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(size); ItemAdd(bb, 0); } void ImGui::NewLine() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; window->DC.LayoutType = ImGuiLayoutType_Vertical; if (window->DC.CurrLineSize.y > 0.0f) ItemSize(ImVec2(0, 0)); else ItemSize(ImVec2(0.0f, g.FontSize)); window->DC.LayoutType = backup_layout_type; } void ImGui::AlignTextToFramePadding() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2); window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y); } void ImGui::SeparatorEx(ImGuiSeparatorFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); float thickness_draw = 1.0f; float thickness_layout = 0.0f; if (flags & ImGuiSeparatorFlags_Vertical) { float y1 = window->DC.CursorPos.y; float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y; const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness_draw, y2)); ItemSize(ImVec2(thickness_layout, 0.0f)); if (!ItemAdd(bb, 0)) return; window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); if (g.LogEnabled) LogText(" |"); } else if (flags & ImGuiSeparatorFlags_Horizontal) { float x1 = window->Pos.x; float x2 = window->Pos.x + window->Size.x; if (g.GroupStack.Size > 0 && g.GroupStack.back().WindowID == window->ID) x1 += window->DC.Indent.x; ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL; if (columns) PushColumnsBackground(); const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness_draw)); ItemSize(ImVec2(0.0f, thickness_layout)); const bool item_visible = ItemAdd(bb, 0); if (item_visible) { window->DrawList->AddLine(ImVec2(bb.Min.x + 19.0f, bb.Min.y), ImVec2(bb.Max.x - 74.0f, bb.Min.y), colBlack); if (g.LogEnabled) LogRenderedText(&bb.Min, "--------------------------------\n"); } if (columns) { PopColumnsBackground(); columns->LineMinY = window->DC.CursorPos.y; } } } void ImGui::Separator() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; flags |= ImGuiSeparatorFlags_SpanAllColumns; SeparatorEx(flags); } bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; bool item_add = ItemAdd(bb, id); window->DC.ItemFlags = item_flags_backup; if (!item_add) return false; bool hovered, held; ImRect bb_interact = bb; bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); if (g.ActiveId != id) SetItemAllowOverlap(); if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); ImRect bb_render = bb; if (held) { ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min; float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x; float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1); float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2); if (mouse_delta < -size_1_maximum_delta) mouse_delta = -size_1_maximum_delta; if (mouse_delta > size_2_maximum_delta) mouse_delta = size_2_maximum_delta; if (mouse_delta != 0.0f) { if (mouse_delta < 0.0f) IM_ASSERT(*size1 + mouse_delta >= min_size1); if (mouse_delta > 0.0f) IM_ASSERT(*size2 - mouse_delta >= min_size2); *size1 += mouse_delta; *size2 -= mouse_delta; bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); MarkItemEdited(id); } } const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f); return held; } static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) { const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs; const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs; if (int d = (int)(b->Width - a->Width)) return d; return (b->Index - a->Index); } void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess) { if (count == 1) { if (items[0].Width >= 0.0f) items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); return; } ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); int count_same_width = 1; while (width_excess > 0.0f && count_same_width < count) { while (count_same_width < count && items[0].Width <= items[count_same_width].Width) count_same_width++; float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); if (max_width_to_remove_per_item <= 0.0f) break; float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item); for (int item_n = 0; item_n < count_same_width; item_n++) items[item_n].Width -= width_to_remove_per_item; width_excess -= width_to_remove_per_item * count_same_width; } width_excess = 0.0f; for (int n = 0; n < count; n++) { float width_rounded = ImFloor(items[n].Width); width_excess += items[n].Width - width_rounded; items[n].Width = width_rounded; } if (width_excess > 0.0f) for (int n = 0; n < count; n++) if (items[n].Index < (int)(width_excess + 0.01f)) items[n].Width += 1.0f; } 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) { ImGuiContext& g = *GImGui; bool has_window_size_constraint = (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) != 0; g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); 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 expected_w = CalcItemWidth(); const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w; ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); ImRect total_bb(frame_bb.Min - ImVec2(0.0f,10.0f), frame_bb.Max + ImVec2(style.ItemInnerSpacing.x,-10.0f)); total_bb.Max.y += 10.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, ImGuiPopupFlags_None); const ImU32 frame_col = GetColorU32(ImGuiCol_FrameBg); const float value_x2 = ImMax(frame_bb.Min.x, frame_bb.Max.x - arrow_size); RenderNavHighlight(frame_bb, id); if (!(flags & ImGuiComboFlags_NoPreview)) window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(value_x2, frame_bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Left); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Min.y), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Min.y), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Min.y), ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Max.y), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Min.y), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Max.y), colWhite, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Max.y), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Max.y), colWhite, 1.0f); if (!(flags & ImGuiComboFlags_NoArrowButton)) { ImU32 bg_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); ImU32 text_col = GetColorU32(ImGuiCol_Text); window->DrawList->AddRectFilled(ImVec2(value_x2 + 1.0f, frame_bb.Min.y + 1.0f), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Max.y), bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right); if (value_x2 + arrow_size - style.FramePadding.x <= frame_bb.Max.x) RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f); } RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding); if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) { ImVec2 preview_pos = frame_bb.Min + style.FramePadding; if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(preview_pos, ImVec2(value_x2, frame_bb.Max.y), preview_value, NULL, NULL, ImVec2(0.0f, 0.0f)); } if (label_size.x > 0) RenderText(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y - 20.0f), label); if ((pressed || g.NavActivateId == id) && !popup_open) { if (window->DC.NavLayerCurrent == 0) window->NavLastIds[0] = id; OpenPopupEx(id, ImGuiPopupFlags_None); popup_open = true; } if (!popup_open) return false; if (has_window_size_constraint) { g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint; 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_)); 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.BeginPopupStack.Size); if (ImGuiWindow* popup_window = FindWindowByName(name)) if (popup_window->WasActive) { ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window); if (flags & ImGuiComboFlags_PopupAlignLeft) popup_window->AutoPosLastDirection = ImGuiDir_Left; else popup_window->AutoPosLastDirection = ImGuiDir_Down; 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); } ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove; PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y)); bool ret = Begin(name, NULL, window_flags); PopStyleVar(); if (!ret) { EndPopup(); IM_ASSERT(0); return false; } return true; } void ImGui::EndCombo() { EndPopup(); } 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; } static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) { 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; } 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; const char* preview_value = NULL; if (*current_item >= 0 && *current_item < items_count) items_getter(data, *current_item, &preview_value); if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)) SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); if (!BeginCombo(label, preview_value, ImGuiComboFlags_None)) return false; 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(); if (value_changed) { MarkItemEdited(g.CurrentWindow->DC.LastItemId); } return value_changed; } 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; } 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; 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; } static const ImGuiDataTypeInfo GDataTypeInfo[] = { { sizeof(char), "S8", "%d", "%d" }, { sizeof(unsigned char), "U8", "%u", "%u" }, { sizeof(short), "S16", "%d", "%d" }, { sizeof(unsigned short), "U16", "%u", "%u" }, { sizeof(int), "S32", "%d", "%d" }, { sizeof(unsigned int), "U32", "%u", "%u" }, #ifdef _MSC_VER { sizeof(ImS64), "S64", "%I64d","%I64d" }, { sizeof(ImU64), "U64", "%I64u","%I64u" }, #else { sizeof(ImS64), "S64", "%lld", "%lld" }, { sizeof(ImU64), "U64", "%llu", "%llu" }, #endif { sizeof(float), "float", "%f", "%f" }, { sizeof(double), "double","%f", "%lf" }, }; IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); static const char* PatchFormatStringFloatToInt(const char* fmt) { if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) return "%d"; const char* fmt_start = ImParseFormatFindStart(fmt); const char* fmt_end = ImParseFormatFindEnd(fmt_start); if (fmt_end > fmt_start && fmt_end[-1] == 'f') { #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (fmt_start == fmt && fmt_end[0] == 0) return "%d"; ImGuiContext& g = *GImGui; ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); return g.TempBuffer; #else IM_ASSERT(0 && "DragInt(): Invalid format string!"); #endif } return fmt; } const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type) { IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); return &GDataTypeInfo[data_type]; } int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format) { if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data); if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data); if (data_type == ImGuiDataType_Float) return ImFormatString(buf, buf_size, format, *(const float*)p_data); if (data_type == ImGuiDataType_Double) return ImFormatString(buf, buf_size, format, *(const double*)p_data); if (data_type == ImGuiDataType_S8) return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data); if (data_type == ImGuiDataType_U8) return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data); if (data_type == ImGuiDataType_S16) return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data); if (data_type == ImGuiDataType_U16) return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data); IM_ASSERT(0); return 0; } void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2) { IM_ASSERT(op == '+' || op == '-'); switch (data_type) { case ImGuiDataType_S8: if (op == '+') { *(ImS8*)output = ImAddClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); } if (op == '-') { *(ImS8*)output = ImSubClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); } return; case ImGuiDataType_U8: if (op == '+') { *(ImU8*)output = ImAddClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); } if (op == '-') { *(ImU8*)output = ImSubClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); } return; case ImGuiDataType_S16: if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); } if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); } return; case ImGuiDataType_U16: if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); } if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); } return; case ImGuiDataType_S32: if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); } if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); } return; case ImGuiDataType_U32: if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); } if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); } return; case ImGuiDataType_S64: if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); } if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); } return; case ImGuiDataType_U64: if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); } if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); } return; case ImGuiDataType_Float: if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; } if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; } return; case ImGuiDataType_Double: if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; } if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; } return; case ImGuiDataType_COUNT: break; } IM_ASSERT(0); } bool ImGui::DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* p_data, const char* format) { while (ImCharIsBlankA(*buf)) buf++; char op = buf[0]; if (op == '+' || op == '*' || op == '/') { buf++; while (ImCharIsBlankA(*buf)) buf++; } else { op = 0; } if (!buf[0]) return false; const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); ImGuiDataTypeTempStorage data_backup; memcpy(&data_backup, p_data, type_info->Size); if (format == NULL) format = type_info->ScanFmt; int arg1i = 0; if (data_type == ImGuiDataType_S32) { int* v = (int*)p_data; int arg0i = *v; float arg1f = 0.0f; if (op && sscanf(initial_value_buf, format, &arg0i) < 1) return false; if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } } else if (data_type == ImGuiDataType_Float) { format = "%f"; float* v = (float*)p_data; float arg0f = *v, arg1f = 0.0f; if (op && sscanf(initial_value_buf, format, &arg0f) < 1) return false; if (sscanf(buf, format, &arg1f) < 1) return false; if (op == '+') { *v = arg0f + arg1f; } else if (op == '*') { *v = arg0f * arg1f; } else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } else { *v = arg1f; } } else if (data_type == ImGuiDataType_Double) { format = "%lf"; double* v = (double*)p_data; double arg0f = *v, arg1f = 0.0; if (op && sscanf(initial_value_buf, format, &arg0f) < 1) return false; if (sscanf(buf, format, &arg1f) < 1) return false; if (op == '+') { *v = arg0f + arg1f; } else if (op == '*') { *v = arg0f * arg1f; } else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } else { *v = arg1f; } } else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) { sscanf(buf, format, p_data); } else { int v32; sscanf(buf, format, &v32); if (data_type == ImGuiDataType_S8) *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX); else if (data_type == ImGuiDataType_U8) *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX); else if (data_type == ImGuiDataType_S16) *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX); else if (data_type == ImGuiDataType_U16) *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX); else IM_ASSERT(0); } return memcmp(&data_backup, p_data, type_info->Size) != 0; } template static int DataTypeCompareT(const T* lhs, const T* rhs) { if (*lhs < *rhs) return -1; if (*lhs > *rhs) return +1; return 0; } int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2) { switch (data_type) { case ImGuiDataType_S8: return DataTypeCompareT((const ImS8* )arg_1, (const ImS8* )arg_2); case ImGuiDataType_U8: return DataTypeCompareT((const ImU8* )arg_1, (const ImU8* )arg_2); case ImGuiDataType_S16: return DataTypeCompareT((const ImS16* )arg_1, (const ImS16* )arg_2); case ImGuiDataType_U16: return DataTypeCompareT((const ImU16* )arg_1, (const ImU16* )arg_2); case ImGuiDataType_S32: return DataTypeCompareT((const ImS32* )arg_1, (const ImS32* )arg_2); case ImGuiDataType_U32: return DataTypeCompareT((const ImU32* )arg_1, (const ImU32* )arg_2); case ImGuiDataType_S64: return DataTypeCompareT((const ImS64* )arg_1, (const ImS64* )arg_2); case ImGuiDataType_U64: return DataTypeCompareT((const ImU64* )arg_1, (const ImU64* )arg_2); case ImGuiDataType_Float: return DataTypeCompareT((const float* )arg_1, (const float* )arg_2); case ImGuiDataType_Double: return DataTypeCompareT((const double*)arg_1, (const double*)arg_2); case ImGuiDataType_COUNT: break; } IM_ASSERT(0); return 0; } template static bool DataTypeClampT(T* v, const T* v_min, const T* v_max) { if (v_min && *v < *v_min) { *v = *v_min; return true; } if (v_max && *v > *v_max) { *v = *v_max; return true; } return false; } bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max) { switch (data_type) { case ImGuiDataType_S8: return DataTypeClampT((ImS8* )p_data, (const ImS8* )p_min, (const ImS8* )p_max); case ImGuiDataType_U8: return DataTypeClampT((ImU8* )p_data, (const ImU8* )p_min, (const ImU8* )p_max); case ImGuiDataType_S16: return DataTypeClampT((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max); case ImGuiDataType_U16: return DataTypeClampT((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max); case ImGuiDataType_S32: return DataTypeClampT((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max); case ImGuiDataType_U32: return DataTypeClampT((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max); case ImGuiDataType_S64: return DataTypeClampT((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max); case ImGuiDataType_U64: return DataTypeClampT((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max); case ImGuiDataType_Float: return DataTypeClampT((float* )p_data, (const float* )p_min, (const float* )p_max); case ImGuiDataType_Double: return DataTypeClampT((double*)p_data, (const double*)p_min, (const double*)p_max); case ImGuiDataType_COUNT: break; } IM_ASSERT(0); return false; } static float GetMinimumStepAtDecimalPrecision(int decimal_precision) { static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; if (decimal_precision < 0) return FLT_MIN; return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision); } template static const char* ImAtoi(const char* src, TYPE* output) { int negative = 0; if (*src == '-') { negative = 1; src++; } if (*src == '+') { src++; } TYPE v = 0; while (*src >= '0' && *src <= '9') v = (v * 10) + (*src++ - '0'); *output = negative ? -v : v; return src; } template TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v) { const char* fmt_start = ImParseFormatFindStart(format); if (fmt_start[0] != '%' || fmt_start[1] == '%') return v; char v_str[64]; ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); const char* p = v_str; while (*p == ' ') p++; if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) v = (TYPE)ImAtof(p); else ImAtoi(p, (SIGNEDTYPE*)&v); return v; } template bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags) { ImGuiContext& g = *GImGui; const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); const bool is_clamped = (v_min < v_max); const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) && is_decimal; if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX)) v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); float adjust_delta = 0.0f; if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { adjust_delta = g.IO.MouseDelta[axis]; if (g.IO.KeyAlt) adjust_delta *= 1.0f / 100.0f; if (g.IO.KeyShift) adjust_delta *= 10.0f; } else if (g.ActiveIdSource == ImGuiInputSource_Nav) { int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0; adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis]; v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); } adjust_delta *= v_speed; if (axis == ImGuiAxis_Y) adjust_delta = -adjust_delta; if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) adjust_delta /= (float)(v_max - v_min); bool is_just_activated = g.ActiveIdIsJustActivated; bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); if (is_just_activated || is_already_past_limits_and_pushing_outward) { g.DragCurrentAccum = 0.0f; g.DragCurrentAccumDirty = false; } else if (adjust_delta != 0.0f) { g.DragCurrentAccum += adjust_delta; g.DragCurrentAccumDirty = true; } if (!g.DragCurrentAccumDirty) return false; TYPE v_cur = *v; FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f; float logarithmic_zero_epsilon = 0.0f; const float zero_deadzone_halfsize = 0.0f; if (is_logarithmic) { const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 1; logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); float v_old_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); float v_new_parametric = v_old_parametric + g.DragCurrentAccum; v_cur = ScaleValueFromRatioT(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); v_old_ref_for_accum_remainder = v_old_parametric; } else { v_cur += (SIGNEDTYPE)g.DragCurrentAccum; } if (!(flags & ImGuiSliderFlags_NoRoundToFormat)) v_cur = RoundScalarWithFormatT(format, data_type, v_cur); g.DragCurrentAccumDirty = false; if (is_logarithmic) { float v_new_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder); } else { g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v); } if (v_cur == (TYPE)-0) v_cur = (TYPE)0; if (*v != v_cur && is_clamped) { if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal)) v_cur = v_min; if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal)) v_cur = v_max; } if (*v == v_cur) return false; *v = v_cur; return true; } bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); ImGuiContext& g = *GImGui; if (g.ActiveId == id) { if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) ClearActiveID(); else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) ClearActiveID(); } if (g.ActiveId != id) return false; if ((g.CurrentWindow->DC.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) return false; switch (data_type) { case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = DragBehaviorT(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN, p_max ? *(const ImS8*)p_max : IM_S8_MAX, format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = DragBehaviorT(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU8*) p_min : IM_U8_MIN, p_max ? *(const ImU8*)p_max : IM_U8_MAX, format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; } case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS16*)p_min : IM_S16_MIN, p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; } case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU16*)p_min : IM_U16_MIN, p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; } case ImGuiDataType_S32: return DragBehaviorT(data_type, (ImS32*)p_v, v_speed, p_min ? *(const ImS32* )p_min : IM_S32_MIN, p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags); case ImGuiDataType_U32: return DragBehaviorT(data_type, (ImU32*)p_v, v_speed, p_min ? *(const ImU32* )p_min : IM_U32_MIN, p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags); case ImGuiDataType_S64: return DragBehaviorT(data_type, (ImS64*)p_v, v_speed, p_min ? *(const ImS64* )p_min : IM_S64_MIN, p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags); case ImGuiDataType_U64: return DragBehaviorT(data_type, (ImU64*)p_v, v_speed, p_min ? *(const ImU64* )p_min : IM_U64_MIN, p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags); case ImGuiDataType_Float: return DragBehaviorT(data_type, (float*)p_v, v_speed, p_min ? *(const float* )p_min : -FLT_MAX, p_max ? *(const float* )p_max : FLT_MAX, format, flags); case ImGuiDataType_Double: return DragBehaviorT(data_type, (double*)p_v, v_speed, p_min ? *(const double*)p_min : -DBL_MAX, p_max ? *(const double*)p_max : DBL_MAX, format, flags); case ImGuiDataType_COUNT: break; } IM_ASSERT(0); return false; } bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const float w = CalcItemWidth(); const ImVec2 label_size = CalcTextSize(label, NULL, true); 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; if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) format = PatchFormatStringFloatToInt(format); const bool hovered = ItemHoverable(frame_bb, id); const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) { const bool focus_requested = temp_input_allowed && FocusableItemRegister(window, id); const bool clicked = (hovered && g.IO.MouseClicked[0]); const bool double_clicked = (hovered && g.IO.MouseDoubleClicked[0]); if (focus_requested || clicked || double_clicked || g.NavActivateId == id || g.NavInputId == id) { SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavInputId == id)) { temp_input_is_active = true; FocusableItemUnregister(window); } } if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active) if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { g.NavInputId = id; temp_input_is_active = true; FocusableItemUnregister(window); } } if (temp_input_is_active) { const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, p_min, p_max) < 0); return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); } const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavHighlight(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags); if (value_changed) MarkItemEdited(id); char value_buf[64]; const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Min.y), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Min.y), colWhite, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Min.y), ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Max.y), colWhite, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Min.y), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Max.y), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Max.y), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Max.y), colBlack, 1.0f); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); return value_changed; } bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; bool value_changed = false; BeginGroup(); PushID(label); PushMultiItemsWidths(components, CalcItemWidth()); size_t type_size = GDataTypeInfo[data_type].Size; for (int i = 0; i < components; i++) { PushID(i); if (i > 0) SameLine(0, g.Style.ItemInnerSpacing.x); value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags); PopID(); PopItemWidth(); p_data = (void*)((char*)p_data + type_size); } PopID(); const char* label_end = FindRenderedTextEnd(label); if (label != label_end) { SameLine(0, g.Style.ItemInnerSpacing.x); TextEx(label, label_end); } EndGroup(); return value_changed; } bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags); } bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags); } bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags); } bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags); } bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; PushID(label); BeginGroup(); PushMultiItemsWidths(2, CalcItemWidth()); float min_min = (v_min >= v_max) ? -FLT_MAX : v_min; float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max); ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags); PopItemWidth(); SameLine(0, g.Style.ItemInnerSpacing.x); float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min); float max_max = (v_min >= v_max) ? FLT_MAX : v_max; ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags); PopItemWidth(); SameLine(0, g.Style.ItemInnerSpacing.x); TextEx(label, FindRenderedTextEnd(label)); EndGroup(); PopID(); return value_changed; } bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags); } bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags); } bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags); } bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags); } bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; PushID(label); BeginGroup(); PushMultiItemsWidths(2, CalcItemWidth()); int min_min = (v_min >= v_max) ? INT_MIN : v_min; int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max); ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags); PopItemWidth(); SameLine(0, g.Style.ItemInnerSpacing.x); int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min); int max_max = (v_min >= v_max) ? INT_MAX : v_max; ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags); PopItemWidth(); SameLine(0, g.Style.ItemInnerSpacing.x); TextEx(label, FindRenderedTextEnd(label)); EndGroup(); PopID(); return value_changed; } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power) { ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None; if (power != 1.0f) { IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); IM_ASSERT(p_min != NULL && p_max != NULL); drag_flags |= ImGuiSliderFlags_Logarithmic; } return DragScalar(label, data_type, p_data, v_speed, p_min, p_max, format, drag_flags); } bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power) { ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None; if (power != 1.0f) { IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); IM_ASSERT(p_min != NULL && p_max != NULL); drag_flags |= ImGuiSliderFlags_Logarithmic; } return DragScalarN(label, data_type, p_data, components, v_speed, p_min, p_max, format, drag_flags); } #endif template float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) { if (v_min == v_max) return 0.0f; IM_UNUSED(data_type); const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); if (is_logarithmic) { bool flipped = v_max < v_min; if (flipped) ImSwap(v_min, v_max); FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; if ((v_min == 0.0f) && (v_max < 0.0f)) v_min_fudged = -logarithmic_zero_epsilon; else if ((v_max == 0.0f) && (v_min < 0.0f)) v_max_fudged = -logarithmic_zero_epsilon; float result; if (v_clamped <= v_min_fudged) result = 0.0f; else if (v_clamped >= v_max_fudged) result = 1.0f; else if ((v_min * v_max) < 0.0f) { float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; if (v == 0.0f) result = zero_point_center; else if (v < 0.0f) result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L; else result = zero_point_snap_R + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) * (1.0f - zero_point_snap_R)); } else if ((v_min < 0.0f) || (v_max < 0.0f)) result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged)); else result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged)); return flipped ? (1.0f - result) : result; } return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min)); } template TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) { if (v_min == v_max) return v_min; const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); TYPE result; if (is_logarithmic) { if (t <= 0.0f) result = v_min; else if (t >= 1.0f) result = v_max; else { bool flipped = v_max < v_min; FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; if (flipped) ImSwap(v_min_fudged, v_max_fudged); if ((v_max == 0.0f) && (v_min < 0.0f)) v_max_fudged = -logarithmic_zero_epsilon; float t_with_flip = flipped ? (1.0f - t) : t; if ((v_min * v_max) < 0.0f) { float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R) result = (TYPE)0.0f; else if (t_with_flip < zero_point_center) result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L)))); else result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R)))); } else if ((v_min < 0.0f) || (v_max < 0.0f)) result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip))); else result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip)); } } else { if (is_decimal) { result = ImLerp(v_min, v_max, t); } else { if (t < 1.0) { FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t; result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5))); } else { result = v_max; } } } return result; } template bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb) { ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) && is_decimal; const float grab_padding = 2.0f; const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f; float grab_sz = style.GrabMinSize; SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); if (!is_decimal && v_range >= 0) grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); grab_sz = ImMin(grab_sz, slider_sz); const float slider_usable_sz = slider_sz - grab_sz; const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f; const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f; float logarithmic_zero_epsilon = 0.0f; float zero_deadzone_halfsize = 0.0f; if (is_logarithmic) { const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 1; logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f); } bool value_changed = false; if (g.ActiveId == id) { bool set_new_value = false; float clicked_t = 0.0f; if (g.ActiveIdSource == ImGuiInputSource_Mouse) { if (!g.IO.MouseDown[0]) { ClearActiveID(); } else { const float mouse_abs_pos = g.IO.MousePos[axis]; clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; if (axis == ImGuiAxis_Y) clicked_t = 1.0f - clicked_t; set_new_value = true; } } else if (g.ActiveIdSource == ImGuiInputSource_Nav) { if (g.ActiveIdIsJustActivated) { g.SliderCurrentAccum = 0.0f; g.SliderCurrentAccumDirty = false; } const ImVec2 input_delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f); float input_delta = (axis == ImGuiAxis_X) ? input_delta2.x : -input_delta2.y; if (input_delta != 0.0f) { const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0; if (decimal_precision > 0) { input_delta /= 100.0f; if (IsNavInputDown(ImGuiNavInput_TweakSlow)) input_delta /= 10.0f; } else { if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow)) input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; else input_delta /= 100.0f; } if (IsNavInputDown(ImGuiNavInput_TweakFast)) input_delta *= 10.0f; g.SliderCurrentAccum += input_delta; g.SliderCurrentAccumDirty = true; } float delta = g.SliderCurrentAccum; if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) { ClearActiveID(); } else if (g.SliderCurrentAccumDirty) { clicked_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) { set_new_value = false; g.SliderCurrentAccum = 0.0f; } else { set_new_value = true; float old_clicked_t = clicked_t; clicked_t = ImSaturate(clicked_t + delta); TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (!(flags & ImGuiSliderFlags_NoRoundToFormat)) v_new = RoundScalarWithFormatT(format, data_type, v_new); float new_clicked_t = ScaleRatioFromValueT(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (delta > 0) g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta); else g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta); } g.SliderCurrentAccumDirty = false; } } if (set_new_value) { TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (!(flags & ImGuiSliderFlags_NoRoundToFormat)) v_new = RoundScalarWithFormatT(format, data_type, v_new); if (*v != v_new) { *v = v_new; value_changed = true; } } } if (slider_sz < 1.0f) { *out_grab_bb = ImRect(bb.Min, bb.Min); } else { float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (axis == ImGuiAxis_Y) grab_t = 1.0f - grab_t; const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); if (axis == ImGuiAxis_X) *out_grab_bb = ImRect(grab_pos - grab_sz * 0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz * 0.5f, bb.Max.y - grab_padding); else *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5f); } return value_changed; } bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb) { IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); ImGuiContext& g = *GImGui; if ((g.CurrentWindow->DC.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) return false; switch (data_type) { case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_U32, &v32, *(const ImU8*)p_min, *(const ImU8*)p_max, format, flags, out_grab_bb); if (r) *(ImU8*)p_v = (ImU8)v32; return r; } case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS16*)p_min, *(const ImS16*)p_max, format, flags, out_grab_bb); if (r) *(ImS16*)p_v = (ImS16)v32; return r; } case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_U32, &v32, *(const ImU16*)p_min, *(const ImU16*)p_max, format, flags, out_grab_bb); if (r) *(ImU16*)p_v = (ImU16)v32; return r; } case ImGuiDataType_S32: IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2); return SliderBehaviorT(bb, id, data_type, (ImS32*)p_v, *(const ImS32*)p_min, *(const ImS32*)p_max, format, flags, out_grab_bb); case ImGuiDataType_U32: IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2); return SliderBehaviorT(bb, id, data_type, (ImU32*)p_v, *(const ImU32*)p_min, *(const ImU32*)p_max, format, flags, out_grab_bb); case ImGuiDataType_S64: IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2); return SliderBehaviorT(bb, id, data_type, (ImS64*)p_v, *(const ImS64*)p_min, *(const ImS64*)p_max, format, flags, out_grab_bb); case ImGuiDataType_U64: IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2); return SliderBehaviorT(bb, id, data_type, (ImU64*)p_v, *(const ImU64*)p_min, *(const ImU64*)p_max, format, flags, out_grab_bb); case ImGuiDataType_Float: IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f); return SliderBehaviorT(bb, id, data_type, (float*)p_v, *(const float*)p_min, *(const float*)p_max, format, flags, out_grab_bb); case ImGuiDataType_Double: IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f); return SliderBehaviorT(bb, id, data_type, (double*)p_v, *(const double*)p_min, *(const double*)p_max, format, flags, out_grab_bb); case ImGuiDataType_COUNT: break; } IM_ASSERT(0); return false; } bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { InvisibleButton("##0", ImVec2(0, 0)); ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const float w = CalcItemWidth(); const ImVec2 label_size = CalcTextSize(label, NULL, true); ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); frame_bb.Max -= ImVec2(style.ItemInnerSpacing.x + 30.0f, 7.0f); ImRect total_bb(frame_bb.Min + ImVec2(0,20.0f), frame_bb.Max + ImVec2(0, 30.0f)); frame_bb.Min -= ImVec2(0, 7.0f); total_bb.Min += ImVec2(0, 10.0f); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id, &frame_bb)) return false; if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) format = PatchFormatStringFloatToInt(format); const bool hovered = ItemHoverable(frame_bb, id); const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) { const bool focus_requested = temp_input_allowed && FocusableItemRegister(window, id); const bool clicked = (hovered && g.IO.MouseClicked[0]); if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id) { SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id)) { temp_input_is_active = true; FocusableItemUnregister(window); } } } if (temp_input_is_active) { const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0; return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); } const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavHighlight(frame_bb, id); ImVec2 tempmin = frame_bb.Min + ImVec2(0, 9.0f); ImVec2 tempmax = frame_bb.Max - ImVec2(0, 9.0f); RenderFrame(tempmin, tempmax, frame_col, true, g.Style.FrameRounding); window->DrawList->AddLine(ImVec2(tempmin.x - 1.0f, tempmin.y - 1.0f), ImVec2(tempmax.x - 1.0f, tempmin.y - 1.0f), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(tempmin.x - 1.0f, tempmin.y - 1.0f), ImVec2(tempmin.x - 1.0f, tempmax.y ), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(tempmax.x - 1.0f, tempmin.y - 1.0f), ImVec2(tempmax.x - 1.0f, tempmax.y), colWhite, 1.0f); window->DrawList->AddLine(ImVec2(tempmin.x - 1.0f, tempmax.y), ImVec2(tempmax.x - 1.0f, tempmax.y), colWhite, 1.0f); ImRect grab_bb; const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb); if (value_changed) MarkItemEdited(id); tempmin.x += 1.0f; tempmax.x -= 2.0f; tempmin.x += (grab_bb.Max.x - grab_bb.Min.x)/2; tempmax.x -= (grab_bb.Max.x - grab_bb.Min.x)/2; for (int i = 0; i < 11; i++) window->DrawList->AddLine(ImVec2(tempmin.x + (i * (tempmax.x - tempmin.x) / 10) - 1.0f, tempmax.y + 4.0f), ImVec2(tempmin.x + (i * (tempmax.x - tempmin.x) / 10) - 1.0f, tempmax.y + 9.0f), 0xFFD9D9D9, 1.0f); if (grab_bb.Max.x > grab_bb.Min.x) { grab_bb.Min.x += 1.0f; grab_bb.Max.x -= 2.0f; grab_bb.Min.y += 1.0f; grab_bb.Max.y -= 1.0f; window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); window->DrawList->AddLine(ImVec2(grab_bb.Min.x - 1.0f, grab_bb.Min.y - 1.0f), ImVec2(grab_bb.Max.x - 1.0f, grab_bb.Min.y - 1.0f), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(grab_bb.Min.x - 1.0f, grab_bb.Min.y - 1.0f), ImVec2(grab_bb.Min.x - 1.0f, grab_bb.Max.y), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(grab_bb.Max.x - 1.0f, grab_bb.Min.y - 1.0f), ImVec2(grab_bb.Max.x - 1.0f, grab_bb.Max.y), colWhite, 1.0f); window->DrawList->AddLine(ImVec2(grab_bb.Min.x - 1.0f, grab_bb.Max.y), ImVec2(grab_bb.Max.x - 1.0f, grab_bb.Max.y), colWhite, 1.0f); } char value_buf[64]; const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min + ImVec2(frame_bb.Max.x - frame_bb.Min.x, 0), frame_bb.Max + ImVec2(45.0f, 0), value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) { RenderText(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y - 20.0f), label); } IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); return value_changed; } bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; bool value_changed = false; BeginGroup(); PushID(label); PushMultiItemsWidths(components, CalcItemWidth()); size_t type_size = GDataTypeInfo[data_type].Size; for (int i = 0; i < components; i++) { PushID(i); if (i > 0) SameLine(0, g.Style.ItemInnerSpacing.x); value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags); PopID(); PopItemWidth(); v = (void*)((char*)v + type_size); } PopID(); const char* label_end = FindRenderedTextEnd(label); if (label != label_end) { SameLine(0, g.Style.ItemInnerSpacing.x); TextEx(label, label_end); } EndGroup(); return value_changed; } bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags); } bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags); } bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags); } bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags); } bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags) { if (format == NULL) format = "%.0f deg"; float v_deg = (*v_rad) * 360.0f / (2 * IM_PI); bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags); *v_rad = v_deg * (2 * IM_PI) / 360.0f; return value_changed; } bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags); } bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags); } bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags); } bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags); } bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); ItemSize(bb, style.FramePadding.y); if (!ItemAdd(frame_bb, id)) return false; if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) format = PatchFormatStringFloatToInt(format); const bool hovered = ItemHoverable(frame_bb, id); if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) { SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); } const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavHighlight(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); ImRect grab_bb; const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb); if (value_changed) MarkItemEdited(id); if (grab_bb.Max.y > grab_bb.Min.y) window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); char value_buf[64]; const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); return value_changed; } bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags); } bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags); } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power) { ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None; if (power != 1.0f) { IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); slider_flags |= ImGuiSliderFlags_Logarithmic; } return SliderScalar(label, data_type, p_data, p_min, p_max, format, slider_flags); } bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power) { ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None; if (power != 1.0f) { IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); slider_flags |= ImGuiSliderFlags_Logarithmic; } return SliderScalarN(label, data_type, v, components, v_min, v_max, format, slider_flags); } #endif const char* ImParseFormatFindStart(const char* fmt) { while (char c = fmt[0]) { if (c == '%' && fmt[1] != '%') return fmt; else if (c == '%') fmt++; fmt++; } return fmt; } const char* ImParseFormatFindEnd(const char* fmt) { if (fmt[0] != '%') return fmt; const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A')); const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a')); for (char c; (c = *fmt) != 0; fmt++) { if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0) return fmt + 1; if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0) return fmt + 1; } return fmt; } const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size) { const char* fmt_start = ImParseFormatFindStart(fmt); if (fmt_start[0] != '%') return fmt; const char* fmt_end = ImParseFormatFindEnd(fmt_start); if (fmt_end[0] == 0) return fmt_start; ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size)); return buf; } int ImParseFormatPrecision(const char* fmt, int default_precision) { fmt = ImParseFormatFindStart(fmt); if (fmt[0] != '%') return default_precision; fmt++; while (*fmt >= '0' && *fmt <= '9') fmt++; int precision = INT_MAX; if (*fmt == '.') { fmt = ImAtoi(fmt + 1, &precision); if (precision < 0 || precision > 99) precision = default_precision; } if (*fmt == 'e' || *fmt == 'E') precision = -1; if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX) precision = -1; return (precision == INT_MAX) ? default_precision : precision; } bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags) { ImGuiContext& g = *GImGui; const bool init = (g.TempInputId != id); if (init) ClearActiveID(); g.CurrentWindow->DC.CursorPos = bb.Min; bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags); if (init) { IM_ASSERT(g.ActiveId == id); g.TempInputId = g.ActiveId; } return value_changed; } bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max) { ImGuiContext& g = *GImGui; char fmt_buf[32]; char data_buf[32]; format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); ImStrTrimBlanks(data_buf); ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited; flags |= ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal); bool value_changed = false; if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags)) { size_t data_type_size = DataTypeGetInfo(data_type)->Size; ImGuiDataTypeTempStorage data_backup; memcpy(&data_backup, p_data, data_type_size); DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, p_data, NULL); if (p_clamp_min || p_clamp_max) { if (DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0) ImSwap(p_clamp_min, p_clamp_max); DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max); } value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; if (value_changed) MarkItemEdited(id); } return value_changed; } bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; char buf[64]; DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); bool value_changed = false; if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0) flags |= ImGuiInputTextFlags_CharsDecimal; flags |= ImGuiInputTextFlags_AutoSelectAll; flags |= ImGuiInputTextFlags_NoMarkEdited; if (p_step != NULL) { const float button_size = GetFrameHeight(); BeginGroup(); PushID(label); SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format); const ImVec2 backup_frame_padding = style.FramePadding; style.FramePadding.x = style.FramePadding.y; ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups; if (flags & ImGuiInputTextFlags_ReadOnly) button_flags |= ImGuiButtonFlags_Disabled; SameLine(0, style.ItemInnerSpacing.x); if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) { DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); value_changed = true; } SameLine(0, style.ItemInnerSpacing.x); if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) { DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); value_changed = true; } const char* label_end = FindRenderedTextEnd(label); if (label != label_end) { SameLine(0, style.ItemInnerSpacing.x); TextEx(label, label_end); } style.FramePadding = backup_frame_padding; PopID(); EndGroup(); } else { if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format); } if (value_changed) MarkItemEdited(window->DC.LastItemId); return value_changed; } bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; bool value_changed = false; BeginGroup(); PushID(label); PushMultiItemsWidths(components, CalcItemWidth()); size_t type_size = GDataTypeInfo[data_type].Size; for (int i = 0; i < components; i++) { PushID(i); if (i > 0) SameLine(0, g.Style.ItemInnerSpacing.x); value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags); PopID(); PopItemWidth(); p_data = (void*)((char*)p_data + type_size); } PopID(); const char* label_end = FindRenderedTextEnd(label); if (label != label_end) { SameLine(0.0f, g.Style.ItemInnerSpacing.x); TextEx(label, label_end); } EndGroup(); return value_changed; } bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags) { flags |= ImGuiInputTextFlags_CharsScientific; return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags); } bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags); } bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags); } bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags); } bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags) { const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags); } bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags); } bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags); } bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags); } bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags) { flags |= ImGuiInputTextFlags_CharsScientific; return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags); } bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); } bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); } bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); } static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) { int line_count = 0; const char* s = text_begin; while (char c = *s++) if (c == '\n') line_count++; s--; if (s[0] != '\n' && s[0] != '\r') line_count++; *out_text_end = s; return line_count; } static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) { ImGuiContext& g = *GImGui; ImFont* font = g.Font; const float line_height = g.FontSize; const float scale = line_height / font->FontSize; ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; const ImWchar* s = text_begin; while (s < text_end) { unsigned int c = (unsigned int)(*s++); if (c == '\n') { text_size.x = ImMax(text_size.x, line_width); text_size.y += line_height; line_width = 0.0f; if (stop_on_new_line) break; continue; } if (c == '\r') continue; const float char_width = font->GetCharAdvance((ImWchar)c) * scale; line_width += char_width; } if (text_size.x < line_width) text_size.x = line_width; if (out_offset) *out_offset = ImVec2(line_width, text_size.y + line_height); if (line_width > 0 || text_size.y == 0.0f) text_size.y += line_height; if (remaining) *remaining = s; return text_size; } namespace ImStb { static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; } static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *GImGui; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); } static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; } static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) { const ImWchar* text = obj->TextW.Data; const ImWchar* text_remaining = NULL; const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; r->ymin = 0.0f; r->ymax = size.y; r->num_chars = (int)(text_remaining - (text + line_start_idx)); } static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator(obj->TextW[idx - 1]) && !is_separator(obj->TextW[idx]) ) : 1; } static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } #ifdef __APPLE__ static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx]) ) : 1; } static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } #else static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } #endif #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) { ImWchar* dst = obj->TextW.Data + pos; obj->Edited = true; obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); obj->CurLenW -= n; const ImWchar* src = obj->TextW.Data + pos + n; while (ImWchar c = *src++) *dst++ = c; *dst = '\0'; } static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) { const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0; const int text_len = obj->CurLenW; IM_ASSERT(pos <= text_len); const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA)) return false; if (new_text_len + text_len + 1 > obj->TextW.Size) { if (!is_resizable) return false; IM_ASSERT(text_len < obj->TextW.Size); obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1); } ImWchar* text = obj->TextW.Data; if (pos != text_len) memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); obj->Edited = true; obj->CurLenW += new_text_len; obj->CurLenA += new_text_len_utf8; obj->TextW[obj->CurLenW] = '\0'; return true; } #define STB_TEXTEDIT_K_LEFT 0x200000 #define STB_TEXTEDIT_K_RIGHT 0x200001 #define STB_TEXTEDIT_K_UP 0x200002 #define STB_TEXTEDIT_K_DOWN 0x200003 #define STB_TEXTEDIT_K_LINESTART 0x200004 #define STB_TEXTEDIT_K_LINEEND 0x200005 #define STB_TEXTEDIT_K_TEXTSTART 0x200006 #define STB_TEXTEDIT_K_TEXTEND 0x200007 #define STB_TEXTEDIT_K_DELETE 0x200008 #define STB_TEXTEDIT_K_BACKSPACE 0x200009 #define STB_TEXTEDIT_K_UNDO 0x20000A #define STB_TEXTEDIT_K_REDO 0x20000B #define STB_TEXTEDIT_K_WORDLEFT 0x20000C #define STB_TEXTEDIT_K_WORDRIGHT 0x20000D #define STB_TEXTEDIT_K_PGUP 0x20000E #define STB_TEXTEDIT_K_PGDOWN 0x20000F #define STB_TEXTEDIT_K_SHIFT 0x400000 #define STB_TEXTEDIT_IMPLEMENTATION #include "imstb_textedit.h" static void stb_textedit_replace(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, const STB_TEXTEDIT_CHARTYPE* text, int text_len) { stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len); ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW); if (text_len <= 0) return; if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len)) { state->cursor = text_len; state->has_preferred_x = 0; return; } IM_ASSERT(0); } } void ImGuiInputTextState::OnKeyPressed(int key) { stb_textedit_key(this, &Stb, key); CursorFollow = true; CursorAnimReset(); } ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() { memset(this, 0, sizeof(*this)); } void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) { IM_ASSERT(pos + bytes_count <= BufTextLen); char* dst = Buf + pos; const char* src = Buf + pos + bytes_count; while (char c = *src++) *dst++ = c; *dst = '\0'; if (CursorPos >= pos + bytes_count) CursorPos -= bytes_count; else if (CursorPos >= pos) CursorPos = pos; SelectionStart = SelectionEnd = CursorPos; BufDirty = true; BufTextLen -= bytes_count; } void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) { const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); if (new_text_len + BufTextLen >= BufSize) { if (!is_resizable) return; ImGuiContext& g = *GImGui; ImGuiInputTextState* edit_state = &g.InputTextState; IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); IM_ASSERT(Buf == edit_state->TextA.Data); int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; edit_state->TextA.reserve(new_buf_size + 1); Buf = edit_state->TextA.Data; BufSize = edit_state->BufCapacityA = new_buf_size; } if (BufTextLen != pos) memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); Buf[BufTextLen + new_text_len] = '\0'; if (CursorPos >= pos) CursorPos += new_text_len; SelectionStart = SelectionEnd = CursorPos; BufDirty = true; BufTextLen += new_text_len; } static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { unsigned int c = *p_char; if (c < 0x20) { bool pass = false; pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); if (!pass) return false; } if (c == 127) return false; if (c >= 0xE000 && c <= 0xF8FF) return false; if (c > IM_UNICODE_CODEPOINT_MAX) return false; if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific)) { ImGuiContext& g = *GImGui; const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint; if (flags & ImGuiInputTextFlags_CharsDecimal) if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/')) return false; if (flags & ImGuiInputTextFlags_CharsScientific) if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E')) return false; if (flags & ImGuiInputTextFlags_CharsHexadecimal) if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) return false; if (flags & ImGuiInputTextFlags_CharsUppercase) if (c >= 'a' && c <= 'z') *p_char = (c += (unsigned int)('A' - 'a')); if (flags & ImGuiInputTextFlags_CharsNoBlank) if (ImCharIsBlankW(c)) return false; } if (flags & ImGuiInputTextFlags_CallbackCharFilter) { ImGuiInputTextCallbackData callback_data; memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; callback_data.EventChar = (ImWchar)c; callback_data.Flags = flags; callback_data.UserData = user_data; if (callback(&callback_data) != 0) return false; *p_char = callback_data.EventChar; if (!callback_data.EventChar) return false; } return true; } bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; IM_ASSERT(buf != NULL && buf_size >= 0); IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; const ImGuiStyle& style = g.Style; const bool RENDER_SELECTION_WHEN_INACTIVE = false; const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; if (is_resizable) IM_ASSERT(callback != NULL); if (is_multiline) BeginGroup(); const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); ImRect total_bb(frame_bb.Min - ImVec2(0.0f, 10.0f), frame_bb.Max + ImVec2(style.ItemInnerSpacing.x, 0.0f)); ImGuiWindow* draw_window = window; ImVec2 inner_size = frame_size; if (is_multiline) { if (!ItemAdd(total_bb, id, &frame_bb)) { ItemSize(total_bb, style.FramePadding.y); EndGroup(); return false; } PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove); PopStyleVar(2); PopStyleColor(); if (!child_visible) { EndChild(); EndGroup(); return false; } draw_window = g.CurrentWindow; draw_window->DC.NavLayerActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); draw_window->DC.CursorPos += style.FramePadding; inner_size.x -= draw_window->ScrollbarSizes.x; } else { ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id, &frame_bb)) return false; } const bool hovered = ItemHoverable(frame_bb, id); if (hovered) g.MouseCursor = ImGuiMouseCursor_TextInput; ImGuiInputTextState* state = GetInputTextState(id); const bool focus_requested = FocusableItemRegister(window, id); const bool focus_requested_by_code = focus_requested && (g.FocusRequestCurrWindow == window && g.FocusRequestCurrCounterRegular == window->DC.FocusCounterRegular); const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; const bool user_clicked = hovered && io.MouseClicked[0]; const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard)); const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); bool clear_active_id = false; bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline); float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline); const bool init_make_active = (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start); const bool init_state = (init_make_active || user_scroll_active); if ((init_state && g.ActiveId != id) || init_changed_specs) { state = &g.InputTextState; state->CursorAnimReset(); const int buf_len = (int)strlen(buf); state->InitialTextA.resize(buf_len + 1); memcpy(state->InitialTextA.Data, buf, buf_len + 1); const char* buf_end = NULL; state->TextW.resize(buf_size + 1); state->TextA.resize(0); state->TextAIsValid = false; state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); state->CurLenA = (int)(buf_end - buf); const bool recycle_state = (state->ID == id && !init_changed_specs); if (recycle_state) { state->CursorClamp(); } else { state->ID = id; state->ScrollX = 0.0f; stb_textedit_initialize_state(&state->Stb, !is_multiline); if (!is_multiline && focus_requested_by_code) select_all = true; } if (flags & ImGuiInputTextFlags_AlwaysInsertMode) state->Stb.insert_mode = 1; if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) select_all = true; } if (g.ActiveId != id && init_make_active) { IM_ASSERT(state && state->ID == id); SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); IM_ASSERT(ImGuiNavInput_COUNT < 32); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Home) | ((ImU64)1 << ImGuiKey_End); if (is_multiline) g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown); if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Tab); } if (g.ActiveId == id && state == NULL) ClearActiveID(); if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) clear_active_id = true; const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active); bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); bool value_changed = false; bool enter_pressed = false; if (is_readonly && state != NULL && (render_cursor || render_selection)) { const char* buf_end = NULL; state->TextW.resize(buf_size + 1); state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end); state->CurLenA = (int)(buf_end - buf); state->CursorClamp(); render_selection &= state->HasSelection(); } const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid; const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); if (is_password && !is_displaying_hint) { const ImFontGlyph* glyph = g.Font->FindGlyph('*'); ImFont* password_font = &g.InputTextPasswordFont; password_font->FontSize = g.Font->FontSize; password_font->Scale = g.Font->Scale; password_font->Ascent = g.Font->Ascent; password_font->Descent = g.Font->Descent; password_font->ContainerAtlas = g.Font->ContainerAtlas; password_font->FallbackGlyph = glyph; password_font->FallbackAdvanceX = glyph->AdvanceX; IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); PushFont(password_font); } int backup_current_text_length = 0; if (g.ActiveId == id) { IM_ASSERT(state != NULL); backup_current_text_length = state->CurLenA; state->Edited = false; state->BufCapacityA = buf_size; state->UserFlags = flags; state->UserCallback = callback; state->UserCallbackData = callback_user_data; g.ActiveIdAllowOverlap = !io.MouseDown[0]; g.WantTextInputNextFrame = 1; const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX; const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f)); const bool is_osx = io.ConfigMacOSXBehaviors; if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0])) { state->SelectAll(); state->SelectedAllMouseLock = true; } else if (hovered && is_osx && io.MouseDoubleClicked[0]) { state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); } else if (io.MouseClicked[0] && !state->SelectedAllMouseLock) { if (hovered) { stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); state->CursorAnimReset(); } } else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) { stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); state->CursorAnimReset(); state->CursorFollow = true; } if (state->SelectedAllMouseLock && !io.MouseDown[0]) state->SelectedAllMouseLock = false; const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper); if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && !is_readonly) if (!io.InputQueueCharacters.contains('\t')) { unsigned int c = '\t'; if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } if (io.InputQueueCharacters.Size > 0) { if (!ignore_char_inputs && !is_readonly && !user_nav_input_start) for (int n = 0; n < io.InputQueueCharacters.Size; n++) { unsigned int c = (unsigned int)io.InputQueueCharacters[n]; if (c == '\t' && io.KeyShift) continue; if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } io.InputQueueCharacters.resize(0); } } bool cancel_edit = false; if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) { IM_ASSERT(state != NULL); IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1); state->Stb.row_count_per_page = row_count_per_page; const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_osx = io.ConfigMacOSXBehaviors; const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift)); const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; const bool is_ctrl_key_only = (io.KeyMods == ImGuiKeyModFlags_Ctrl); const bool is_shift_key_only = (io.KeyMods == ImGuiKeyModFlags_Shift); const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl); const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection()); const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_readonly; const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && !is_readonly && is_undoable); const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && !is_readonly && is_undoable; if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_PageUp) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; } else if (IsKeyPressedMap(ImGuiKey_PageDown) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; } else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Backspace) && !is_readonly) { if (!state->HasSelection()) { if (is_wordmove_key_down) state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT); else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT); } state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Enter) || IsKeyPressedMap(ImGuiKey_KeyPadEnter)) { bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) { enter_pressed = clear_active_id = true; } else if (!is_readonly) { unsigned int c = '\n'; if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } } else if (IsKeyPressedMap(ImGuiKey_Escape)) { clear_active_id = cancel_edit = true; } else if (is_undo || is_redo) { state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); state->ClearSelection(); } else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A)) { state->SelectAll(); state->CursorFollow = true; } else if (is_cut || is_copy) { if (io.SetClipboardTextFn) { const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0; const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW; const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1; char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char)); ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie); SetClipboardText(clipboard_data); MemFree(clipboard_data); } if (is_cut) { if (!state->HasSelection()) state->SelectAll(); state->CursorFollow = true; stb_textedit_cut(state, &state->Stb); } } else if (is_paste) { if (const char* clipboard = GetClipboardText()) { const int clipboard_len = (int)strlen(clipboard); ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar)); int clipboard_filtered_len = 0; for (const char* s = clipboard; *s; ) { unsigned int c; s += ImTextCharFromUtf8(&c, s, NULL); if (c == 0) break; if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data)) continue; clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; } clipboard_filtered[clipboard_filtered_len] = 0; if (clipboard_filtered_len > 0) { stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len); state->CursorFollow = true; } MemFree(clipboard_filtered); } } render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); } if (g.ActiveId == id) { IM_ASSERT(state != NULL); const char* apply_new_text = NULL; int apply_new_text_length = 0; if (cancel_edit) { if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0) { apply_new_text = state->InitialTextA.Data; apply_new_text_length = state->InitialTextA.Size - 1; ImVector w_text; if (apply_new_text_length > 0) { w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1); ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length); } stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0); } } bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); if (apply_edit_back_to_user_buffer) { if (!is_readonly) { state->TextAIsValid = true; state->TextA.resize(state->TextW.Size * 4 + 1); ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL); } if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0) { IM_ASSERT(callback != NULL); ImGuiInputTextFlags event_flag = 0; ImGuiKey event_key = ImGuiKey_COUNT; if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) { event_flag = ImGuiInputTextFlags_CallbackCompletion; event_key = ImGuiKey_Tab; } else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) { event_flag = ImGuiInputTextFlags_CallbackHistory; event_key = ImGuiKey_UpArrow; } else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) { event_flag = ImGuiInputTextFlags_CallbackHistory; event_key = ImGuiKey_DownArrow; } else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited) { event_flag = ImGuiInputTextFlags_CallbackEdit; } else if (flags & ImGuiInputTextFlags_CallbackAlways) { event_flag = ImGuiInputTextFlags_CallbackAlways; } if (event_flag) { ImGuiInputTextCallbackData callback_data; memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); callback_data.EventFlag = event_flag; callback_data.Flags = flags; callback_data.UserData = callback_user_data; callback_data.EventKey = event_key; callback_data.Buf = state->TextA.Data; callback_data.BufTextLen = state->CurLenA; callback_data.BufSize = state->BufCapacityA; callback_data.BufDirty = false; ImWchar* text = state->TextW.Data; const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor); const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start); const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end); callback(&callback_data); IM_ASSERT(callback_data.Buf == state->TextA.Data); IM_ASSERT(callback_data.BufSize == state->BufCapacityA); IM_ASSERT(callback_data.Flags == flags); const bool buf_dirty = callback_data.BufDirty; if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb.cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } if (buf_dirty) { IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); if (callback_data.BufTextLen > backup_current_text_length && is_resizable) state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL); state->CurLenA = callback_data.BufTextLen; state->CursorAnimReset(); } } } if (!is_readonly && strcmp(state->TextA.Data, buf) != 0) { apply_new_text = state->TextA.Data; apply_new_text_length = state->CurLenA; } } if (apply_new_text) { IM_ASSERT(apply_new_text_length >= 0); if (is_resizable) { ImGuiInputTextCallbackData callback_data; callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; callback_data.Flags = flags; callback_data.Buf = buf; callback_data.BufTextLen = apply_new_text_length; callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); callback_data.UserData = callback_user_data; callback(&callback_data); buf = callback_data.Buf; buf_size = callback_data.BufSize; apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); IM_ASSERT(apply_new_text_length <= buf_size); } ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size)); value_changed = true; } state->UserFlags = 0; state->UserCallback = NULL; state->UserCallbackData = NULL; } if (clear_active_id && g.ActiveId == id) ClearActiveID(); if (!is_multiline) { RenderNavHighlight(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); } window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Min.y), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Min.y), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Min.y), ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Max.y), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Min.y), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Max.y), colWhite, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Max.y), ImVec2(frame_bb.Max.x - 1.0f, frame_bb.Max.y), colWhite, 1.0f); const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; ImVec2 text_size(0.0f, 0.0f); const int buf_display_max_length = 2 * 1024 * 1024; const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; const char* buf_display_end = NULL; if (is_displaying_hint) { buf_display = hint; buf_display_end = hint + strlen(hint); } if (render_cursor || render_selection) { IM_ASSERT(state != NULL); if (!is_displaying_hint) buf_display_end = buf_display + state->CurLenA; const ImWchar* text_begin = state->TextW.Data; ImVec2 cursor_offset, select_start_offset; { const ImWchar* searches_input_ptr[2] = { NULL, NULL }; int searches_result_line_no[2] = { -1000, -1000 }; int searches_remaining = 0; if (render_cursor) { searches_input_ptr[0] = text_begin + state->Stb.cursor; searches_result_line_no[0] = -1; searches_remaining++; } if (render_selection) { searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); searches_result_line_no[1] = -1; searches_remaining++; } searches_remaining += is_multiline ? 1 : 0; int line_count = 0; for (const ImWchar* s = text_begin; *s != 0; s++) if (*s == '\n') { line_count++; if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; } if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; } } line_count++; if (searches_result_line_no[0] == -1) searches_result_line_no[0] = line_count; if (searches_result_line_no[1] == -1) searches_result_line_no[1] = line_count; cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; cursor_offset.y = searches_result_line_no[0] * g.FontSize; if (searches_result_line_no[1] >= 0) { select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; select_start_offset.y = searches_result_line_no[1] * g.FontSize; } if (is_multiline) text_size = ImVec2(inner_size.x, line_count * g.FontSize); } if (render_cursor && state->CursorFollow) { if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) { const float scroll_increment_x = inner_size.x * 0.25f; const float visible_width = inner_size.x - style.FramePadding.x; if (cursor_offset.x < state->ScrollX) state->ScrollX = IM_FLOOR(ImMax(0.0f, cursor_offset.x - scroll_increment_x)); else if (cursor_offset.x - visible_width >= state->ScrollX) state->ScrollX = IM_FLOOR(cursor_offset.x - visible_width + scroll_increment_x); } else { state->ScrollX = 0.0f; } if (is_multiline) { if (cursor_offset.y - g.FontSize < scroll_y) scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); else if (cursor_offset.y - inner_size.y >= scroll_y) scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f; const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y); draw_pos.y += (draw_window->Scroll.y - scroll_y); draw_window->Scroll.y = scroll_y; } state->CursorFollow = false; } const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f); if (render_selection) { const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end); ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); float bg_offy_up = is_multiline ? 0.0f : -1.0f; float bg_offy_dn = is_multiline ? 0.0f : 2.0f; ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll; for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { if (rect_pos.y > clip_rect.w + g.FontSize) break; if (rect_pos.y < clip_rect.y) { while (p < text_selected_end) if (*p++ == '\n') break; } else { ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); if (rect_size.x <= 0.0f) rect_size.x = IM_FLOOR(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); rect.ClipWith(clip_rect); if (rect.Overlaps(clip_rect)) draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); } rect_pos.x = draw_pos.x - draw_scroll.x; rect_pos.y += g.FontSize; } } if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); } if (render_cursor) { state->CursorAnim += io.DeltaTime; bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; ImVec2 cursor_screen_pos = draw_pos + cursor_offset - draw_scroll; ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); if (!is_readonly) g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); } } else { if (is_multiline) text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); else if (!is_displaying_hint && g.ActiveId == id) buf_display_end = buf_display + state->CurLenA; else if (!is_displaying_hint) buf_display_end = buf_display + strlen(buf_display); if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); } } if (is_password && !is_displaying_hint) PopFont(); if (is_multiline) { Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y)); EndChild(); EndGroup(); } if (g.LogEnabled && (!is_password || is_displaying_hint)) { LogSetNextTextDecoration("{", "}"); LogRenderedText(&draw_pos, buf_display, buf_display_end); } if (label_size.x > 0) RenderText(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y - 20.0f), label); if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited)) MarkItemEdited(id); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) return enter_pressed; else return value_changed; } bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) { return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); } bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const float square_sz = GetFrameHeight(); const float w_full = CalcItemWidth(); const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); const float w_inputs = w_full - w_button; const char* label_display_end = FindRenderedTextEnd(label); g.NextItemData.ClearFlags(); BeginGroup(); PushID(label); window->DC.CursorPos.y += 5.0f; const ImGuiColorEditFlags flags_untouched = flags; if (flags & ImGuiColorEditFlags_NoInputs) flags = (flags & (~ImGuiColorEditFlags__DisplayMask)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions; if (!(flags & ImGuiColorEditFlags_NoOptions)) ColorEditOptionsPopup(col, flags); if (!(flags & ImGuiColorEditFlags__DisplayMask)) flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DisplayMask); if (!(flags & ImGuiColorEditFlags__DataTypeMask)) flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask); if (!(flags & ImGuiColorEditFlags__PickerMask)) flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask); if (!(flags & ImGuiColorEditFlags__InputMask)) flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputMask); flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; const int components = alpha ? 4 : 3; float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB)) ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV)) { ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) { if (f[1] == 0) f[0] = g.ColorEditLastHue; if (f[2] == 0) f[1] = g.ColorEditLastSat; } } int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; bool value_changed = false; bool value_changed_as_float = false; ImVec2 pos = window->DC.CursorPos; const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f; window->DC.CursorPos.x = pos.x + inputs_offset_x; if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) { const float w_item_one = ImMax(1.0f, IM_FLOOR((w_inputs - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components)); const float w_item_last = ImMax(1.0f, IM_FLOOR(w_inputs - (w_item_one + style.ItemInnerSpacing.x) * (components - 1))); const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); static const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; static const char* fmt_table_int[3][4] = { { "%3d", "%3d", "%3d", "%3d" }, { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } }; static const char* fmt_table_float[3][4] = { { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } }; const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1; for (int n = 0; n < components; n++) { if (n > 0) SameLine(0, style.ItemInnerSpacing.x); SetNextItemWidth((n + 1 < components) ? w_item_one : w_item_last); if (flags & ImGuiColorEditFlags_Float) { value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); value_changed_as_float |= value_changed; } else { value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context"); } } else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) { char buf[64]; if (alpha) ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255)); else ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255)); SetNextItemWidth(w_inputs); if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) { value_changed = true; char* p = buf; while (*p == '#' || ImCharIsBlankA(*p)) p++; i[0] = i[1] = i[2] = i[3] = 0; if (alpha) sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); else sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context"); } ImGuiWindow* picker_active_window = NULL; if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) { const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x; window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y - 5.0f); const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); if (ColorButton("##ColorButton", col_v4, flags)) { if (!(flags & ImGuiColorEditFlags_NoPicker)) { g.ColorPickerRef = col_v4; OpenPopup("picker"); SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1, style.ItemSpacing.y)); } } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context"); if (BeginPopup("picker")) { picker_active_window = g.CurrentWindow; if (label != label_display_end) { TextEx(label, label_display_end); Spacing(); } ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; SetNextItemWidth(square_sz * 12.0f); value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); EndPopup(); } } if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) { const float text_offset_x = (flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x; window->DC.CursorPos = ImVec2(pos.x + text_offset_x, pos.y + style.FramePadding.y); TextEx(label, label_display_end); } if (value_changed && picker_active_window == NULL) { if (!value_changed_as_float) for (int n = 0; n < 4; n++) f[n] = i[n] / 255.0f; if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB)) { g.ColorEditLastHue = f[0]; g.ColorEditLastSat = f[1]; ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); memcpy(g.ColorEditLastColor, f, sizeof(float) * 3); } if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV)) ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); col[0] = f[0]; col[1] = f[1]; col[2] = f[2]; if (alpha) col[3] = f[3]; } PopID(); EndGroup(); if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) { bool accepted_drag_drop = false; if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) { memcpy((float*)col, payload->Data, sizeof(float) * 3); value_changed = accepted_drag_drop = true; } if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) { memcpy((float*)col, payload->Data, sizeof(float) * components); value_changed = accepted_drag_drop = true; } if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV)) ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]); EndDragDropTarget(); } if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) window->DC.LastItemId = g.ActiveId; if (value_changed) MarkItemEdited(window->DC.LastItemId); window->DC.CursorPos.y += 10.0f; return value_changed; } bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) { float col4[4] = { col[0], col[1], col[2], 1.0f }; if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) return false; col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; return true; } static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha) { ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha); ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32(0,0,0,alpha8)); ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32(255,255,255,alpha8)); ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32(0,0,0,alpha8)); ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32(255,255,255,alpha8)); } bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImDrawList* draw_list = window->DrawList; ImGuiStyle& style = g.Style; ImGuiIO& io = g.IO; const float width = CalcItemWidth(); g.NextItemData.ClearFlags(); PushID(label); BeginGroup(); if (!(flags & ImGuiColorEditFlags_NoSidePreview)) flags |= ImGuiColorEditFlags_NoSmallPreview; if (!(flags & ImGuiColorEditFlags_NoOptions)) ColorPickerOptionsPopup(col, flags); if (!(flags & ImGuiColorEditFlags__PickerMask)) flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask; if (!(flags & ImGuiColorEditFlags__InputMask)) flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__InputMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__InputMask; IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); if (!(flags & ImGuiColorEditFlags_NoOptions)) flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); ImVec2 picker_pos = window->DC.CursorPos; float square_sz = GetFrameHeight(); float bars_width = square_sz; float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; float bars_triangles_half_sz = IM_FLOOR(bars_width * 0.20f); float backup_initial_col[4]; memcpy(backup_initial_col, col, components * sizeof(float)); float wheel_thickness = sv_picker_size * 0.08f; float wheel_r_outer = sv_picker_size * 0.50f; float wheel_r_inner = wheel_r_outer - wheel_thickness; ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f); float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); float H = col[0], S = col[1], V = col[2]; float R = col[0], G = col[1], B = col[2]; if (flags & ImGuiColorEditFlags_InputRGB) { ColorConvertRGBtoHSV(R, G, B, H, S, V); if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) { if (S == 0) H = g.ColorEditLastHue; if (V == 0) S = g.ColorEditLastSat; } } else if (flags & ImGuiColorEditFlags_InputHSV) { ColorConvertHSVtoRGB(H, S, V, R, G, B); } bool value_changed = false, value_changed_h = false, value_changed_sv = false; PushItemFlag(ImGuiItemFlags_NoNav, true); if (flags & ImGuiColorEditFlags_PickerHueWheel) { InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); if (IsItemActive()) { ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; ImVec2 current_off = g.IO.MousePos - wheel_center; float initial_dist2 = ImLengthSqr(initial_off); if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1)) { H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f; if (H < 0.0f) H += 1.0f; value_changed = value_changed_h = true; } float cos_hue_angle = ImCos(-H * 2.0f * IM_PI); float sin_hue_angle = ImSin(-H * 2.0f * IM_PI); if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) { ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); float uu, vv, ww; ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); V = ImClamp(1.0f - vv, 0.0001f, 1.0f); S = ImClamp(uu / V, 0.0001f, 1.0f); value_changed = value_changed_sv = true; } } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context"); } else if (flags & ImGuiColorEditFlags_PickerHueBar) { InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); if (IsItemActive()) { S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1)); V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); value_changed = value_changed_sv = true; } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context"); SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); if (IsItemActive()) { H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); value_changed = value_changed_h = true; } } if (alpha_bar) { SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); if (IsItemActive()) { col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); value_changed = true; } } PopItemFlag(); if (!(flags & ImGuiColorEditFlags_NoSidePreview)) { SameLine(0, style.ItemInnerSpacing.x); BeginGroup(); } if (!(flags & ImGuiColorEditFlags_NoLabel)) { const char* label_display_end = FindRenderedTextEnd(label); if (label != label_display_end) { if ((flags & ImGuiColorEditFlags_NoSidePreview)) SameLine(0, style.ItemInnerSpacing.x); TextEx(label, label_display_end); } } if (!(flags & ImGuiColorEditFlags_NoSidePreview)) { PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); if ((flags & ImGuiColorEditFlags_NoLabel)) Text("Current"); ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip; ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)); if (ref_col != NULL) { Text("Original"); ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2))) { memcpy(col, ref_col, components * sizeof(float)); value_changed = true; } } PopItemFlag(); EndGroup(); } if (value_changed_h || value_changed_sv) { if (flags & ImGuiColorEditFlags_InputRGB) { ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10 * 1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]); g.ColorEditLastHue = H; g.ColorEditLastSat = S; memcpy(g.ColorEditLastColor, col, sizeof(float) * 3); } else if (flags & ImGuiColorEditFlags_InputHSV) { col[0] = H; col[1] = S; col[2] = V; } } bool value_changed_fix_hue_wrap = false; if ((flags & ImGuiColorEditFlags_NoInputs) == 0) { PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags__DisplayMask) == 0) if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB)) { value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap); value_changed = true; } if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags__DisplayMask) == 0) value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV); if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags__DisplayMask) == 0) value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex); PopItemWidth(); } if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB)) { float new_H, new_S, new_V; ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); if (new_H <= 0 && H > 0) { if (new_V <= 0 && V != new_V) ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); else if (new_S <= 0) ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); } } if (value_changed) { if (flags & ImGuiColorEditFlags_InputRGB) { R = col[0]; G = col[1]; B = col[2]; ColorConvertRGBtoHSV(R, G, B, H, S, V); if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) { if (S == 0) H = g.ColorEditLastHue; if (V == 0) S = g.ColorEditLastSat; } } else if (flags & ImGuiColorEditFlags_InputHSV) { H = col[0]; S = col[1]; V = col[2]; ColorConvertHSVtoRGB(H, S, V, R, G, B); } } const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha); const ImU32 col_black = IM_COL32(0,0,0,style_alpha8); const ImU32 col_white = IM_COL32(255,255,255,style_alpha8); const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8); const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) }; ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); ImVec2 sv_cursor_pos; if (flags & ImGuiColorEditFlags_PickerHueWheel) { const float aeps = 0.5f / wheel_r_outer; const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); for (int n = 0; n < 6; n++) { const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps; const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; const int vert_start_idx = draw_list->VtxBuffer.Size; draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); draw_list->PathStroke(col_white, false, wheel_thickness); const int vert_end_idx = draw_list->VtxBuffer.Size; ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner); ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner); ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]); } float cos_hue_angle = ImCos(H * 2.0f * IM_PI); float sin_hue_angle = ImSin(H * 2.0f * IM_PI); ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f); float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32); draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments); draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments); ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); ImVec2 uv_white = GetFontTexUvWhitePixel(); draw_list->PrimReserve(6, 6); draw_list->PrimVtx(tra, uv_white, hue_color32); draw_list->PrimVtx(trb, uv_white, hue_color32); draw_list->PrimVtx(trc, uv_white, col_white); draw_list->PrimVtx(tra, uv_white, 0); draw_list->PrimVtx(trb, uv_white, col_black); draw_list->PrimVtx(trc, uv_white, 0); draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f); sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); } else if (flags & ImGuiColorEditFlags_PickerHueBar) { draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white); draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black); RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f); sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S) * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); for (int i = 0; i < 6; ++i) draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]); float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size); RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha); } float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f; draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, 12); draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, 12); draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, 12); if (alpha_bar) { float alpha = ImSaturate(col[3]); ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK); float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size); RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha); } EndGroup(); if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0) value_changed = false; if (value_changed) MarkItemEdited(window->DC.LastItemId); PopID(); return value_changed; } bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiID id = window->GetID(desc_id); float default_size = GetFrameHeight(); if (size.x == 0.0f) size.x = default_size; if (size.y == 0.0f) size.y = default_size; const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); if (!ItemAdd(bb, id)) return false; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held); if (flags & ImGuiColorEditFlags_NoAlpha) flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); ImVec4 col_rgb = col; if (flags & ImGuiColorEditFlags_InputHSV) ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z); ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); float grid_step = ImMin(size.x, size.y) / 2.99f; float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); ImRect bb_inner = bb; float off = 0.0f; if ((flags & ImGuiColorEditFlags_NoBorder) == 0) { off = -0.75f; bb_inner.Expand(off); } RenderFrame(bb_inner.Min - ImVec2(1, 1), bb_inner.Max - ImVec2(0, 0), colWhite, true, 0); RenderFrame(bb_inner.Min + ImVec2(0, 0), bb_inner.Max + ImVec2(1, 1), colBlack, true, 0); if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight); window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft); } else { ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha; if (col_source.w < 1.0f) RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); else window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All); } RenderNavHighlight(bb, id); if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource()) { if (flags & ImGuiColorEditFlags_NoAlpha) SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once); else SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once); ColorButton(desc_id, col, flags); SameLine(); TextEx("Color"); EndDragDropSource(); } if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); return pressed; } void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) { ImGuiContext& g = *GImGui; if ((flags & ImGuiColorEditFlags__DisplayMask) == 0) flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DisplayMask; if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0) flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask; if ((flags & ImGuiColorEditFlags__PickerMask) == 0) flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask; if ((flags & ImGuiColorEditFlags__InputMask) == 0) flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputMask; IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DataTypeMask)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); g.ColorEditOptions = flags; } void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) { ImGuiContext& g = *GImGui; BeginTooltipEx(0, ImGuiTooltipFlags_OverridePreviousTooltip); const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; if (text_end > text) { TextEx(text, text_end); Separator(); } ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); SameLine(); if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags__InputMask)) { if (flags & ImGuiColorEditFlags_NoAlpha) Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); else Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); } else if (flags & ImGuiColorEditFlags_InputHSV) { if (flags & ImGuiColorEditFlags_NoAlpha) Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]); else Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]); } EndTooltip(); } void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) { bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__DisplayMask); bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) return; ImGuiContext& g = *GImGui; ImGuiColorEditFlags opts = g.ColorEditOptions; if (allow_opt_inputs) { if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayRGB; if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHSV; if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHex; } if (allow_opt_datatype) { if (allow_opt_inputs) Separator(); if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; } if (allow_opt_inputs || allow_opt_datatype) Separator(); if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy"); if (BeginPopup("Copy")) { int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); char buf[64]; ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); if (Selectable(buf)) SetClipboardText(buf); ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); if (Selectable(buf)) SetClipboardText(buf); ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb); if (Selectable(buf)) SetClipboardText(buf); if (!(flags & ImGuiColorEditFlags_NoAlpha)) { ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca); if (Selectable(buf)) SetClipboardText(buf); } EndPopup(); } g.ColorEditOptions = opts; EndPopup(); } void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags) { bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context")) return; ImGuiContext& g = *GImGui; if (allow_opt_picker) { ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); PushItemWidth(picker_size.x); for (int picker_type = 0; picker_type < 2; picker_type++) { if (picker_type > 0) Separator(); PushID(picker_type); ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha); if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar; if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; ImVec2 backup_pos = GetCursorScreenPos(); if (Selectable("##selectable", false, 0, picker_size)) g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask); SetCursorScreenPos(backup_pos); ImVec4 previewing_ref_col; memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4)); ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags); PopID(); } PopItemWidth(); } if (allow_opt_alpha_bar) { if (allow_opt_picker) Separator(); CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); } EndPopup(); } bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) { va_list args; va_start(args, fmt); bool is_open = TreeNodeExV(str_id, 0, fmt, args); va_end(args); return is_open; } bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) { va_list args; va_start(args, fmt); bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); va_end(args); return is_open; } bool ImGui::TreeNode(const char* label) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; return TreeNodeBehavior(window->GetID(label), 0, label, NULL); } bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) { return TreeNodeExV(str_id, 0, fmt, args); } bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) { return TreeNodeExV(ptr_id, 0, fmt, args); } bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; return TreeNodeBehavior(window->GetID(label), flags, label, NULL); } bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) { va_list args; va_start(args, fmt); bool is_open = TreeNodeExV(str_id, flags, fmt, args); va_end(args); return is_open; } bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) { va_list args; va_start(args, fmt); bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); va_end(args); return is_open; } bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); } bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); } bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) { if (flags & ImGuiTreeNodeFlags_Leaf) return true; ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiStorage* storage = window->DC.StateStorage; bool is_open; if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen) { if (g.NextItemData.OpenCond & ImGuiCond_Always) { is_open = g.NextItemData.OpenVal; storage->SetInt(id, is_open); } else { const int stored_value = storage->GetInt(id, -1); if (stored_value == -1) { is_open = g.NextItemData.OpenVal; storage->SetInt(id, is_open); } else { is_open = stored_value != 0; } } } else { is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; } if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand) is_open = true; return is_open; } bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y)); if (!label_end) label_end = FindRenderedTextEnd(label); const ImVec2 label_size = CalcTextSize(label, label_end, false); const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); ImRect frame_bb; frame_bb.Min.x = (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x; frame_bb.Min.y = window->DC.CursorPos.y; frame_bb.Max.x = window->WorkRect.Max.x; frame_bb.Max.y = window->DC.CursorPos.y + frame_height; if (display_frame) { frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f); frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f); } const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f); ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y); ItemSize(ImVec2(text_width, frame_height), padding.y); ImRect interact_bb = frame_bb; if (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth)) == 0) interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f; const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; bool is_open = TreeNodeBehaviorIsOpen(id, flags); if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth); bool item_add = ItemAdd(interact_bb, id); window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; window->DC.LastItemDisplayRect = frame_bb; if (!item_add) { if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); return is_open; } ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None; if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) button_flags |= ImGuiButtonFlags_AllowItemOverlap; if (!is_leaf) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x; const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x; const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2); if (window != g.HoveredWindow || !is_mouse_x_over_arrow) button_flags |= ImGuiButtonFlags_NoKeyModifiers; if (is_mouse_x_over_arrow) button_flags |= ImGuiButtonFlags_PressedOnClick; else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; else button_flags |= ImGuiButtonFlags_PressedOnClickRelease; bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0; const bool was_selected = selected; bool hovered, held; bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); bool toggled = false; if (!is_leaf) { if (pressed && g.DragDropHoldJustPressedId != id) { if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id)) toggled = true; if (flags & ImGuiTreeNodeFlags_OpenOnArrow) toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseDoubleClicked[0]) toggled = true; } else if (pressed && g.DragDropHoldJustPressedId == id) { IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold); if (!is_open) toggled = true; } if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open) { toggled = true; NavMoveRequestCancel(); } if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) { toggled = true; NavMoveRequestCancel(); } if (toggled) { is_open = !is_open; window->DC.StateStorage->SetInt(id, is_open); window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledOpen; } } if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) SetItemAllowOverlap(); if (selected != was_selected) window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection; const ImU32 text_col = GetColorU32(ImGuiCol_Text); ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin; if (display_frame) { const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); RenderNavHighlight(frame_bb, id, nav_highlight_flags); if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); else if (!is_leaf) RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); else text_pos.x -= text_offset_x; if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton) frame_bb.Max.x -= g.FontSize + style.FramePadding.x; if (g.LogEnabled) LogSetNextTextDecoration("###", "###"); RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); } else { if (hovered || selected) { const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); RenderNavHighlight(frame_bb, id, nav_highlight_flags); } if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); else if (!is_leaf) RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f); if (g.LogEnabled) LogSetNextTextDecoration(">", NULL); RenderText(text_pos, label, label_end, false); } if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); return is_open; } void ImGui::TreePush(const char* str_id) { ImGuiWindow* window = GetCurrentWindow(); Indent(); window->DC.TreeDepth++; PushID(str_id ? str_id : "#TreePush"); } void ImGui::TreePush(const void* ptr_id) { ImGuiWindow* window = GetCurrentWindow(); Indent(); window->DC.TreeDepth++; PushID(ptr_id ? ptr_id : (const void*)"#TreePush"); } void ImGui::TreePushOverrideID(ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; Indent(); window->DC.TreeDepth++; window->IDStack.push_back(id); } void ImGui::TreePop() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; Unindent(); window->DC.TreeDepth--; ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) if (g.NavIdIsAlive && (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask)) { SetNavID(window->IDStack.back(), g.NavLayer, 0); NavMoveRequestCancel(); } window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1; IM_ASSERT(window->IDStack.Size > 1); PopID(); } float ImGui::GetTreeNodeToLabelSpacing() { ImGuiContext& g = *GImGui; return g.FontSize + (g.Style.FramePadding.x * 2.0f); } void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond) { ImGuiContext& g = *GImGui; if (g.CurrentWindow->SkipItems) return; g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen; g.NextItemData.OpenVal = is_open; g.NextItemData.OpenCond = cond ? cond : ImGuiCond_Always; } bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label); } bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; if (p_visible && !*p_visible) return false; ImGuiID id = window->GetID(label); flags |= ImGuiTreeNodeFlags_CollapsingHeader; if (p_visible) flags |= ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_ClipLabelForTrailingButton; bool is_open = TreeNodeBehavior(id, flags, label); if (p_visible != NULL) { ImGuiContext& g = *GImGui; ImGuiLastItemDataBackup last_item_backup; float button_size = g.FontSize; float button_x = ImMax(window->DC.LastItemRect.Min.x, window->DC.LastItemRect.Max.x - g.Style.FramePadding.x * 2.0f - button_size); float button_y = window->DC.LastItemRect.Min.y; ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id); if (CloseButton(close_button_id, ImVec2(button_x, button_y))) *p_visible = false; last_item_backup.Restore(); } return is_open; } bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg, bool ncolorhovered) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; ImGuiID id = window->GetID(label); ImVec2 label_size = CalcTextSize(label, NULL, true); ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); ImVec2 pos = window->DC.CursorPos; pos.y += window->DC.CurrLineTextBaseOffset; ItemSize(size, 0.0f); const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x; const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth)) size.x = ImMax(label_size.x, max_x - min_x); const ImVec2 text_min = pos; const ImVec2 text_max(min_x + size.x, pos.y + size.y); ImRect bb(min_x, pos.y, text_max.x, text_max.y); if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) { const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; const float spacing_y = style.ItemSpacing.y; const float spacing_L = IM_FLOOR(spacing_x * 0.50f); const float spacing_U = IM_FLOOR(spacing_y * 0.50f); bb.Min.x -= spacing_L; bb.Min.y -= spacing_U; bb.Max.x += (spacing_x - spacing_L); bb.Max.y += (spacing_y - spacing_U); } const float backup_clip_rect_min_x = window->ClipRect.Min.x; const float backup_clip_rect_max_x = window->ClipRect.Max.x; if (span_all_columns) { window->ClipRect.Min.x = window->ParentWorkRect.Min.x; window->ClipRect.Max.x = window->ParentWorkRect.Max.x; } bool item_add; if (flags & ImGuiSelectableFlags_Disabled) { ImGuiItemFlags backup_item_flags = window->DC.ItemFlags; window->DC.ItemFlags |= ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNavDefaultFocus; item_add = ItemAdd(bb, id); window->DC.ItemFlags = backup_item_flags; } else { item_add = ItemAdd(bb, id); } if (span_all_columns) { window->ClipRect.Min.x = backup_clip_rect_min_x; window->ClipRect.Max.x = backup_clip_rect_max_x; } if (!item_add) return false; if (span_all_columns && window->DC.CurrentColumns) PushColumnsBackground(); else if (span_all_columns && g.CurrentTable) TablePushBackgroundChannel(); ImGuiButtonFlags button_flags = 0; if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } if (flags & ImGuiSelectableFlags_Disabled) { button_flags |= ImGuiButtonFlags_Disabled; } if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; } if (flags & ImGuiSelectableFlags_Disabled) selected = false; const bool was_selected = selected; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) { if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { g.NavDisableHighlight = true; SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent); } } if (pressed) MarkItemEdited(id); if (flags & ImGuiSelectableFlags_AllowItemOverlap) SetItemAllowOverlap(); if (selected != was_selected) window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection; if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) hovered = true; if (ncolorhovered) { if (selected) { ImU32 col = GetColorU32(ImGuiCol_HeaderActive); RenderFrame(bb.Min, bb.Max, col, false, 0.0f); RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); } } else { if (hovered || selected) { const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(bb.Min, bb.Max, col, false, 0.0f); RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); } } if (span_all_columns && window->DC.CurrentColumns) PopColumnsBackground(); else if (span_all_columns && g.CurrentTable) TablePopBackgroundChannel(); if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); if (ncolorhovered) { RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); } else { if (selected || hovered) RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb, colBlack); else RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); } if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) CloseCurrentPopup(); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); return pressed; } bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) { if (Selectable(label, *p_selected, flags, size_arg)) { *p_selected = !*p_selected; return true; } return false; } bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; const ImGuiStyle& style = g.Style; const ImGuiID id = GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); ImVec2 size = ImFloor(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f)); ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Min.y - 1.0f), ImVec2(frame_bb.Max.x , frame_bb.Min.y - 1.0f), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Min.y - 1.0f), ImVec2(frame_bb.Min.x - 1.0f, frame_bb.Max.y), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Max.x , frame_bb.Min.y), ImVec2(frame_bb.Max.x , frame_bb.Max.y), colWhite, 1.0f); window->DrawList->AddLine(ImVec2(frame_bb.Min.x , frame_bb.Max.y), ImVec2(frame_bb.Max.x , frame_bb.Max.y), colWhite, 1.0f); ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); g.NextItemData.ClearFlags(); if (!IsRectVisible(bb.Min, bb.Max)) { ItemSize(bb.GetSize(), style.FramePadding.y); ItemAdd(bb, 0, &frame_bb); return false; } BeginGroup(); if (label_size.x > 0.0f) { ImVec2 label_pos = ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y - 20.0f); RenderText(label_pos, label); window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); } BeginChildFrame(id, frame_bb.GetSize()); return true; } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) { ImGuiContext& g = *GImGui; float height_in_items_f = (height_in_items < 0 ? ImMin(items_count, 7) : height_in_items) + 0.25f; ImVec2 size; size.x = 0.0f; size.y = GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f; return BeginListBox(label, size); } #endif void ImGui::EndListBox() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?"); EndChildFrame(); 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) { ImGuiContext& g = *GImGui; if (height_in_items < 0) height_in_items = ImMin(items_count, 7); float height_in_items_f = height_in_items + 0.25f; ImVec2 size(0.0f, ImFloor(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f)); if (!BeginListBox(label, size)) return false; bool value_changed = false; ImGuiListClipper clipper; clipper.Begin(items_count, GetTextLineHeightWithSpacing()); while (clipper.Step()) for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { const char* item_text; if (!items_getter(data, i, &item_text)) item_text = "*Unknown item*"; PushID(i); const bool item_selected = (i == *current_item); if (Selectable(item_text, item_selected)) { *current_item = i; value_changed = true; } if (item_selected) SetItemDefaultFocus(); PopID(); } EndListBox(); if (value_changed) MarkItemEdited(g.CurrentWindow->DC.LastItemId); return value_changed; } int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return -1; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); if (frame_size.x == 0.0f) frame_size.x = CalcItemWidth(); if (frame_size.y == 0.0f) frame_size.y = label_size.y + (style.FramePadding.y * 2); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); 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)); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, 0, &frame_bb)) return -1; const bool hovered = ItemHoverable(frame_bb, id); if (scale_min == FLT_MAX || scale_max == FLT_MAX) { float v_min = FLT_MAX; float v_max = -FLT_MAX; for (int i = 0; i < values_count; i++) { const float v = values_getter(data, i); if (v != v) continue; v_min = ImMin(v_min, v); v_max = ImMax(v_max, v); } if (scale_min == FLT_MAX) scale_min = v_min; if (scale_max == FLT_MAX) scale_max = v_max; } RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1; int idx_hovered = -1; if (values_count >= values_count_min) { int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); if (hovered && inner_bb.Contains(g.IO.MousePos)) { const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); const int v_idx = (int)(t * item_count); IM_ASSERT(v_idx >= 0 && v_idx < values_count); const float v0 = values_getter(data, (v_idx + values_offset) % values_count); const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); if (plot_type == ImGuiPlotType_Lines) SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1); else if (plot_type == ImGuiPlotType_Histogram) SetTooltip("%d: %8.4g", v_idx, v0); idx_hovered = v_idx; } const float t_step = 1.0f / (float)res_w; const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); float v0 = values_getter(data, (0 + values_offset) % values_count); float t0 = 0.0f; ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); for (int n = 0; n < res_w; n++) { const float t1 = t0 + t_step; const int v1_idx = (int)(t0 * item_count + 0.5f); IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); if (plot_type == ImGuiPlotType_Lines) { window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); } else if (plot_type == ImGuiPlotType_Histogram) { if (pos1.x >= pos0.x + 2.0f) pos1.x -= 1.0f; window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); } t0 = t1; tp0 = tp1; } } if (overlay_text) RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); return idx_hovered; } struct ImGuiPlotArrayGetterData { const float* Values; int Stride; ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } }; static float Plot_ArrayGetter(void* data, int idx) { ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); return v; } void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) { ImGuiPlotArrayGetterData data(values, stride); PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) { PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) { ImGuiPlotArrayGetterData data(values, stride); PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) { PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } void ImGui::Value(const char* prefix, bool b) { Text("%s: %s", prefix, (b ? "true" : "false")); } void ImGui::Value(const char* prefix, int v) { Text("%s: %d", prefix, v); } void ImGui::Value(const char* prefix, unsigned int v) { Text("%s: %d", prefix, v); } void ImGui::Value(const char* prefix, float v, const char* float_format) { if (float_format) { char fmt[64]; ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); Text(fmt, prefix, v); } else { Text("%s: %.3f", prefix, v); } } void ImGuiMenuColumns::Update(int count, float spacing, bool clear) { IM_ASSERT(count == IM_ARRAYSIZE(Pos)); IM_UNUSED(count); Width = NextWidth = 0.0f; Spacing = spacing; if (clear) memset(NextWidths, 0, sizeof(NextWidths)); for (int i = 0; i < IM_ARRAYSIZE(Pos); i++) { if (i > 0 && NextWidths[i] > 0.0f) Width += Spacing; Pos[i] = IM_FLOOR(Width); Width += NextWidths[i]; NextWidths[i] = 0.0f; } } float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) { NextWidth = 0.0f; NextWidths[0] = ImMax(NextWidths[0], w0); NextWidths[1] = ImMax(NextWidths[1], w1); NextWidths[2] = ImMax(NextWidths[2], w2); for (int i = 0; i < IM_ARRAYSIZE(Pos); i++) NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); return ImMax(Width, NextWidth); } float ImGuiMenuColumns::CalcExtraSpace(float avail_w) const { return ImMax(0.0f, avail_w - Width); } bool ImGui::BeginMenuBar() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; if (!(window->Flags & ImGuiWindowFlags_MenuBar)) return false; IM_ASSERT(!window->DC.MenuBarAppending); BeginGroup(); PushID("##menubar"); ImRect bar_rect = window->MenuBarRect(); ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize), IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), IM_ROUND(bar_rect.Max.y)); clip_rect.ClipWith(window->OuterRectClipped); PushClipRect(clip_rect.Min, clip_rect.Max, false); window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y); window->DC.LayoutType = ImGuiLayoutType_Horizontal; window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; window->DC.MenuBarAppending = true; AlignTextToFramePadding(); return true; } void ImGui::EndMenuBar() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) { ImGuiWindow* nav_earliest_child = g.NavWindow; while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) nav_earliest_child = nav_earliest_child->ParentWindow; if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None) { const ImGuiNavLayer layer = ImGuiNavLayer_Menu; IM_ASSERT(window->DC.NavLayerActiveMaskNext & (1 << layer)); FocusWindow(window); SetNavIDWithRectRel(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); g.NavLayer = layer; g.NavDisableHighlight = true; g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; NavMoveRequestCancel(); } } IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); IM_ASSERT(window->DC.MenuBarAppending); PopClipRect(); PopID(); window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; g.GroupStack.back().EmitItem = false; EndGroup(); window->DC.LayoutType = ImGuiLayoutType_Vertical; window->DC.NavLayerCurrent = ImGuiNavLayer_Main; window->DC.MenuBarAppending = false; } bool ImGui::BeginMainMenuBar() { ImGuiContext& g = *GImGui; g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); SetNextWindowPos(ImVec2(0.0f, 0.0f)); SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y)); PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar(); PopStyleVar(2); g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); if (!is_open) { End(); return false; } return true; } void ImGui::EndMainMenuBar() { EndMenuBar(); ImGuiContext& g = *GImGui; if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest) FocusTopMostWindowUnderOne(g.NavWindow, NULL); End(); } bool ImGui::BeginMenu(const char* label, bool enabled) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None); ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) flags |= ImGuiWindowFlags_ChildWindow; if (g.MenusIdSubmittedThisFrame.contains(id)) { if (menu_is_open) menu_is_open = BeginPopupEx(id, flags); else g.NextWindowData.ClearFlags(); return menu_is_open; } g.MenusIdSubmittedThisFrame.push_back(id); ImVec2 label_size = CalcTextSize(label, NULL, true); bool pressed; bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back()); ImGuiWindow* backed_nav_window = g.NavWindow; if (menuset_is_open) g.NavWindow = window; ImVec2 popup_pos, pos = window->DC.CursorPos; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); float w = label_size.x; pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); PopStyleVar(); window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); } else { popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(min_w, 0.0f)); ImU32 text_col = GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled); RenderArrow(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right); } const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); if (menuset_is_open) g.NavWindow = backed_nav_window; bool want_open = false; bool want_close = false; if (window->DC.LayoutType == ImGuiLayoutType_Vertical) { bool moving_toward_other_child_menu = false; ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL; if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar)) { ImRect next_window_rect = child_menu_window->Rect(); ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); } if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu) want_close = true; if (!menu_is_open && hovered && pressed) want_open = true; else if (!menu_is_open && hovered && !moving_toward_other_child_menu) want_open = true; if (g.NavActivateId == id) { want_close = menu_is_open; want_open = !menu_is_open; } if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) { want_open = true; NavMoveRequestCancel(); } } else { if (menu_is_open && pressed && menuset_is_open) { want_close = true; want_open = menu_is_open = false; } else if (pressed || (hovered && menuset_is_open && !menu_is_open)) { want_open = true; } else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) { want_open = true; NavMoveRequestCancel(); } } if (!enabled) want_close = true; if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None)) ClosePopupToLevel(g.BeginPopupStack.Size, true); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { OpenPopup(label); return false; } menu_is_open |= want_open; if (want_open) OpenPopup(label); if (menu_is_open) { SetNextWindowPos(popup_pos, ImGuiCond_Always); menu_is_open = BeginPopupEx(id, flags); } else { g.NextWindowData.ClearFlags(); } return menu_is_open; } void ImGui::EndMenu() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical) { ClosePopupToLevel(g.BeginPopupStack.Size, true); NavMoveRequestCancel(); } EndPopup(); } bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; ImVec2 pos = window->DC.CursorPos; ImVec2 label_size = CalcTextSize(label, NULL, true); ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); bool pressed; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { float w = label_size.x; window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); PopStyleVar(); window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); } else { float shortcut_w = shortcut ? CalcTextSize(shortcut, NULL).x : 0.0f; float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); pressed = Selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); if (shortcut_w > 0.0f) { PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); PopStyleColor(); } if (selected) RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); } IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); return pressed; } bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) { if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) { if (p_selected) *p_selected = !*p_selected; return true; } return false; } struct ImGuiTabBarSection { int TabCount; float Width; float Spacing; ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); } }; namespace ImGui { static void TabBarLayout(ImGuiTabBar* tab_bar); static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label); static float TabBarCalcMaxTabWidth(); static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImGuiTabBarSection* sections); static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar); static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar); } ImGuiTabBar::ImGuiTabBar() { memset(this, 0, sizeof(*this)); CurrFrameVisible = PrevFrameVisible = -1; LastTabItemIdx = -1; } static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs) { const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; const int a_section = (a->Flags & ImGuiTabItemFlags_Leading) ? 0 : (a->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; const int b_section = (b->Flags & ImGuiTabItemFlags_Leading) ? 0 : (b->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; if (a_section != b_section) return a_section - b_section; return (int)(a->IndexDuringLayout - b->IndexDuringLayout); } static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs) { const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; return (int)(a->BeginOrder - b->BeginOrder); } static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref) { ImGuiContext& g = *GImGui; return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index); } static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; if (g.TabBars.Contains(tab_bar)) return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar)); return ImGuiPtrOrIndex(tab_bar); } bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; ImGuiID id = window->GetID(str_id); ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id); ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2); tab_bar->ID = id; return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused); } bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; if ((flags & ImGuiTabBarFlags_DockNode) == 0) PushOverrideID(tab_bar->ID); g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar)); g.CurrentTabBar = tab_bar; tab_bar->BackupCursorPos = window->DC.CursorPos; if (tab_bar->CurrFrameVisible == g.FrameCount) { window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY); tab_bar->BeginCount++; return true; } if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable))) if (tab_bar->Tabs.Size > 1) ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); tab_bar->TabsAddedNew = false; if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) flags |= ImGuiTabBarFlags_FittingPolicyDefault_; tab_bar->Flags = flags; tab_bar->BarRect = tab_bar_bb; tab_bar->WantLayout = true; tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible; tab_bar->CurrFrameVisible = g.FrameCount; tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight; tab_bar->CurrTabsContentsHeight = 0.0f; tab_bar->ItemSpacingY = g.Style.ItemSpacing.y; tab_bar->FramePadding = g.Style.FramePadding; tab_bar->TabsActiveCount = 0; tab_bar->BeginCount = 1; window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY); const ImU32 col = colWhite; const float y = tab_bar->BarRect.Max.y - 1.0f; { float separator_min_x = tab_bar->BarRect.Min.x - IM_FLOOR(window->WindowPadding.x * 0.5f) + 2.0f ; float separator_max_x = tab_bar->BarRect.Max.x + IM_FLOOR(window->WindowPadding.x * 0.5f) - 2.0f ; window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f); float vosem = 380.0f; window->DrawList->AddLine(ImVec2(separator_min_x, y + vosem), ImVec2(separator_max_x, y + vosem), colBlack, 1.0f); window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_min_x, y + vosem), col, 1.0f); window->DrawList->AddLine(ImVec2(separator_max_x, y), ImVec2(separator_max_x, y + vosem), colBlack, 1.0f); } return true; } void ImGui::EndTabBar() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!"); return; } if (tab_bar->WantLayout) TabBarLayout(tab_bar); const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing) { tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight); window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight; } else { window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight; } if (tab_bar->BeginCount > 1) window->DC.CursorPos = tab_bar->BackupCursorPos; if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) PopID(); g.CurrentTabBarStack.pop_back(); g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back()); } static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; tab_bar->WantLayout = false; int tab_dst_n = 0; bool need_sort_by_section = false; ImGuiTabBarSection sections[3]; for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n]; if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose) { if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; } if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; } if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; } continue; } if (tab_dst_n != tab_src_n) tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n]; tab = &tab_bar->Tabs[tab_dst_n]; tab->IndexDuringLayout = (ImS16)tab_dst_n; int curr_tab_section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; if (tab_dst_n > 0) { ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1]; int prev_tab_section_n = (prev_tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (prev_tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; if (curr_tab_section_n == 0 && prev_tab_section_n != 0) need_sort_by_section = true; if (prev_tab_section_n == 2 && curr_tab_section_n != 2) need_sort_by_section = true; } sections[curr_tab_section_n].TabCount++; tab_dst_n++; } if (tab_bar->Tabs.Size != tab_dst_n) tab_bar->Tabs.resize(tab_dst_n); if (need_sort_by_section) ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection); sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; ImGuiID scroll_track_selected_tab_id = 0; if (tab_bar->NextSelectedTabId) { tab_bar->SelectedTabId = tab_bar->NextSelectedTabId; tab_bar->NextSelectedTabId = 0; scroll_track_selected_tab_id = tab_bar->SelectedTabId; } if (tab_bar->ReorderRequestTabId != 0) { if (TabBarProcessReorder(tab_bar)) if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId) scroll_track_selected_tab_id = tab_bar->ReorderRequestTabId; tab_bar->ReorderRequestTabId = 0; } const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0; if (tab_list_popup_button) if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount }; g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); ImGuiTabItem* most_recently_selected_tab = NULL; int curr_section_n = -1; bool found_selected_tab_id = false; for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible); if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button)) most_recently_selected_tab = tab; if (tab->ID == tab_bar->SelectedTabId) found_selected_tab_id = true; if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID) scroll_track_selected_tab_id = tab->ID; const char* tab_name = tab_bar->GetTabName(tab); const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true; tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x; int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; ImGuiTabBarSection* section = §ions[section_n]; section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); curr_section_n = section_n; int shrink_buffer_index = shrink_buffer_indexes[section_n]++; g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n; g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth; IM_ASSERT(tab->ContentWidth > 0.0f); tab->Width = tab->ContentWidth; } tab_bar->WidthAllTabsIdeal = 0.0f; for (int section_n = 0; section_n < 3; section_n++) tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) { scroll_track_selected_tab_id = scroll_track_selected_tab->ID; if (!(scroll_track_selected_tab->Flags & ImGuiTabItemFlags_Button)) tab_bar->SelectedTabId = scroll_track_selected_tab_id; } float section_0_w = sections[0].Width + sections[0].Spacing; float section_1_w = sections[1].Width + sections[1].Spacing; float section_2_w = sections[2].Width + sections[2].Spacing; bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth(); float width_excess; if (central_section_is_visible) width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); else width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); if (width_excess > 0.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible)) { int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0); ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess); for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); if (shrinked_width < 0.0f) continue; int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; sections[section_n].Width -= (tab->Width - shrinked_width); tab->Width = shrinked_width; } } int section_tab_index = 0; float tab_offset = 0.0f; tab_bar->WidthAllTabs = 0.0f; for (int section_n = 0; section_n < 3; section_n++) { ImGuiTabBarSection* section = §ions[section_n]; if (section_n == 2) tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset); for (int tab_n = 0; tab_n < section->TabCount; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n]; tab->Offset = tab_offset; tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); } tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); tab_offset += section->Spacing; section_tab_index += section->TabCount; } if (found_selected_tab_id == false) tab_bar->SelectedTabId = 0; if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL) scroll_track_selected_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID; tab_bar->VisibleTabId = tab_bar->SelectedTabId; tab_bar->VisibleTabWasSubmitted = false; if (scroll_track_selected_tab_id) if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id)) TabBarScrollToTab(tab_bar, scroll_track_selected_tab, sections); tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim); tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget); if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget) { tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize); tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f); const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize); tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed); } else { tab_bar->ScrollingSpeed = 0.0f; } tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing; tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing; if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) tab_bar->TabsNames.Buf.resize(0); ImGuiWindow* window = g.CurrentWindow; window->DC.CursorPos = tab_bar->BarRect.Min; ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y); window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal); } static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label) { if (tab_bar->Flags & ImGuiTabBarFlags_DockNode) { ImGuiID id = ImHashStr(label); KeepAliveID(id); return id; } else { ImGuiWindow* window = GImGui->CurrentWindow; return window->GetID(label); } } static float ImGui::TabBarCalcMaxTabWidth() { ImGuiContext& g = *GImGui; return g.FontSize * 20.0f; } ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id) { if (tab_id != 0) for (int n = 0; n < tab_bar->Tabs.Size; n++) if (tab_bar->Tabs[n].ID == tab_id) return &tab_bar->Tabs[n]; return NULL; } void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) { if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) tab_bar->Tabs.erase(tab); if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; } if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; } if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; } } void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { IM_ASSERT(!(tab->Flags & ImGuiTabItemFlags_Button)); if (!(tab->Flags & ImGuiTabItemFlags_UnsavedDocument)) { tab->WantClose = true; if (tab_bar->VisibleTabId == tab->ID) { tab->LastFrameVisible = -1; tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0; } } else { if (tab_bar->VisibleTabId != tab->ID) tab_bar->NextSelectedTabId = tab->ID; } } static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling) { scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth()); return ImMax(scrolling, 0.0f); } static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImGuiTabBarSection* sections) { if (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) return; ImGuiContext& g = *GImGui; float margin = g.FontSize * 1.0f; int order = tab_bar->GetTabOrder(tab); float scrollable_width = tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing; float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f); float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f); tab_bar->ScrollingTargetDistToVisibility = 0.0f; if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width)) { tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f); tab_bar->ScrollingTarget = tab_x1; } else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width) { tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f); tab_bar->ScrollingTarget = tab_x2 - scrollable_width; } } void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir) { IM_ASSERT(dir == -1 || dir == +1); IM_ASSERT(tab_bar->ReorderRequestTabId == 0); tab_bar->ReorderRequestTabId = tab->ID; tab_bar->ReorderRequestDir = (ImS8)dir; } bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) { ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId); if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder)) return false; int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir; if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size) return false; ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order]; if (tab2->Flags & ImGuiTabItemFlags_NoReorder) return false; if ((tab1->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (tab2->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing))) return false; ImGuiTabItem item_tmp = *tab1; *tab1 = *tab2; *tab2 = item_tmp; if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings) MarkIniSettingsDirty(); return true; } static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f); const float scrolling_buttons_width = arrow_button_size.x * 2.0f; const ImVec2 backup_cursor_pos = window->DC.CursorPos; int select_dir = 0; ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; arrow_col.w *= 0.5f; PushStyleColor(ImGuiCol_Text, arrow_col); PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); const float backup_repeat_delay = g.IO.KeyRepeatDelay; const float backup_repeat_rate = g.IO.KeyRepeatRate; g.IO.KeyRepeatDelay = 0.250f; g.IO.KeyRepeatRate = 0.200f; float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width); window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y); if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) select_dir = -1; window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y); if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) select_dir = +1; PopStyleColor(2); g.IO.KeyRepeatRate = backup_repeat_rate; g.IO.KeyRepeatDelay = backup_repeat_delay; ImGuiTabItem* tab_to_scroll_to = NULL; if (select_dir != 0) if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId)) { int selected_order = tab_bar->GetTabOrder(tab_item); int target_order = selected_order + select_dir; while (tab_to_scroll_to == NULL) { tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button) { target_order += select_dir; selected_order += select_dir; tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL; } } } window->DC.CursorPos = backup_cursor_pos; tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f; return tab_to_scroll_to; } static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y; const ImVec2 backup_cursor_pos = window->DC.CursorPos; window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y); tab_bar->BarRect.Min.x += tab_list_popup_button_width; ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; arrow_col.w *= 0.5f; PushStyleColor(ImGuiCol_Text, arrow_col); PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest); PopStyleColor(2); ImGuiTabItem* tab_to_select = NULL; if (open) { for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; if (tab->Flags & ImGuiTabItemFlags_Button) continue; const char* tab_name = tab_bar->GetTabName(tab); if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID)) tab_to_select = tab; } EndCombo(); } window->DC.CursorPos = backup_cursor_pos; return tab_to_select; } bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); return false; } IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); bool ret = TabItemEx(tab_bar, label, p_open, flags); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; PushOverrideID(tab->ID); } return ret; } void ImGui::EndTabItem() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); return; } IM_ASSERT(tab_bar->LastTabItemIdx >= 0); ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; if (!(tab->Flags & ImGuiTabItemFlags_NoPushId)) PopID(); } bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); return false; } return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder); } bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags) { if (tab_bar->WantLayout) TabBarLayout(tab_bar); ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; const ImGuiStyle& style = g.Style; const ImGuiID id = TabBarCalcTabID(tab_bar, label); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); if (p_open && !*p_open) { PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true); ItemAdd(ImRect(), id); PopItemFlag(); return false; } IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button)); IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); if (flags & ImGuiTabItemFlags_NoCloseButton) p_open = NULL; else if (p_open == NULL) flags |= ImGuiTabItemFlags_NoCloseButton; ImVec2 size = TabItemCalcSize(label, p_open != NULL); ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id); bool tab_is_new = false; if (tab == NULL) { tab_bar->Tabs.push_back(ImGuiTabItem()); tab = &tab_bar->Tabs.back(); tab->ID = id; tab->Width = size.x; tab_bar->TabsAddedNew = true; tab_is_new = true; } tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab); tab->ContentWidth = size.x; tab->BeginOrder = tab_bar->TabsActiveCount++; const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0; const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount); const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0; tab->LastFrameVisible = g.FrameCount; tab->Flags = flags; tab->NameOffset = (ImS16)tab_bar->TabsNames.size(); tab_bar->TabsNames.append(label, label + strlen(label) + 1); if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0) if (!tab_bar_appearing || tab_bar->SelectedTabId == 0) if (!is_tab_button) tab_bar->NextSelectedTabId = id; if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) if (!is_tab_button) tab_bar->NextSelectedTabId = id; bool tab_contents_visible = (tab_bar->VisibleTabId == id); if (tab_contents_visible) tab_bar->VisibleTabWasSubmitted = true; if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing) if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs)) tab_contents_visible = true; if (tab_appearing && (!tab_bar_appearing || tab_is_new)) { PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true); ItemAdd(ImRect(), id); PopItemFlag(); if (is_tab_button) return false; return tab_contents_visible; } if (tab_bar->SelectedTabId == id) tab->LastFrameSelected = g.FrameCount; const ImVec2 backup_main_cursor_pos = window->DC.CursorPos; const bool is_central_section = (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) == 0; size.x = tab->Width; if (is_central_section) window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f); else window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); ImVec2 pos = window->DC.CursorPos; ImRect bb(pos, pos + size); const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX); if (want_clip_rect) PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; ItemSize(bb.GetSize(), style.FramePadding.y); window->DC.CursorMaxPos = backup_cursor_max_pos; if (!ItemAdd(bb, id)) { if (want_clip_rect) PopClipRect(); window->DC.CursorPos = backup_main_cursor_pos; return tab_contents_visible; } ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowItemOverlap); if (g.DragDropActive) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (pressed && !is_tab_button) tab_bar->NextSelectedTabId = id; hovered |= (g.HoveredId == id); if (g.ActiveId != id) SetItemAllowOverlap(); if (held && !tab_appearing && IsMouseDragging(0)) { if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)) { if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x) { if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) TabBarQueueReorder(tab_bar, tab, -1); } else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x) { if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) TabBarQueueReorder(tab_bar, tab, +1); } } } #if 0 if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth) { bb.Max.x = bb.Min.x + IM_FLOOR(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f))); display_draw_list = GetForegroundDrawList(window); TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive)); } #endif ImDrawList* display_draw_list = window->DrawList; const ImU32 tab_col = GetColorU32(ImGuiCol_Tab); bb.Min.y -= 5.0f; if ( tab_contents_visible && tab_bar_focused) bb.Min.y -= 3.0f; bb.Max.x += 3.0f; bb.Max.y += 1.0f; TabItemBackground(display_draw_list, bb, flags, tab_col); const float y2 = bb.Max.y - 1.0f; if ((!tab_contents_visible && tab_bar_focused)) { display_draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2)); display_draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); display_draw_list->PathStroke(colWhite, false, g.Style.TabBorderSize); } RenderNavHighlight(bb, id); const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1))) if (!is_tab_button) tab_bar->NextSelectedTabId = id; if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0; bool just_closed; bool text_clipped; bb.Min.y += 3.0f; if ((!tab_contents_visible && tab_bar_focused)) TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped, false); else TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); if (just_closed && p_open != NULL) { *p_open = false; TabBarCloseTab(tab_bar, tab); } if (want_clip_rect) PopClipRect(); window->DC.CursorPos = backup_main_cursor_pos; if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered()) if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); if (is_tab_button) return pressed; return tab_contents_visible; } void ImGui::SetTabItemClosed(const char* label) { ImGuiContext& g = *GImGui; bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode); if (is_within_manual_tab_bar) { ImGuiTabBar* tab_bar = g.CurrentTabBar; ImGuiID tab_id = TabBarCalcTabID(tab_bar, label); if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) tab->WantClose = true; } } ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button) { ImGuiContext& g = *GImGui; ImVec2 label_size = CalcTextSize(label, NULL, true); ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f); if (has_close_button) size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); else size.x += g.Style.FramePadding.x + 1.0f; return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); } void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col) { ImGuiContext& g = *GImGui; const float width = bb.GetWidth(); IM_UNUSED(flags); IM_ASSERT(width > 0.0f); const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f)); const float y1 = bb.Min.y + 1.0f; const float y2 = bb.Max.y - 1.0f; draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9); draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12); draw_list->PathLineTo(ImVec2(bb.Max.x, y2)); draw_list->PathFillConvex(col); if (g.Style.TabBorderSize > 0.0f) { draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2)); draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); draw_list->PathStroke(GetColorU32(ImGuiCol_Border), false, g.Style.TabBorderSize); draw_list->PathLineTo(ImVec2(bb.Max.x, y2)); draw_list->PathLineTo(ImVec2(bb.Max.x, bb.Min.y + 2.0f)); draw_list->PathStroke(colBlack, false, g.Style.TabBorderSize); } } void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped, bool tabActive) { ImGuiContext& g = *GImGui; ImVec2 label_size = CalcTextSize(label, NULL, true); if (out_just_closed) *out_just_closed = false; if (out_text_clipped) *out_text_clipped = false; if (bb.GetWidth() <= 1.0f) return; #if 0 const float backup_alpha = g.Style.Alpha; if (!is_contents_visible) g.Style.Alpha *= 0.7f; #endif const char* TAB_UNSAVED_MARKER = "*"; ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); if (flags & ImGuiTabItemFlags_UnsavedDocument) { text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x; ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + frame_padding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + frame_padding.y + IM_FLOOR(-g.FontSize * 0.25f)); RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL); } ImRect text_ellipsis_clip_bb = text_pixel_clip_bb; if (out_text_clipped) { *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x; } bool close_button_pressed = false; bool close_button_visible = false; if (close_button_id != 0) if (is_contents_visible || bb.GetWidth() >= g.Style.TabMinWidthForCloseButton) if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id) close_button_visible = true; if (close_button_visible) { ImGuiLastItemDataBackup last_item_backup; const float close_button_sz = g.FontSize; PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding); if (CloseButton(close_button_id, ImVec2(bb.Max.x - frame_padding.x * 2.0f - close_button_sz, bb.Min.y))) close_button_pressed = true; PopStyleVar(); last_item_backup.Restore(); if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) close_button_pressed = true; text_pixel_clip_bb.Max.x -= close_button_sz; } float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f; if (!tabActive) RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size, colWhiteText); else RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); #if 0 if (!is_contents_visible) g.Style.Alpha = backup_alpha; #endif if (out_just_closed) *out_just_closed = close_button_pressed; } #endif