| // dear imgui, v1.91.5 |
| // (widgets code) |
| |
| /* |
| |
| Index of this file: |
| |
| // [SECTION] Forward Declarations |
| // [SECTION] Widgets: Text, etc. |
| // [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.) |
| // [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.) |
| // [SECTION] Widgets: ComboBox |
| // [SECTION] Data Type and Data Formatting Helpers |
| // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. |
| // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. |
| // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. |
| // [SECTION] Widgets: InputText, InputTextMultiline |
| // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. |
| // [SECTION] Widgets: TreeNode, CollapsingHeader, etc. |
| // [SECTION] Widgets: Selectable |
| // [SECTION] Widgets: Typing-Select support |
| // [SECTION] Widgets: Box-Select support |
| // [SECTION] Widgets: Multi-Select support |
| // [SECTION] Widgets: Multi-Select helpers |
| // [SECTION] Widgets: ListBox |
| // [SECTION] Widgets: PlotLines, PlotHistogram |
| // [SECTION] Widgets: Value helpers |
| // [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc. |
| // [SECTION] Widgets: BeginTabBar, EndTabBar, etc. |
| // [SECTION] Widgets: BeginTabItem, EndTabItem, etc. |
| // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. |
| |
| */ |
| |
| #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) |
| #define _CRT_SECURE_NO_WARNINGS |
| #endif |
| |
| #ifndef IMGUI_DEFINE_MATH_OPERATORS |
| #define IMGUI_DEFINE_MATH_OPERATORS |
| #endif |
| |
| #include "imgui.h" |
| #ifndef IMGUI_DISABLE |
| #include "imgui_internal.h" |
| |
| // System includes |
| #include <stdint.h> // intptr_t |
| |
| //------------------------------------------------------------------------- |
| // Warnings |
| //------------------------------------------------------------------------- |
| |
| // Visual Studio warnings |
| #ifdef _MSC_VER |
| #pragma warning (disable: 4127) // condition expression is constant |
| #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen |
| #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later |
| #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types |
| #endif |
| #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). |
| #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). |
| #endif |
| |
| // Clang/GCC warnings with -Weverything |
| #if defined(__clang__) |
| #if __has_warning("-Wunknown-warning-option") |
| #pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! |
| #endif |
| #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' |
| #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. |
| #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. |
| #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. |
| #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness |
| #pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used // we define snprintf/vsnprintf on Windows so they are available, but not always used. |
| #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 |
| #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. |
| #pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') |
| #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated |
| #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision |
| #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access |
| #pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type |
| #elif defined(__GNUC__) |
| #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind |
| #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked |
| #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead |
| #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated |
| #endif |
| |
| //------------------------------------------------------------------------- |
| // Data |
| //------------------------------------------------------------------------- |
| |
| // Widgets |
| static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior. |
| static const float DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f; // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags. |
| |
| // Those MIN/MAX values are not define because we need to point to them |
| 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; // (-2147483647 - 1), (0x80000000); |
| static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF) |
| static const ImU32 IM_U32_MIN = 0; |
| static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF) |
| #ifdef LLONG_MIN |
| static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll); |
| static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll); |
| #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; // (0xFFFFFFFFFFFFFFFFull); |
| #else |
| static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); |
| #endif |
| |
| //------------------------------------------------------------------------- |
| // [SECTION] Forward Declarations |
| //------------------------------------------------------------------------- |
| |
| // For InputTextEx() |
| static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); |
| static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); |
| static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); |
| |
| //------------------------------------------------------------------------- |
| // [SECTION] Widgets: Text, etc. |
| //------------------------------------------------------------------------- |
| // - TextEx() [Internal] |
| // - TextUnformatted() |
| // - Text() |
| // - TextV() |
| // - TextColored() |
| // - TextColoredV() |
| // - TextDisabled() |
| // - TextDisabledV() |
| // - TextWrapped() |
| // - TextWrappedV() |
| // - LabelText() |
| // - LabelTextV() |
| // - BulletText() |
| // - BulletTextV() |
| //------------------------------------------------------------------------- |
| |
| void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| ImGuiContext& g = *GImGui; |
| |
| // Accept null ranges |
| if (text == text_end) |
| text = text_end = ""; |
| |
| // Calculate length |
| const char* text_begin = text; |
| if (text_end == NULL) |
| text_end = text + strlen(text); // FIXME-OPT |
| |
| 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) |
| { |
| // Common case |
| const 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; |
| |
| // Render (we don't hide text after ## in this end-user function) |
| RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); |
| } |
| else |
| { |
| // Long text! |
| // Perform manual coarse clipping to optimize for long multi-line text |
| // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. |
| // - We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. |
| // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop. |
| const char* line = text; |
| const float line_height = GetTextLineHeight(); |
| ImVec2 text_size(0, 0); |
| |
| // Lines to skip (can't skip when logging text) |
| 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; |
| } |
| } |
| |
| // Lines to render |
| if (line < text_end) |
| { |
| ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); |
| while (line < text_end) |
| { |
| if (IsClippedEx(line_rect, 0)) |
| 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; |
| } |
| |
| // Count remaining lines |
| 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); |
| } |
| } |
| |
| 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; |
| |
| const char* text, *text_end; |
| ImFormatStringToTempBufferV(&text, &text_end, fmt, args); |
| TextEx(text, 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); |
| 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]); |
| 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; |
| const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set |
| if (need_backup) |
| PushTextWrapPos(0.0f); |
| 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); |
| } |
| |
| // Add a label+text combo aligned to other label+value widgets |
| 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 char* value_text_begin, *value_text_end; |
| ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args); |
| const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false); |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| |
| const ImVec2 pos = window->DC.CursorPos; |
| const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2)); |
| const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(value_size.y, label_size.y) + style.FramePadding.y * 2)); |
| ItemSize(total_bb, style.FramePadding.y); |
| if (!ItemAdd(total_bb, 0)) |
| return; |
| |
| // Render |
| RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f)); |
| 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); |
| } |
| |
| // Text with a little bullet aligned to the typical tree node. |
| 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, *text_end; |
| ImFormatStringToTempBufferV(&text_begin, &text_end, 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); // Empty text doesn't add padding |
| 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; |
| |
| // Render |
| 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); |
| } |
| |
| //------------------------------------------------------------------------- |
| // [SECTION] Widgets: Main |
| //------------------------------------------------------------------------- |
| // - ButtonBehavior() [Internal] |
| // - Button() |
| // - SmallButton() |
| // - InvisibleButton() |
| // - ArrowButton() |
| // - CloseButton() [Internal] |
| // - CollapseButton() [Internal] |
| // - GetWindowScrollbarID() [Internal] |
| // - GetWindowScrollbarRect() [Internal] |
| // - Scrollbar() [Internal] |
| // - ScrollbarEx() [Internal] |
| // - Image() |
| // - ImageButton() |
| // - Checkbox() |
| // - CheckboxFlagsT() [Internal] |
| // - CheckboxFlags() |
| // - RadioButton() |
| // - ProgressBar() |
| // - Bullet() |
| // - Hyperlink() |
| //------------------------------------------------------------------------- |
| |
| // The ButtonBehavior() function is key to many interactions and used by many/most widgets. |
| // Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_), |
| // this code is a little complex. |
| // By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior. |
| // See the series of events below and the corresponding state reported by dear imgui: |
| //------------------------------------------------------------------------------------------------------------------------------------------------ |
| // with PressedOnClickRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() |
| // Frame N+0 (mouse is outside bb) - - - - - - |
| // Frame N+1 (mouse moves inside bb) - true - - - - |
| // Frame N+2 (mouse button is down) - true true true - true |
| // Frame N+3 (mouse button is down) - true true - - - |
| // Frame N+4 (mouse moves outside bb) - - true - - - |
| // Frame N+5 (mouse moves inside bb) - true true - - - |
| // Frame N+6 (mouse button is released) true true - - true - |
| // Frame N+7 (mouse button is released) - true - - - - |
| // Frame N+8 (mouse moves outside bb) - - - - - - |
| //------------------------------------------------------------------------------------------------------------------------------------------------ |
| // with PressedOnClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() |
| // Frame N+2 (mouse button is down) true true true true - true |
| // Frame N+3 (mouse button is down) - true true - - - |
| // Frame N+6 (mouse button is released) - true - - true - |
| // Frame N+7 (mouse button is released) - true - - - - |
| //------------------------------------------------------------------------------------------------------------------------------------------------ |
| // with PressedOnRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() |
| // Frame N+2 (mouse button is down) - true - - - true |
| // Frame N+3 (mouse button is down) - true - - - - |
| // Frame N+6 (mouse button is released) true true - - - - |
| // Frame N+7 (mouse button is released) - true - - - - |
| //------------------------------------------------------------------------------------------------------------------------------------------------ |
| // with PressedOnDoubleClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() |
| // Frame N+0 (mouse button is down) - true - - - true |
| // Frame N+1 (mouse button is down) - true - - - - |
| // Frame N+2 (mouse button is released) - true - - - - |
| // Frame N+3 (mouse button is released) - true - - - - |
| // Frame N+4 (mouse button is down) true true true true - true |
| // Frame N+5 (mouse button is down) - true true - - - |
| // Frame N+6 (mouse button is released) - true - - true - |
| // Frame N+7 (mouse button is released) - true - - - - |
| //------------------------------------------------------------------------------------------------------------------------------------------------ |
| // Note that some combinations are supported, |
| // - PressedOnDragDropHold can generally be associated with any flag. |
| // - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported. |
| //------------------------------------------------------------------------------------------------------------------------------------------------ |
| // The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set: |
| // Repeat+ Repeat+ Repeat+ Repeat+ |
| // PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick |
| //------------------------------------------------------------------------------------------------------------------------------------------------- |
| // Frame N+0 (mouse button is down) - true - true |
| // ... - - - - |
| // Frame N + RepeatDelay true true - true |
| // ... - - - - |
| // Frame N + RepeatDelay + RepeatRate*N true true - true |
| //------------------------------------------------------------------------------------------------------------------------------------------------- |
| |
| // - FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc. |
| // And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' |
| // For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. |
| // - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature. |
| // One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior() |
| // with same ID and different MouseButton (see #8030). You can fix it by: |
| // (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags. |
| // or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag() |
| bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = GetCurrentWindow(); |
| |
| // Default behavior inherited from item flags |
| // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. |
| ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags); |
| if (flags & ImGuiButtonFlags_AllowOverlap) |
| item_flags |= ImGuiItemFlags_AllowOverlap; |
| |
| // Default only reacts to left mouse button |
| if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) |
| flags |= ImGuiButtonFlags_MouseButtonLeft; |
| |
| // Default behavior requires click + release inside bounding box |
| if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) |
| flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_; |
| |
| ImGuiWindow* backup_hovered_window = g.HoveredWindow; |
| const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window; |
| if (flatten_hovered_children) |
| g.HoveredWindow = window; |
| |
| #ifdef IMGUI_ENABLE_TEST_ENGINE |
| // Alternate registration spot, for when caller didn't use ItemAdd() |
| if (g.LastItemData.ID != id) |
| IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL); |
| #endif |
| |
| bool pressed = false; |
| bool hovered = ItemHoverable(bb, id, item_flags); |
| |
| // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button |
| if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) |
| if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) |
| { |
| hovered = true; |
| SetHoveredID(id); |
| if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER) |
| { |
| pressed = true; |
| g.DragDropHoldJustPressedId = id; |
| FocusWindow(window); |
| } |
| } |
| |
| if (flatten_hovered_children) |
| g.HoveredWindow = backup_hovered_window; |
| |
| // Mouse handling |
| const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id; |
| if (hovered) |
| { |
| IM_ASSERT(id != 0); // Lazily check inside rare path. |
| |
| // Poll mouse buttons |
| // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId. |
| // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine. |
| int mouse_button_clicked = -1; |
| int mouse_button_released = -1; |
| for (int button = 0; button < 3; button++) |
| if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here. |
| { |
| if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; } |
| if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; } |
| } |
| |
| // Process initial action |
| const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt); |
| if (mods_ok) |
| { |
| if (mouse_button_clicked != -1 && g.ActiveId != id) |
| { |
| if (!(flags & ImGuiButtonFlags_NoSetKeyOwner)) |
| SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id); |
| if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere)) |
| { |
| SetActiveID(id, window); |
| g.ActiveIdMouseButton = mouse_button_clicked; |
| if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
| { |
| SetFocusID(id, window); |
| FocusWindow(window); |
| } |
| else |
| { |
| FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child |
| } |
| } |
| if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2)) |
| { |
| pressed = true; |
| if (flags & ImGuiButtonFlags_NoHoldingActiveId) |
| ClearActiveID(); |
| else |
| SetActiveID(id, window); // Hold on ID |
| g.ActiveIdMouseButton = mouse_button_clicked; |
| if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
| { |
| SetFocusID(id, window); |
| FocusWindow(window); |
| } |
| else |
| { |
| FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child |
| } |
| } |
| } |
| if (flags & ImGuiButtonFlags_PressedOnRelease) |
| { |
| if (mouse_button_released != -1) |
| { |
| const bool has_repeated_at_least_once = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior |
| if (!has_repeated_at_least_once) |
| pressed = true; |
| if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
| SetFocusID(id, window); // FIXME: Lack of FocusWindow() call here is inconsistent with other paths. Research why. |
| ClearActiveID(); |
| } |
| } |
| |
| // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). |
| // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. |
| if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat)) |
| if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id)) |
| pressed = true; |
| } |
| |
| if (pressed && g.IO.ConfigNavCursorVisibleAuto) |
| g.NavCursorVisible = false; |
| } |
| |
| // Keyboard/Gamepad navigation handling |
| // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse. |
| if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav) |
| if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) |
| hovered = true; |
| if (g.NavActivateDownId == id) |
| { |
| bool nav_activated_by_code = (g.NavActivateId == id); |
| bool nav_activated_by_inputs = (g.NavActivatePressedId == id); |
| if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat)) |
| { |
| // Avoid pressing multiple keys from triggering excessive amount of repeat events |
| const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space); |
| const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter); |
| const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate); |
| const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration); |
| nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; |
| } |
| if (nav_activated_by_code || nav_activated_by_inputs) |
| { |
| // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. |
| pressed = true; |
| SetActiveID(id, window); |
| g.ActiveIdSource = g.NavInputSource; |
| if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)) |
| SetFocusID(id, window); |
| if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut) |
| g.ActiveIdFromShortcut = true; |
| } |
| } |
| |
| // Process while held |
| 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; |
| if (mouse_button == -1) |
| { |
| // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304). |
| ClearActiveID(); |
| } |
| else if (IsMouseDown(mouse_button, test_owner_id)) |
| { |
| 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) |
| { |
| // Report as pressed when releasing the mouse (this is the most common path) |
| bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2; |
| bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release> |
| bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id); |
| if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned) |
| pressed = true; |
| } |
| ClearActiveID(); |
| } |
| if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto) |
| g.NavCursorVisible = false; |
| } |
| else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) |
| { |
| // When activated using Nav, we hold on the ActiveID until activation button is released |
| if (g.NavActivateDownId == id) |
| held = true; // hovered == true not true as we are already likely hovered on direct activation. |
| else |
| ClearActiveID(); |
| } |
| if (pressed) |
| g.ActiveIdHasBeenPressedBefore = true; |
| } |
| |
| // Activation highlight (this may be a remote activation) |
| if (g.NavHighlightActivatedId == id) |
| hovered = 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) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) |
| 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); |
| |
| const ImRect bb(pos, pos + size); |
| ItemSize(size, style.FramePadding.y); |
| if (!ItemAdd(bb, id)) |
| return false; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); |
| |
| // Render |
| const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
| RenderNavCursor(bb, id); |
| RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); |
| |
| if (g.LogEnabled) |
| LogSetNextTextDecoration("[", "]"); |
| RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); |
| |
| // Automatically close popups |
| //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) |
| // CloseCurrentPopup(); |
| |
| IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); |
| return pressed; |
| } |
| |
| bool ImGui::Button(const char* label, const ImVec2& size_arg) |
| { |
| return ButtonEx(label, size_arg, ImGuiButtonFlags_None); |
| } |
| |
| // Small buttons fits within text without additional vertical spacing. |
| 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; |
| } |
| |
| // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. |
| // Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) |
| bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size. |
| 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, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav)) |
| return false; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); |
| RenderNavCursor(bb, id); |
| |
| IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); |
| return pressed; |
| } |
| |
| bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| 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; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); |
| |
| // Render |
| const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
| const ImU32 text_col = GetColorU32(ImGuiCol_Text); |
| RenderNavCursor(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); |
| |
| IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); |
| return pressed; |
| } |
| |
| bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) |
| { |
| float sz = GetFrameHeight(); |
| return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None); |
| } |
| |
| // Button to close a window |
| bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| |
| // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825) |
| // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible? |
| const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize)); |
| ImRect bb_interact = bb; |
| const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea(); |
| if (area_to_visible_ratio < 1.5f) |
| bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f)); |
| |
| // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window. |
| // (this isn't the common behavior of buttons, but it doesn't affect the user because navigation tends to keep items visible in scrolling layer). |
| bool is_clipped = !ItemAdd(bb_interact, id); |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held); |
| if (is_clipped) |
| return pressed; |
| |
| // Render |
| ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); |
| if (hovered) |
| window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); |
| RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); |
| ImU32 cross_col = GetColorU32(ImGuiCol_Text); |
| ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); |
| float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; |
| window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); |
| window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_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)); |
| bool is_clipped = !ItemAdd(bb, id); |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); |
| if (is_clipped) |
| return pressed; |
| |
| // Render |
| ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
| ImU32 text_col = GetColorU32(ImGuiCol_Text); |
| if (hovered || held) |
| window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); |
| RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); |
| RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); |
| |
| // Switch to moving the window after mouse is moved beyond the initial drag threshold |
| if (IsItemActive() && IsMouseDragging(0)) |
| StartMouseMovingWindow(window); |
| |
| return pressed; |
| } |
| |
| ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis) |
| { |
| return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY"); |
| } |
| |
| // Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set. |
| 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]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) |
| 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 - border_size, outer_rect.Max.y - border_size); |
| else |
| return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size); |
| } |
| |
| void ImGui::Scrollbar(ImGuiAxis axis) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| const ImGuiID id = GetWindowScrollbarID(window, axis); |
| |
| // Calculate scrollbar bounding box |
| ImRect bb = GetWindowScrollbarRect(window, axis); |
| ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone; |
| if (axis == ImGuiAxis_X) |
| { |
| rounding_corners |= ImDrawFlags_RoundCornersBottomLeft; |
| if (!window->ScrollbarY) |
| rounding_corners |= ImDrawFlags_RoundCornersBottomRight; |
| } |
| else |
| { |
| if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) |
| rounding_corners |= ImDrawFlags_RoundCornersTopRight; |
| if (!window->ScrollbarX) |
| rounding_corners |= ImDrawFlags_RoundCornersBottomRight; |
| } |
| float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; |
| float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; |
| ImS64 scroll = (ImS64)window->Scroll[axis]; |
| ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners); |
| window->Scroll[axis] = (float)scroll; |
| } |
| |
| // Vertical/Horizontal scrollbar |
| // The entire piece of code below is rather confusing because: |
| // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) |
| // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar |
| // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. |
| // Still, the code should probably be made simpler.. |
| bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags flags) |
| { |
| 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; |
| |
| // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab) |
| 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_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f))); |
| |
| // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) |
| const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight(); |
| |
| // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) |
| // But we maintain a minimum size in pixel to allow for the user to still aim inside. |
| IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. |
| const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1); |
| const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v); |
| const float grab_h_norm = grab_h_pixels / scrollbar_size_v; |
| |
| // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). |
| bool held = false; |
| bool hovered = false; |
| ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav); |
| ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); |
| |
| const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v); |
| float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max); |
| float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space |
| if (held && allow_interaction && grab_h_norm < 1.0f) |
| { |
| const float scrollbar_pos_v = bb.Min[axis]; |
| const float mouse_pos_v = g.IO.MousePos[axis]; |
| |
| // Click position in scrollbar normalized space (0.0f->1.0f) |
| const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); |
| |
| const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0; |
| if (g.ActiveIdIsJustActivated) |
| { |
| // On initial click when held_dir == 0 (clicked over grab): calculate the distance between mouse and the center of the grab |
| const bool scroll_to_clicked_location = (g.IO.ConfigScrollbarScrollByPage == false || g.IO.KeyShift || held_dir == 0); |
| g.ScrollbarSeekMode = scroll_to_clicked_location ? 0 : (short)held_dir; |
| g.ScrollbarClickDeltaToGrabCenter = (held_dir == 0 && !g.IO.KeyShift) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f; |
| } |
| |
| // Apply scroll (p_scroll_v will generally point on one member of window->Scroll) |
| // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position |
| if (g.ScrollbarSeekMode == 0) |
| { |
| // Absolute seeking |
| const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm)); |
| *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max); |
| } |
| else |
| { |
| // Page by page |
| if (IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_Repeat) && held_dir == g.ScrollbarSeekMode) |
| { |
| float page_dir = (g.ScrollbarSeekMode > 0.0f) ? +1.0f : -1.0f; |
| *p_scroll_v = ImClamp(*p_scroll_v + (ImS64)(page_dir * size_visible_v), (ImS64)0, scroll_max); |
| } |
| } |
| |
| // Update values for rendering |
| scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max); |
| grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; |
| |
| // Update distance to grab now that we have seek'ed and saturated |
| //if (seek_absolute) |
| // g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; |
| } |
| |
| // Render |
| const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg); |
| const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha); |
| window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags); |
| 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); |
| window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); |
| |
| return held; |
| } |
| |
| // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples |
| // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. |
| void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f; |
| const ImVec2 padding(border_size, border_size); |
| const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f); |
| ItemSize(bb); |
| if (!ItemAdd(bb, 0)) |
| return; |
| |
| // Render |
| if (border_size > 0.0f) |
| window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f, ImDrawFlags_None, border_size); |
| window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); |
| } |
| |
| // ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390) |
| // We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API. |
| bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| const ImVec2 padding = g.Style.FramePadding; |
| const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f); |
| ItemSize(bb); |
| if (!ItemAdd(bb, id)) |
| return false; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); |
| |
| // Render |
| const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
| RenderNavCursor(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; |
| } |
| |
| // Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. |
| bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| if (window->SkipItems) |
| return false; |
| |
| return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col); |
| } |
| |
| #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS |
| // Legacy API obsoleted in 1.89. Two differences with new ImageButton() |
| // - old ImageButton() used ImTextureId as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID) |
| // - new ImageButton() requires an explicit 'const char* str_id' |
| // - old ImageButton() had frame_padding' override argument. |
| // - new ImageButton() always use style.FramePadding. |
| /* |
| 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) |
| { |
| // Default to using texture ID as ID. User can still push string/integer prefixes. |
| PushID((ImTextureID)(intptr_t)user_texture_id); |
| if (frame_padding >= 0) |
| PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding)); |
| bool ret = ImageButton("", user_texture_id, size, uv0, uv1, bg_col, tint_col); |
| if (frame_padding >= 0) |
| PopStyleVar(); |
| PopID(); |
| return ret; |
| } |
| */ |
| #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS |
| |
| 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; |
| 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); |
| const bool is_visible = ItemAdd(total_bb, id); |
| const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; |
| if (!is_visible) |
| if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of "no logic clip" for box-select support |
| { |
| IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); |
| return false; |
| } |
| |
| // Range-Selection/Multi-selection support (header) |
| bool checked = *v; |
| if (is_multi_select) |
| MultiSelectItemHeader(id, &checked, NULL); |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); |
| |
| // Range-Selection/Multi-selection support (footer) |
| if (is_multi_select) |
| MultiSelectItemFooter(id, &checked, &pressed); |
| else if (pressed) |
| checked = !checked; |
| |
| if (*v != checked) |
| { |
| *v = checked; |
| pressed = true; // return value |
| MarkItemEdited(id); |
| } |
| |
| const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); |
| const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; |
| if (is_visible) |
| { |
| RenderNavCursor(total_bb, id); |
| RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); |
| ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); |
| if (mixed_value) |
| { |
| // Undocumented tristate/mixed/indeterminate checkbox (#2644) |
| // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) |
| ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(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_TRUNC(square_sz / 6.0f)); |
| RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); |
| } |
| } |
| const ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); |
| if (g.LogEnabled) |
| LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); |
| if (is_visible && label_size.x > 0.0f) |
| RenderText(label_pos, label); |
| |
| IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); |
| return pressed; |
| } |
| |
| template<typename T> |
| 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) |
| { |
| ImGuiContext& g = *GImGui; |
| g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue; |
| pressed = Checkbox(label, &all_on); |
| } |
| 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); |
| |
| RenderNavCursor(total_bb, id); |
| const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); |
| window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); |
| if (active) |
| { |
| const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); |
| window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark)); |
| } |
| |
| if (style.FrameBorderSize > 0.0f) |
| { |
| window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize); |
| window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, 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, g.LastItemData.StatusFlags); |
| return pressed; |
| } |
| |
| // FIXME: This would work nicely if it was a public template, e.g. 'template<T> RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it.. |
| 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; |
| } |
| |
| // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size |
| 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 < 0.0f will display an indeterminate progress bar animation |
| // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works. |
| const bool is_indeterminate = (fraction < 0.0f); |
| if (!is_indeterminate) |
| fraction = ImSaturate(fraction); |
| |
| // Out of courtesy we accept a NaN fraction without crashing |
| float fill_n0 = 0.0f; |
| float fill_n1 = (fraction == fraction) ? fraction : 0.0f; |
| |
| if (is_indeterminate) |
| { |
| const float fill_width_n = 0.2f; |
| fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n; |
| fill_n1 = ImSaturate(fill_n0 + fill_width_n); |
| fill_n0 = ImSaturate(fill_n0); |
| } |
| |
| // Render |
| RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); |
| bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); |
| RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding); |
| |
| // Default displaying the fraction as percentage string, but user can override it |
| // Don't display text for indeterminate bars by default |
| char overlay_buf[32]; |
| if (!is_indeterminate || overlay != NULL) |
| { |
| 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) |
| { |
| float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x; |
| RenderTextClipped(ImVec2(ImClamp(text_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 + 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; |
| } |
| |
| // Render and stay on same line |
| 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); |
| } |
| |
| // This is provided as a convenience for being an often requested feature. |
| // FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system. |
| // Because of this we currently don't provide many styling options for this widget |
| // (e.g. hovered/active colors are automatically inferred from a single color). |
| bool ImGui::TextLink(const char* label) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiID id = window->GetID(label); |
| const char* label_end = FindRenderedTextEnd(label); |
| |
| ImVec2 pos = window->DC.CursorPos; |
| ImVec2 size = CalcTextSize(label, label_end, true); |
| ImRect bb(pos, pos + size); |
| ItemSize(size, 0.0f); |
| if (!ItemAdd(bb, id)) |
| return false; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held); |
| RenderNavCursor(bb, id); |
| |
| if (hovered) |
| SetMouseCursor(ImGuiMouseCursor_Hand); |
| |
| ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink]; |
| ImVec4 line_colf = text_colf; |
| { |
| // FIXME-STYLE: Read comments above. This widget is NOT written in the same style as some earlier widgets, |
| // as we are currently experimenting/planning a different styling system. |
| float h, s, v; |
| ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v); |
| if (held || hovered) |
| { |
| v = ImSaturate(v + (held ? 0.4f : 0.3f)); |
| h = ImFmod(h + 0.02f, 1.0f); |
| } |
| ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z); |
| v = ImSaturate(v - 0.20f); |
| ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); |
| } |
| |
| float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f); |
| window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode. |
| |
| PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); |
| RenderText(bb.Min, label, label_end); |
| PopStyleColor(); |
| |
| IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); |
| return pressed; |
| } |
| |
| void ImGui::TextLinkOpenURL(const char* label, const char* url) |
| { |
| ImGuiContext& g = *GImGui; |
| if (url == NULL) |
| url = label; |
| if (TextLink(label)) |
| if (g.PlatformIO.Platform_OpenInShellFn != NULL) |
| g.PlatformIO.Platform_OpenInShellFn(&g, url); |
| SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label |
| if (BeginPopupContextItem()) |
| { |
| if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink))) |
| SetClipboardText(url); |
| EndPopup(); |
| } |
| } |
| |
| //------------------------------------------------------------------------- |
| // [SECTION] Widgets: Low-level Layout helpers |
| //------------------------------------------------------------------------- |
| // - Spacing() |
| // - Dummy() |
| // - NewLine() |
| // - AlignTextToFramePadding() |
| // - SeparatorEx() [Internal] |
| // - Separator() |
| // - SplitterBehavior() [Internal] |
| // - ShrinkWidths() [Internal] |
| //------------------------------------------------------------------------- |
| |
| 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; |
| window->DC.IsSameLine = false; |
| if (window->DC.CurrLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. |
| 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); |
| } |
| |
| // Horizontal/vertical separating line |
| // FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues. |
| // Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are. |
| void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| ImGuiContext& g = *GImGui; |
| IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected |
| IM_ASSERT(thickness > 0.0f); |
| |
| if (flags & ImGuiSeparatorFlags_Vertical) |
| { |
| // Vertical separator, for menu bars (use current line height). |
| 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, y2)); |
| ItemSize(ImVec2(thickness, 0.0f)); |
| if (!ItemAdd(bb, 0)) |
| return; |
| |
| // Draw |
| window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator)); |
| if (g.LogEnabled) |
| LogText(" |"); |
| } |
| else if (flags & ImGuiSeparatorFlags_Horizontal) |
| { |
| // Horizontal Separator |
| float x1 = window->DC.CursorPos.x; |
| float x2 = window->WorkRect.Max.x; |
| |
| // Preserve legacy behavior inside Columns() |
| // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set. |
| // We currently don't need to provide the same feature for tables because tables naturally have border features. |
| ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL; |
| if (columns) |
| { |
| x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03 |
| x2 = window->Pos.x + window->Size.x; |
| PushColumnsBackground(); |
| } |
| |
| // We don't provide our width to the layout so that it doesn't get feed back into AutoFit |
| // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell) |
| const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change. |
| const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness)); |
| ItemSize(ImVec2(0.0f, thickness_for_layout)); |
| |
| if (ItemAdd(bb, 0)) |
| { |
| // Draw |
| window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator)); |
| 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; |
| |
| // Those flags should eventually be configurable by the user |
| // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f. |
| ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; |
| |
| // Only applies to legacy Columns() api as they relied on Separator() a lot. |
| if (window->DC.CurrentColumns) |
| flags |= ImGuiSeparatorFlags_SpanAllColumns; |
| |
| SeparatorEx(flags, 1.0f); |
| } |
| |
| void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| ImGuiStyle& style = g.Style; |
| |
| const ImVec2 label_size = CalcTextSize(label, label_end, false); |
| const ImVec2 pos = window->DC.CursorPos; |
| const ImVec2 padding = style.SeparatorTextPadding; |
| |
| const float separator_thickness = style.SeparatorTextBorderSize; |
| const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness)); |
| const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y)); |
| const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImFloor((style.SeparatorTextSize - label_size.y) * 0.5f)); |
| ItemSize(min_size, text_baseline_y); |
| if (!ItemAdd(bb, id)) |
| return; |
| |
| const float sep1_x1 = pos.x; |
| const float sep2_x2 = bb.Max.x; |
| const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f); |
| |
| const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); |
| const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN |
| |
| // This allows using SameLine() to position something in the 'extra_w' |
| window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x; |
| |
| const ImU32 separator_col = GetColorU32(ImGuiCol_Separator); |
| if (label_size.x > 0.0f) |
| { |
| const float sep1_x2 = label_pos.x - style.ItemSpacing.x; |
| const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x; |
| if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f) |
| window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness); |
| if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f) |
| window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); |
| if (g.LogEnabled) |
| LogSetNextTextDecoration("---", NULL); |
| RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size); |
| } |
| else |
| { |
| if (g.LogEnabled) |
| LogText("---"); |
| if (separator_thickness > 0.0f) |
| window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); |
| } |
| } |
| |
| void ImGui::SeparatorText(const char* label) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want: |
| // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight) |
| // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string) |
| // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...' |
| // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item, |
| // and then we can turn this into a format function. |
| SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f); |
| } |
| |
| // Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise. |
| 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, ImU32 bg_col) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| |
| if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav)) |
| return false; |
| |
| // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is |
| // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item. |
| // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item. |
| ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren; |
| #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS |
| button_flags |= ImGuiButtonFlags_AllowOverlap; |
| #endif |
| |
| 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, button_flags); |
| if (hovered) |
| g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb |
| |
| if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) |
| SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); |
| |
| ImRect bb_render = bb; |
| if (held) |
| { |
| float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis]; |
| |
| // Minimum pane size |
| 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; |
| |
| // Apply resize |
| if (mouse_delta != 0.0f) |
| { |
| *size1 = ImMax(*size1 + mouse_delta, min_size1); |
| *size2 = ImMax(*size2 - mouse_delta, min_size2); |
| bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); |
| MarkItemEdited(id); |
| } |
| } |
| |
| // Render at new position |
| if (bg_col & IM_COL32_A_MASK) |
| window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f); |
| 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); |
| } |
| |
| // Shrink excess width from a set of item, by removing width from the larger items first. |
| // Set items Width to -1.0f to disable shrinking this item. |
| 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; |
| } |
| |
| // Round width and redistribute remainder |
| // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator. |
| width_excess = 0.0f; |
| for (int n = 0; n < count; n++) |
| { |
| float width_rounded = ImTrunc(items[n].Width); |
| width_excess += items[n].Width - width_rounded; |
| items[n].Width = width_rounded; |
| } |
| while (width_excess > 0.0f) |
| for (int n = 0; n < count && width_excess > 0.0f; n++) |
| { |
| float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f); |
| items[n].Width += width_to_add; |
| width_excess -= width_to_add; |
| } |
| } |
| |
| //------------------------------------------------------------------------- |
| // [SECTION] Widgets: ComboBox |
| //------------------------------------------------------------------------- |
| // - CalcMaxPopupHeightFromItemCount() [Internal] |
| // - BeginCombo() |
| // - BeginComboPopup() [Internal] |
| // - EndCombo() |
| // - BeginComboPreview() [Internal] |
| // - EndComboPreview() [Internal] |
| // - Combo() |
| //------------------------------------------------------------------------- |
| |
| static float CalcMaxPopupHeightFromItemCount(int items_count) |
| { |
| ImGuiContext& g = *GImGui; |
| if (items_count <= 0) |
| return FLT_MAX; |
| return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); |
| } |
| |
| bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = GetCurrentWindow(); |
| |
| ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags; |
| g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values |
| if (window->SkipItems) |
| return false; |
| |
| const ImGuiStyle& style = g.Style; |
| const ImGuiID id = window->GetID(label); |
| IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together |
| if (flags & ImGuiComboFlags_WidthFitPreview) |
| IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); |
| |
| const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f; |
| const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth()); |
| const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); |
| const ImRect total_bb(bb.Min, 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, &bb)) |
| return false; |
| |
| // Open on click |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held); |
| const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id); |
| bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None); |
| if (pressed && !popup_open) |
| { |
| OpenPopupEx(popup_id, ImGuiPopupFlags_None); |
| popup_open = true; |
| } |
| |
| // Render shape |
| const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); |
| const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size); |
| RenderNavCursor(bb, id); |
| if (!(flags & ImGuiComboFlags_NoPreview)) |
| window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); |
| if (!(flags & ImGuiComboFlags_NoArrowButton)) |
| { |
| ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
| ImU32 text_col = GetColorU32(ImGuiCol_Text); |
| window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight); |
| if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x) |
| RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f); |
| } |
| RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding); |
| |
| // Custom preview |
| if (flags & ImGuiComboFlags_CustomPreview) |
| { |
| g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); |
| IM_ASSERT(preview_value == NULL || preview_value[0] == 0); |
| preview_value = NULL; |
| } |
| |
| // Render preview and label |
| if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) |
| { |
| if (g.LogEnabled) |
| LogSetNextTextDecoration("{", "}"); |
| RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL); |
| } |
| if (label_size.x > 0) |
| RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label); |
| |
| if (!popup_open) |
| return false; |
| |
| g.NextWindowData.Flags = backup_next_window_data_flags; |
| return BeginComboPopup(popup_id, bb, flags); |
| } |
| |
| bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags) |
| { |
| ImGuiContext& g = *GImGui; |
| if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None)) |
| { |
| g.NextWindowData.ClearFlags(); |
| return false; |
| } |
| |
| // Set popup size |
| float w = bb.GetWidth(); |
| if (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_)); // Only one |
| int popup_max_height_in_items = -1; |
| if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; |
| else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; |
| else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; |
| ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX); |
| if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size |
| constraint_min.x = w; |
| if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f) |
| constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); |
| SetNextWindowSizeConstraints(constraint_min, constraint_max); |
| } |
| |
| // This is essentially a specialized version of BeginPopupEx() |
| char name[16]; |
| ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth |
| |
| // Set position given a custom constraint (peak into expected window size so we can position it) |
| // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function? |
| // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()? |
| if (ImGuiWindow* popup_window = FindWindowByName(name)) |
| if (popup_window->WasActive) |
| { |
| // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us. |
| ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window); |
| popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)" |
| ImRect r_outer = GetPopupAllowedExtentRect(popup_window); |
| ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox); |
| SetNextWindowPos(pos); |
| } |
| |
| // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx() |
| ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove; |
| PushStyleVarX(ImGuiStyleVar_WindowPadding, g.Style.FramePadding.x); // Horizontally align ourselves with the framed text |
| bool ret = Begin(name, NULL, window_flags); |
| PopStyleVar(); |
| if (!ret) |
| { |
| EndPopup(); |
| IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above |
| return false; |
| } |
| g.BeginComboDepth++; |
| return true; |
| } |
| |
| void ImGui::EndCombo() |
| { |
| ImGuiContext& g = *GImGui; |
| EndPopup(); |
| g.BeginComboDepth--; |
| } |
| |
| // Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements |
| // (Experimental, see GitHub issues: #1658, #4168) |
| bool ImGui::BeginComboPreview() |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; |
| |
| if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)) |
| return false; |
| IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag? |
| if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional) |
| return false; |
| |
| // FIXME: This could be contained in a PushWorkRect() api |
| preview_data->BackupCursorPos = window->DC.CursorPos; |
| preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos; |
| preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; |
| preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; |
| preview_data->BackupLayout = window->DC.LayoutType; |
| window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding; |
| window->DC.CursorMaxPos = window->DC.CursorPos; |
| window->DC.LayoutType = ImGuiLayoutType_Horizontal; |
| window->DC.IsSameLine = false; |
| PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true); |
| |
| return true; |
| } |
| |
| void ImGui::EndComboPreview() |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; |
| |
| // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future |
| ImDrawList* draw_list = window->DrawList; |
| if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y) |
| if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command |
| { |
| draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect; |
| draw_list->_TryMergeDrawCmds(); |
| } |
| PopClipRect(); |
| window->DC.CursorPos = preview_data->BackupCursorPos; |
| window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); |
| window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine; |
| window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset; |
| window->DC.LayoutType = preview_data->BackupLayout; |
| window->DC.IsSameLine = false; |
| preview_data->PreviewRect = ImRect(); |
| } |
| |
| // Getter for the old Combo() API: const char*[] |
| static const char* Items_ArrayGetter(void* data, int idx) |
| { |
| const char* const* items = (const char* const*)data; |
| return items[idx]; |
| } |
| |
| // Getter for the old Combo() API: "item1\0item2\0item3\0" |
| static const char* Items_SingleStringGetter(void* data, int idx) |
| { |
| 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++; |
| } |
| return *p ? p : NULL; |
| } |
| |
| // Old API, prefer using BeginCombo() nowadays if you can. |
| bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items) |
| { |
| ImGuiContext& g = *GImGui; |
| |
| // Call the getter to obtain the preview string which is a parameter to BeginCombo() |
| const char* preview_value = NULL; |
| if (*current_item >= 0 && *current_item < items_count) |
| preview_value = getter(user_data, *current_item); |
| |
| // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. |
| if (popup_max_height_in_items != -1 && !(g.NextWindowData.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; |
| |
| // Display items |
| bool value_changed = false; |
| ImGuiListClipper clipper; |
| clipper.Begin(items_count); |
| clipper.IncludeItemByIndex(*current_item); |
| while (clipper.Step()) |
| for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) |
| { |
| const char* item_text = getter(user_data, i); |
| if (item_text == NULL) |
| 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; |
| *current_item = i; |
| } |
| if (item_selected) |
| SetItemDefaultFocus(); |
| PopID(); |
| } |
| |
| EndCombo(); |
| if (value_changed) |
| MarkItemEdited(g.LastItemData.ID); |
| |
| return value_changed; |
| } |
| |
| // Combo box helper allowing to pass an array of strings. |
| bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) |
| { |
| const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); |
| return value_changed; |
| } |
| |
| // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" |
| bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) |
| { |
| int items_count = 0; |
| const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open |
| while (*p) |
| { |
| p += strlen(p) + 1; |
| items_count++; |
| } |
| bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); |
| return value_changed; |
| } |
| |
| #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS |
| |
| struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); }; |
| static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx) |
| { |
| ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data; |
| const char* s = NULL; |
| data->OldCallback(data->UserData, idx, &s); |
| return s; |
| } |
| |
| bool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items) |
| { |
| ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter }; |
| return ListBox(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, height_in_items); |
| } |
| bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int popup_max_height_in_items) |
| { |
| ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter }; |
| return Combo(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, popup_max_height_in_items); |
| } |
| |
| #endif |
| |
| //------------------------------------------------------------------------- |
| // [SECTION] Data Type and Data Formatting Helpers [Internal] |
| //------------------------------------------------------------------------- |
| // - DataTypeGetInfo() |
| // - DataTypeFormatString() |
| // - DataTypeApplyOp() |
| // - DataTypeApplyFromText() |
| // - DataTypeCompare() |
| // - DataTypeClamp() |
| // - GetMinimumStepAtDecimalPrecision |
| // - RoundScalarWithFormat<>() |
| //------------------------------------------------------------------------- |
| |
| static const ImGuiDataTypeInfo GDataTypeInfo[] = |
| { |
| { sizeof(char), "S8", "%d", "%d" }, // ImGuiDataType_S8 |
| { sizeof(unsigned char), "U8", "%u", "%u" }, |
| { sizeof(short), "S16", "%d", "%d" }, // ImGuiDataType_S16 |
| { sizeof(unsigned short), "U16", "%u", "%u" }, |
| { sizeof(int), "S32", "%d", "%d" }, // ImGuiDataType_S32 |
| { sizeof(unsigned int), "U32", "%u", "%u" }, |
| #ifdef _MSC_VER |
| { sizeof(ImS64), "S64", "%I64d","%I64d" }, // ImGuiDataType_S64 |
| { sizeof(ImU64), "U64", "%I64u","%I64u" }, |
| #else |
| { sizeof(ImS64), "S64", "%lld", "%lld" }, // ImGuiDataType_S64 |
| { sizeof(ImU64), "U64", "%llu", "%llu" }, |
| #endif |
| { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) |
| { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double |
| { sizeof(bool), "bool", "%d", "%d" }, // ImGuiDataType_Bool |
| }; |
| IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); |
| |
| 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) |
| { |
| // Signedness doesn't matter when pushing integer arguments |
| 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); |
| } |
| |
| // User can input math operators (e.g. +100) to edit a numerical values. |
| // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess.. |
| bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty) |
| { |
| // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all. |
| const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); |
| ImGuiDataTypeStorage data_backup; |
| memcpy(&data_backup, p_data, type_info->Size); |
| |
| while (ImCharIsBlankA(*buf)) |
| buf++; |
| if (!buf[0]) |
| { |
| if (p_data_when_empty != NULL) |
| { |
| memcpy(p_data, p_data_when_empty, type_info->Size); |
| return memcmp(&data_backup, p_data, type_info->Size) != 0; |
| } |
| return false; |
| } |
| |
| // Sanitize format |
| // - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf |
| // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %. |
| char format_sanitized[32]; |
| if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) |
| format = type_info->ScanFmt; |
| else |
| format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized)); |
| |
| // Small types need a 32-bit buffer to receive the result from scanf() |
| int v32 = 0; |
| if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1) |
| return false; |
| if (type_info->Size < 4) |
| { |
| 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<typename T> |
| 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<ImS8 >((const ImS8* )arg_1, (const ImS8* )arg_2); |
| case ImGuiDataType_U8: return DataTypeCompareT<ImU8 >((const ImU8* )arg_1, (const ImU8* )arg_2); |
| case ImGuiDataType_S16: return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2); |
| case ImGuiDataType_U16: return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2); |
| case ImGuiDataType_S32: return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2); |
| case ImGuiDataType_U32: return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2); |
| case ImGuiDataType_S64: return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2); |
| case ImGuiDataType_U64: return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2); |
| case ImGuiDataType_Float: return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2); |
| case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2); |
| case ImGuiDataType_COUNT: break; |
| } |
| IM_ASSERT(0); |
| return 0; |
| } |
| |
| template<typename T> |
| static bool DataTypeClampT(T* v, const T* v_min, const T* v_max) |
| { |
| // Clamp, both sides are optional, return true if modified |
| 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 >((ImS8* )p_data, (const ImS8* )p_min, (const ImS8* )p_max); |
| case ImGuiDataType_U8: return DataTypeClampT<ImU8 >((ImU8* )p_data, (const ImU8* )p_min, (const ImU8* )p_max); |
| case ImGuiDataType_S16: return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max); |
| case ImGuiDataType_U16: return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max); |
| case ImGuiDataType_S32: return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max); |
| case ImGuiDataType_U32: return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max); |
| case ImGuiDataType_S64: return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max); |
| case ImGuiDataType_U64: return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max); |
| case ImGuiDataType_Float: return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max); |
| case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max); |
| case ImGuiDataType_COUNT: break; |
| } |
| IM_ASSERT(0); |
| return false; |
| } |
| |
| bool ImGui::DataTypeIsZero(ImGuiDataType data_type, const void* p_data) |
| { |
| ImGuiContext& g = *GImGui; |
| return DataTypeCompare(data_type, p_data, &g.DataTypeZeroValue) == 0; |
| } |
| |
| 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<typename TYPE> |
| TYPE ImGui::RoundScalarWithFormatT(const
|