imgui

FORK: Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
git clone https://git.neptards.moe/neptards/imgui.git
Log | Files | Refs

imgui_widgets.cpp (394022B)


      1 // dear imgui, v1.83
      2 // (widgets code)
      3 
      4 /*
      5 
      6 Index of this file:
      7 
      8 // [SECTION] Forward Declarations
      9 // [SECTION] Widgets: Text, etc.
     10 // [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
     11 // [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
     12 // [SECTION] Widgets: ComboBox
     13 // [SECTION] Data Type and Data Formatting Helpers
     14 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
     15 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
     16 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
     17 // [SECTION] Widgets: InputText, InputTextMultiline
     18 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
     19 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
     20 // [SECTION] Widgets: Selectable
     21 // [SECTION] Widgets: ListBox
     22 // [SECTION] Widgets: PlotLines, PlotHistogram
     23 // [SECTION] Widgets: Value helpers
     24 // [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
     25 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
     26 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
     27 // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
     28 
     29 */
     30 
     31 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
     32 #define _CRT_SECURE_NO_WARNINGS
     33 #endif
     34 
     35 #include "imgui.h"
     36 #ifndef IMGUI_DISABLE
     37 
     38 #ifndef IMGUI_DEFINE_MATH_OPERATORS
     39 #define IMGUI_DEFINE_MATH_OPERATORS
     40 #endif
     41 #include "imgui_internal.h"
     42 
     43 // System includes
     44 #include <ctype.h>      // toupper
     45 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
     46 #include <stddef.h>     // intptr_t
     47 #else
     48 #include <stdint.h>     // intptr_t
     49 #endif
     50 
     51 //-------------------------------------------------------------------------
     52 // Warnings
     53 //-------------------------------------------------------------------------
     54 
     55 // Visual Studio warnings
     56 #ifdef _MSC_VER
     57 #pragma warning (disable: 4127)     // condition expression is constant
     58 #pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
     59 #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
     60 #pragma warning (disable: 5054)     // operator '|': deprecated between enumerations of different types
     61 #endif
     62 #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).
     63 #pragma warning (disable: 26812)    // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
     64 #endif
     65 
     66 // Clang/GCC warnings with -Weverything
     67 #if defined(__clang__)
     68 #if __has_warning("-Wunknown-warning-option")
     69 #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!
     70 #endif
     71 #pragma clang diagnostic ignored "-Wunknown-pragmas"                // warning: unknown warning group 'xxx'
     72 #pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                            // yes, they are more terse.
     73 #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.
     74 #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.
     75 #pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
     76 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0
     77 #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.
     78 #pragma clang diagnostic ignored "-Wenum-enum-conversion"           // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
     79 #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
     80 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
     81 #elif defined(__GNUC__)
     82 #pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
     83 #pragma GCC diagnostic ignored "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
     84 #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
     85 #endif
     86 
     87 //-------------------------------------------------------------------------
     88 // Data
     89 //-------------------------------------------------------------------------
     90 
     91 // Widgets
     92 static const float          DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f;    // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
     93 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.
     94 
     95 // Those MIN/MAX values are not define because we need to point to them
     96 static const signed char    IM_S8_MIN  = -128;
     97 static const signed char    IM_S8_MAX  = 127;
     98 static const unsigned char  IM_U8_MIN  = 0;
     99 static const unsigned char  IM_U8_MAX  = 0xFF;
    100 static const signed short   IM_S16_MIN = -32768;
    101 static const signed short   IM_S16_MAX = 32767;
    102 static const unsigned short IM_U16_MIN = 0;
    103 static const unsigned short IM_U16_MAX = 0xFFFF;
    104 static const ImS32          IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
    105 static const ImS32          IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
    106 static const ImU32          IM_U32_MIN = 0;
    107 static const ImU32          IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
    108 #ifdef LLONG_MIN
    109 static const ImS64          IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
    110 static const ImS64          IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
    111 #else
    112 static const ImS64          IM_S64_MIN = -9223372036854775807LL - 1;
    113 static const ImS64          IM_S64_MAX = 9223372036854775807LL;
    114 #endif
    115 static const ImU64          IM_U64_MIN = 0;
    116 #ifdef ULLONG_MAX
    117 static const ImU64          IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
    118 #else
    119 static const ImU64          IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
    120 #endif
    121 
    122 //-------------------------------------------------------------------------
    123 // [SECTION] Forward Declarations
    124 //-------------------------------------------------------------------------
    125 
    126 // For InputTextEx()
    127 static bool             InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source);
    128 static int              InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
    129 static ImVec2           InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
    130 
    131 //-------------------------------------------------------------------------
    132 // [SECTION] Widgets: Text, etc.
    133 //-------------------------------------------------------------------------
    134 // - TextEx() [Internal]
    135 // - TextUnformatted()
    136 // - Text()
    137 // - TextV()
    138 // - TextColored()
    139 // - TextColoredV()
    140 // - TextDisabled()
    141 // - TextDisabledV()
    142 // - TextWrapped()
    143 // - TextWrappedV()
    144 // - LabelText()
    145 // - LabelTextV()
    146 // - BulletText()
    147 // - BulletTextV()
    148 //-------------------------------------------------------------------------
    149 
    150 void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
    151 {
    152     ImGuiWindow* window = GetCurrentWindow();
    153     if (window->SkipItems)
    154         return;
    155 
    156     ImGuiContext& g = *GImGui;
    157     IM_ASSERT(text != NULL);
    158     const char* text_begin = text;
    159     if (text_end == NULL)
    160         text_end = text + strlen(text); // FIXME-OPT
    161 
    162     const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
    163     const float wrap_pos_x = window->DC.TextWrapPos;
    164     const bool wrap_enabled = (wrap_pos_x >= 0.0f);
    165     if (text_end - text > 2000 && !wrap_enabled)
    166     {
    167         // Long text!
    168         // Perform manual coarse clipping to optimize for long multi-line text
    169         // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
    170         // - 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.
    171         // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
    172         const char* line = text;
    173         const float line_height = GetTextLineHeight();
    174         ImVec2 text_size(0, 0);
    175 
    176         // Lines to skip (can't skip when logging text)
    177         ImVec2 pos = text_pos;
    178         if (!g.LogEnabled)
    179         {
    180             int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
    181             if (lines_skippable > 0)
    182             {
    183                 int lines_skipped = 0;
    184                 while (line < text_end && lines_skipped < lines_skippable)
    185                 {
    186                     const char* line_end = (const char*)memchr(line, '\n', text_end - line);
    187                     if (!line_end)
    188                         line_end = text_end;
    189                     if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
    190                         text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
    191                     line = line_end + 1;
    192                     lines_skipped++;
    193                 }
    194                 pos.y += lines_skipped * line_height;
    195             }
    196         }
    197 
    198         // Lines to render
    199         if (line < text_end)
    200         {
    201             ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
    202             while (line < text_end)
    203             {
    204                 if (IsClippedEx(line_rect, 0, false))
    205                     break;
    206 
    207                 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
    208                 if (!line_end)
    209                     line_end = text_end;
    210                 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
    211                 RenderText(pos, line, line_end, false);
    212                 line = line_end + 1;
    213                 line_rect.Min.y += line_height;
    214                 line_rect.Max.y += line_height;
    215                 pos.y += line_height;
    216             }
    217 
    218             // Count remaining lines
    219             int lines_skipped = 0;
    220             while (line < text_end)
    221             {
    222                 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
    223                 if (!line_end)
    224                     line_end = text_end;
    225                 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
    226                     text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
    227                 line = line_end + 1;
    228                 lines_skipped++;
    229             }
    230             pos.y += lines_skipped * line_height;
    231         }
    232         text_size.y = (pos - text_pos).y;
    233 
    234         ImRect bb(text_pos, text_pos + text_size);
    235         ItemSize(text_size, 0.0f);
    236         ItemAdd(bb, 0);
    237     }
    238     else
    239     {
    240         const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
    241         const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
    242 
    243         ImRect bb(text_pos, text_pos + text_size);
    244         ItemSize(text_size, 0.0f);
    245         if (!ItemAdd(bb, 0))
    246             return;
    247 
    248         // Render (we don't hide text after ## in this end-user function)
    249         RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
    250     }
    251 }
    252 
    253 void ImGui::TextUnformatted(const char* text, const char* text_end)
    254 {
    255     TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
    256 }
    257 
    258 void ImGui::Text(const char* fmt, ...)
    259 {
    260     va_list args;
    261     va_start(args, fmt);
    262     TextV(fmt, args);
    263     va_end(args);
    264 }
    265 
    266 void ImGui::TextV(const char* fmt, va_list args)
    267 {
    268     ImGuiWindow* window = GetCurrentWindow();
    269     if (window->SkipItems)
    270         return;
    271 
    272     ImGuiContext& g = *GImGui;
    273     const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
    274     TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
    275 }
    276 
    277 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
    278 {
    279     va_list args;
    280     va_start(args, fmt);
    281     TextColoredV(col, fmt, args);
    282     va_end(args);
    283 }
    284 
    285 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
    286 {
    287     PushStyleColor(ImGuiCol_Text, col);
    288     if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
    289         TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
    290     else
    291         TextV(fmt, args);
    292     PopStyleColor();
    293 }
    294 
    295 void ImGui::TextDisabled(const char* fmt, ...)
    296 {
    297     va_list args;
    298     va_start(args, fmt);
    299     TextDisabledV(fmt, args);
    300     va_end(args);
    301 }
    302 
    303 void ImGui::TextDisabledV(const char* fmt, va_list args)
    304 {
    305     ImGuiContext& g = *GImGui;
    306     PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
    307     if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
    308         TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
    309     else
    310         TextV(fmt, args);
    311     PopStyleColor();
    312 }
    313 
    314 void ImGui::TextWrapped(const char* fmt, ...)
    315 {
    316     va_list args;
    317     va_start(args, fmt);
    318     TextWrappedV(fmt, args);
    319     va_end(args);
    320 }
    321 
    322 void ImGui::TextWrappedV(const char* fmt, va_list args)
    323 {
    324     ImGuiContext& g = *GImGui;
    325     bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f);  // Keep existing wrap position if one is already set
    326     if (need_backup)
    327         PushTextWrapPos(0.0f);
    328     if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
    329         TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
    330     else
    331         TextV(fmt, args);
    332     if (need_backup)
    333         PopTextWrapPos();
    334 }
    335 
    336 void ImGui::LabelText(const char* label, const char* fmt, ...)
    337 {
    338     va_list args;
    339     va_start(args, fmt);
    340     LabelTextV(label, fmt, args);
    341     va_end(args);
    342 }
    343 
    344 // Add a label+text combo aligned to other label+value widgets
    345 void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
    346 {
    347     ImGuiWindow* window = GetCurrentWindow();
    348     if (window->SkipItems)
    349         return;
    350 
    351     ImGuiContext& g = *GImGui;
    352     const ImGuiStyle& style = g.Style;
    353     const float w = CalcItemWidth();
    354 
    355     const char* value_text_begin = &g.TempBuffer[0];
    356     const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
    357     const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
    358     const ImVec2 label_size = CalcTextSize(label, NULL, true);
    359 
    360     const ImVec2 pos = window->DC.CursorPos;
    361     const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
    362     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));
    363     ItemSize(total_bb, style.FramePadding.y);
    364     if (!ItemAdd(total_bb, 0))
    365         return;
    366 
    367     // Render
    368     RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
    369     if (label_size.x > 0.0f)
    370         RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
    371 }
    372 
    373 void ImGui::BulletText(const char* fmt, ...)
    374 {
    375     va_list args;
    376     va_start(args, fmt);
    377     BulletTextV(fmt, args);
    378     va_end(args);
    379 }
    380 
    381 // Text with a little bullet aligned to the typical tree node.
    382 void ImGui::BulletTextV(const char* fmt, va_list args)
    383 {
    384     ImGuiWindow* window = GetCurrentWindow();
    385     if (window->SkipItems)
    386         return;
    387 
    388     ImGuiContext& g = *GImGui;
    389     const ImGuiStyle& style = g.Style;
    390 
    391     const char* text_begin = g.TempBuffer;
    392     const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
    393     const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
    394     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
    395     ImVec2 pos = window->DC.CursorPos;
    396     pos.y += window->DC.CurrLineTextBaseOffset;
    397     ItemSize(total_size, 0.0f);
    398     const ImRect bb(pos, pos + total_size);
    399     if (!ItemAdd(bb, 0))
    400         return;
    401 
    402     // Render
    403     ImU32 text_col = GetColorU32(ImGuiCol_Text);
    404     RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);
    405     RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
    406 }
    407 
    408 //-------------------------------------------------------------------------
    409 // [SECTION] Widgets: Main
    410 //-------------------------------------------------------------------------
    411 // - ButtonBehavior() [Internal]
    412 // - Button()
    413 // - SmallButton()
    414 // - InvisibleButton()
    415 // - ArrowButton()
    416 // - CloseButton() [Internal]
    417 // - CollapseButton() [Internal]
    418 // - GetWindowScrollbarID() [Internal]
    419 // - GetWindowScrollbarRect() [Internal]
    420 // - Scrollbar() [Internal]
    421 // - ScrollbarEx() [Internal]
    422 // - Image()
    423 // - ImageButton()
    424 // - Checkbox()
    425 // - CheckboxFlagsT() [Internal]
    426 // - CheckboxFlags()
    427 // - RadioButton()
    428 // - ProgressBar()
    429 // - Bullet()
    430 //-------------------------------------------------------------------------
    431 
    432 // The ButtonBehavior() function is key to many interactions and used by many/most widgets.
    433 // Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
    434 // this code is a little complex.
    435 // By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
    436 // See the series of events below and the corresponding state reported by dear imgui:
    437 //------------------------------------------------------------------------------------------------------------------------------------------------
    438 // with PressedOnClickRelease:             return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
    439 //   Frame N+0 (mouse is outside bb)        -             -                -               -                  -                    -
    440 //   Frame N+1 (mouse moves inside bb)      -             true             -               -                  -                    -
    441 //   Frame N+2 (mouse button is down)       -             true             true            true               -                    true
    442 //   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
    443 //   Frame N+4 (mouse moves outside bb)     -             -                true            -                  -                    -
    444 //   Frame N+5 (mouse moves inside bb)      -             true             true            -                  -                    -
    445 //   Frame N+6 (mouse button is released)   true          true             -               -                  true                 -
    446 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
    447 //   Frame N+8 (mouse moves outside bb)     -             -                -               -                  -                    -
    448 //------------------------------------------------------------------------------------------------------------------------------------------------
    449 // with PressedOnClick:                    return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
    450 //   Frame N+2 (mouse button is down)       true          true             true            true               -                    true
    451 //   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
    452 //   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
    453 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
    454 //------------------------------------------------------------------------------------------------------------------------------------------------
    455 // with PressedOnRelease:                  return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
    456 //   Frame N+2 (mouse button is down)       -             true             -               -                  -                    true
    457 //   Frame N+3 (mouse button is down)       -             true             -               -                  -                    -
    458 //   Frame N+6 (mouse button is released)   true          true             -               -                  -                    -
    459 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
    460 //------------------------------------------------------------------------------------------------------------------------------------------------
    461 // with PressedOnDoubleClick:              return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
    462 //   Frame N+0 (mouse button is down)       -             true             -               -                  -                    true
    463 //   Frame N+1 (mouse button is down)       -             true             -               -                  -                    -
    464 //   Frame N+2 (mouse button is released)   -             true             -               -                  -                    -
    465 //   Frame N+3 (mouse button is released)   -             true             -               -                  -                    -
    466 //   Frame N+4 (mouse button is down)       true          true             true            true               -                    true
    467 //   Frame N+5 (mouse button is down)       -             true             true            -                  -                    -
    468 //   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
    469 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
    470 //------------------------------------------------------------------------------------------------------------------------------------------------
    471 // Note that some combinations are supported,
    472 // - PressedOnDragDropHold can generally be associated with any flag.
    473 // - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
    474 //------------------------------------------------------------------------------------------------------------------------------------------------
    475 // The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
    476 //                                         Repeat+                  Repeat+           Repeat+             Repeat+
    477 //                                         PressedOnClickRelease    PressedOnClick    PressedOnRelease    PressedOnDoubleClick
    478 //-------------------------------------------------------------------------------------------------------------------------------------------------
    479 //   Frame N+0 (mouse button is down)       -                        true              -                   true
    480 //   ...                                    -                        -                 -                   -
    481 //   Frame N + RepeatDelay                  true                     true              -                   true
    482 //   ...                                    -                        -                 -                   -
    483 //   Frame N + RepeatDelay + RepeatRate*N   true                     true              -                   true
    484 //-------------------------------------------------------------------------------------------------------------------------------------------------
    485 
    486 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
    487 {
    488     ImGuiContext& g = *GImGui;
    489     ImGuiWindow* window = GetCurrentWindow();
    490 
    491     if (flags & ImGuiButtonFlags_Disabled)
    492     {
    493         if (out_hovered) *out_hovered = false;
    494         if (out_held) *out_held = false;
    495         if (g.ActiveId == id) ClearActiveID();
    496         return false;
    497     }
    498 
    499     // Default only reacts to left mouse button
    500     if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
    501         flags |= ImGuiButtonFlags_MouseButtonDefault_;
    502 
    503     // Default behavior requires click + release inside bounding box
    504     if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
    505         flags |= ImGuiButtonFlags_PressedOnDefault_;
    506 
    507     ImGuiWindow* backup_hovered_window = g.HoveredWindow;
    508     const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window;
    509     if (flatten_hovered_children)
    510         g.HoveredWindow = window;
    511 
    512 #ifdef IMGUI_ENABLE_TEST_ENGINE
    513     if (id != 0 && window->DC.LastItemId != id)
    514         IMGUI_TEST_ENGINE_ITEM_ADD(bb, id);
    515 #endif
    516 
    517     bool pressed = false;
    518     bool hovered = ItemHoverable(bb, id);
    519 
    520     // Drag source doesn't report as hovered
    521     if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
    522         hovered = false;
    523 
    524     // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
    525     if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
    526         if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
    527         {
    528             hovered = true;
    529             SetHoveredID(id);
    530             if (CalcTypematicRepeatAmount(g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, g.HoveredIdTimer + 0.0001f, DRAGDROP_HOLD_TO_OPEN_TIMER, 0.00f))
    531             {
    532                 pressed = true;
    533                 g.DragDropHoldJustPressedId = id;
    534                 FocusWindow(window);
    535             }
    536         }
    537 
    538     if (flatten_hovered_children)
    539         g.HoveredWindow = backup_hovered_window;
    540 
    541     // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
    542     if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
    543         hovered = false;
    544 
    545     // Mouse handling
    546     if (hovered)
    547     {
    548         if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
    549         {
    550             // Poll buttons
    551             int mouse_button_clicked = -1;
    552             int mouse_button_released = -1;
    553             if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseClicked[0])         { mouse_button_clicked = 0; }
    554             else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseClicked[1])   { mouse_button_clicked = 1; }
    555             else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseClicked[2])  { mouse_button_clicked = 2; }
    556             if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseReleased[0])        { mouse_button_released = 0; }
    557             else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseReleased[1])  { mouse_button_released = 1; }
    558             else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseReleased[2]) { mouse_button_released = 2; }
    559 
    560             if (mouse_button_clicked != -1 && g.ActiveId != id)
    561             {
    562                 if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
    563                 {
    564                     SetActiveID(id, window);
    565                     g.ActiveIdMouseButton = mouse_button_clicked;
    566                     if (!(flags & ImGuiButtonFlags_NoNavFocus))
    567                         SetFocusID(id, window);
    568                     FocusWindow(window);
    569                 }
    570                 if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[mouse_button_clicked]))
    571                 {
    572                     pressed = true;
    573                     if (flags & ImGuiButtonFlags_NoHoldingActiveId)
    574                         ClearActiveID();
    575                     else
    576                         SetActiveID(id, window); // Hold on ID
    577                     g.ActiveIdMouseButton = mouse_button_clicked;
    578                     FocusWindow(window);
    579                 }
    580             }
    581             if ((flags & ImGuiButtonFlags_PressedOnRelease) && mouse_button_released != -1)
    582             {
    583                 // Repeat mode trumps on release behavior
    584                 const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay;
    585                 if (!has_repeated_at_least_once)
    586                     pressed = true;
    587                 ClearActiveID();
    588             }
    589 
    590             // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
    591             // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
    592             if (g.ActiveId == id && (flags & ImGuiButtonFlags_Repeat))
    593                 if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, true))
    594                     pressed = true;
    595         }
    596 
    597         if (pressed)
    598             g.NavDisableHighlight = true;
    599     }
    600 
    601     // Gamepad/Keyboard navigation
    602     // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
    603     if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
    604         if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
    605             hovered = true;
    606     if (g.NavActivateDownId == id)
    607     {
    608         bool nav_activated_by_code = (g.NavActivateId == id);
    609         bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
    610         if (nav_activated_by_code || nav_activated_by_inputs)
    611             pressed = true;
    612         if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
    613         {
    614             // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
    615             g.NavActivateId = id; // This is so SetActiveId assign a Nav source
    616             SetActiveID(id, window);
    617             if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus))
    618                 SetFocusID(id, window);
    619         }
    620     }
    621 
    622     // Process while held
    623     bool held = false;
    624     if (g.ActiveId == id)
    625     {
    626         if (g.ActiveIdSource == ImGuiInputSource_Mouse)
    627         {
    628             if (g.ActiveIdIsJustActivated)
    629                 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
    630 
    631             const int mouse_button = g.ActiveIdMouseButton;
    632             IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT);
    633             if (g.IO.MouseDown[mouse_button])
    634             {
    635                 held = true;
    636             }
    637             else
    638             {
    639                 bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
    640                 bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
    641                 if ((release_in || release_anywhere) && !g.DragDropActive)
    642                 {
    643                     // Report as pressed when releasing the mouse (this is the most common path)
    644                     bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDownWasDoubleClick[mouse_button];
    645                     bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
    646                     if (!is_double_click_release && !is_repeating_already)
    647                         pressed = true;
    648                 }
    649                 ClearActiveID();
    650             }
    651             if (!(flags & ImGuiButtonFlags_NoNavFocus))
    652                 g.NavDisableHighlight = true;
    653         }
    654         else if (g.ActiveIdSource == ImGuiInputSource_Nav)
    655         {
    656             // When activated using Nav, we hold on the ActiveID until activation button is released
    657             if (g.NavActivateDownId != id)
    658                 ClearActiveID();
    659         }
    660         if (pressed)
    661             g.ActiveIdHasBeenPressedBefore = true;
    662     }
    663 
    664     if (out_hovered) *out_hovered = hovered;
    665     if (out_held) *out_held = held;
    666 
    667     return pressed;
    668 }
    669 
    670 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
    671 {
    672     ImGuiWindow* window = GetCurrentWindow();
    673     if (window->SkipItems)
    674         return false;
    675 
    676     ImGuiContext& g = *GImGui;
    677     const ImGuiStyle& style = g.Style;
    678     const ImGuiID id = window->GetID(label);
    679     const ImVec2 label_size = CalcTextSize(label, NULL, true);
    680 
    681     ImVec2 pos = window->DC.CursorPos;
    682     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)
    683         pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
    684     ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
    685 
    686     const ImRect bb(pos, pos + size);
    687     ItemSize(size, style.FramePadding.y);
    688     if (!ItemAdd(bb, id))
    689         return false;
    690 
    691     if (g.CurrentItemFlags & ImGuiItemFlags_ButtonRepeat)
    692         flags |= ImGuiButtonFlags_Repeat;
    693     bool hovered, held;
    694     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
    695 
    696     // Render
    697     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
    698     RenderNavHighlight(bb, id);
    699     RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
    700 
    701     if (g.LogEnabled)
    702         LogSetNextTextDecoration("[", "]");
    703     RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
    704 
    705     // Automatically close popups
    706     //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
    707     //    CloseCurrentPopup();
    708 
    709     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
    710     return pressed;
    711 }
    712 
    713 bool ImGui::Button(const char* label, const ImVec2& size_arg)
    714 {
    715     return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
    716 }
    717 
    718 // Small buttons fits within text without additional vertical spacing.
    719 bool ImGui::SmallButton(const char* label)
    720 {
    721     ImGuiContext& g = *GImGui;
    722     float backup_padding_y = g.Style.FramePadding.y;
    723     g.Style.FramePadding.y = 0.0f;
    724     bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
    725     g.Style.FramePadding.y = backup_padding_y;
    726     return pressed;
    727 }
    728 
    729 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
    730 // 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)
    731 bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
    732 {
    733     ImGuiWindow* window = GetCurrentWindow();
    734     if (window->SkipItems)
    735         return false;
    736 
    737     // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
    738     IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
    739 
    740     const ImGuiID id = window->GetID(str_id);
    741     ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
    742     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    743     ItemSize(size);
    744     if (!ItemAdd(bb, id))
    745         return false;
    746 
    747     bool hovered, held;
    748     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
    749 
    750     return pressed;
    751 }
    752 
    753 bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
    754 {
    755     ImGuiWindow* window = GetCurrentWindow();
    756     if (window->SkipItems)
    757         return false;
    758 
    759     ImGuiContext& g = *GImGui;
    760     const ImGuiID id = window->GetID(str_id);
    761     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    762     const float default_size = GetFrameHeight();
    763     ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
    764     if (!ItemAdd(bb, id))
    765         return false;
    766 
    767     if (g.CurrentItemFlags & ImGuiItemFlags_ButtonRepeat)
    768         flags |= ImGuiButtonFlags_Repeat;
    769 
    770     bool hovered, held;
    771     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
    772 
    773     // Render
    774     const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
    775     const ImU32 text_col = GetColorU32(ImGuiCol_Text);
    776     RenderNavHighlight(bb, id);
    777     RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
    778     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);
    779 
    780     return pressed;
    781 }
    782 
    783 bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
    784 {
    785     float sz = GetFrameHeight();
    786     return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
    787 }
    788 
    789 // Button to close a window
    790 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
    791 {
    792     ImGuiContext& g = *GImGui;
    793     ImGuiWindow* window = g.CurrentWindow;
    794 
    795     // 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)
    796     // 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?
    797     const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
    798     ImRect bb_interact = bb;
    799     const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
    800     if (area_to_visible_ratio < 1.5f)
    801         bb_interact.Expand(ImFloor(bb_interact.GetSize() * -0.25f));
    802 
    803     // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
    804     // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
    805     bool is_clipped = !ItemAdd(bb_interact, id);
    806 
    807     bool hovered, held;
    808     bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
    809     if (is_clipped)
    810         return pressed;
    811 
    812     // Render
    813     // FIXME: Clarify this mess
    814     ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
    815     ImVec2 center = bb.GetCenter();
    816     if (hovered)
    817         window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12);
    818 
    819     float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
    820     ImU32 cross_col = GetColorU32(ImGuiCol_Text);
    821     center -= ImVec2(0.5f, 0.5f);
    822     window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);
    823     window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);
    824 
    825     return pressed;
    826 }
    827 
    828 bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
    829 {
    830     ImGuiContext& g = *GImGui;
    831     ImGuiWindow* window = g.CurrentWindow;
    832 
    833     ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
    834     ItemAdd(bb, id);
    835     bool hovered, held;
    836     bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
    837 
    838     // Render
    839     ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
    840     ImU32 text_col = GetColorU32(ImGuiCol_Text);
    841     ImVec2 center = bb.GetCenter();
    842     if (hovered || held)
    843         window->DrawList->AddCircleFilled(center/*+ ImVec2(0.0f, -0.5f)*/, g.FontSize * 0.5f + 1.0f, bg_col, 12);
    844     RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
    845 
    846     // Switch to moving the window after mouse is moved beyond the initial drag threshold
    847     if (IsItemActive() && IsMouseDragging(0))
    848         StartMouseMovingWindow(window);
    849 
    850     return pressed;
    851 }
    852 
    853 ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
    854 {
    855     return window->GetIDNoKeepAlive(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
    856 }
    857 
    858 // Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
    859 ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
    860 {
    861     const ImRect outer_rect = window->Rect();
    862     const ImRect inner_rect = window->InnerRect;
    863     const float border_size = window->WindowBorderSize;
    864     const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
    865     IM_ASSERT(scrollbar_size > 0.0f);
    866     if (axis == ImGuiAxis_X)
    867         return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x, outer_rect.Max.y);
    868     else
    869         return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x, inner_rect.Max.y);
    870 }
    871 
    872 void ImGui::Scrollbar(ImGuiAxis axis)
    873 {
    874     ImGuiContext& g = *GImGui;
    875     ImGuiWindow* window = g.CurrentWindow;
    876 
    877     const ImGuiID id = GetWindowScrollbarID(window, axis);
    878     KeepAliveID(id);
    879 
    880     // Calculate scrollbar bounding box
    881     ImRect bb = GetWindowScrollbarRect(window, axis);
    882     ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
    883     if (axis == ImGuiAxis_X)
    884     {
    885         rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
    886         if (!window->ScrollbarY)
    887             rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
    888     }
    889     else
    890     {
    891         if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
    892             rounding_corners |= ImDrawFlags_RoundCornersTopRight;
    893         if (!window->ScrollbarX)
    894             rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
    895     }
    896     float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
    897     float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
    898     ScrollbarEx(bb, id, axis, &window->Scroll[axis], size_avail, size_contents, rounding_corners);
    899 }
    900 
    901 // Vertical/Horizontal scrollbar
    902 // The entire piece of code below is rather confusing because:
    903 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
    904 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
    905 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
    906 // Still, the code should probably be made simpler..
    907 bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float size_avail_v, float size_contents_v, ImDrawFlags flags)
    908 {
    909     ImGuiContext& g = *GImGui;
    910     ImGuiWindow* window = g.CurrentWindow;
    911     if (window->SkipItems)
    912         return false;
    913 
    914     const float bb_frame_width = bb_frame.GetWidth();
    915     const float bb_frame_height = bb_frame.GetHeight();
    916     if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
    917         return false;
    918 
    919     // 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)
    920     float alpha = 1.0f;
    921     if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
    922         alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
    923     if (alpha <= 0.0f)
    924         return false;
    925 
    926     const ImGuiStyle& style = g.Style;
    927     const bool allow_interaction = (alpha >= 1.0f);
    928 
    929     ImRect bb = bb_frame;
    930     bb.Expand(ImVec2(-ImClamp(IM_FLOOR((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_FLOOR((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
    931 
    932     // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
    933     const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
    934 
    935     // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
    936     // But we maintain a minimum size in pixel to allow for the user to still aim inside.
    937     IM_ASSERT(ImMax(size_contents_v, size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
    938     const float win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), 1.0f);
    939     const float grab_h_pixels = ImClamp(scrollbar_size_v * (size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
    940     const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
    941 
    942     // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
    943     bool held = false;
    944     bool hovered = false;
    945     ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
    946 
    947     float scroll_max = ImMax(1.0f, size_contents_v - size_avail_v);
    948     float scroll_ratio = ImSaturate(*p_scroll_v / scroll_max);
    949     float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
    950     if (held && allow_interaction && grab_h_norm < 1.0f)
    951     {
    952         float scrollbar_pos_v = bb.Min[axis];
    953         float mouse_pos_v = g.IO.MousePos[axis];
    954 
    955         // Click position in scrollbar normalized space (0.0f->1.0f)
    956         const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
    957         SetHoveredID(id);
    958 
    959         bool seek_absolute = false;
    960         if (g.ActiveIdIsJustActivated)
    961         {
    962             // On initial click calculate the distance between mouse and the center of the grab
    963             seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm);
    964             if (seek_absolute)
    965                 g.ScrollbarClickDeltaToGrabCenter = 0.0f;
    966             else
    967                 g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
    968         }
    969 
    970         // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
    971         // 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
    972         const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
    973         *p_scroll_v = IM_ROUND(scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
    974 
    975         // Update values for rendering
    976         scroll_ratio = ImSaturate(*p_scroll_v / scroll_max);
    977         grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
    978 
    979         // Update distance to grab now that we have seeked and saturated
    980         if (seek_absolute)
    981             g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
    982     }
    983 
    984     // Render
    985     const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
    986     const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
    987     window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags);
    988     ImRect grab_rect;
    989     if (axis == ImGuiAxis_X)
    990         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);
    991     else
    992         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);
    993     window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
    994 
    995     return held;
    996 }
    997 
    998 void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
    999 {
   1000     ImGuiWindow* window = GetCurrentWindow();
   1001     if (window->SkipItems)
   1002         return;
   1003 
   1004     ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
   1005     if (border_col.w > 0.0f)
   1006         bb.Max += ImVec2(2, 2);
   1007     ItemSize(bb);
   1008     if (!ItemAdd(bb, 0))
   1009         return;
   1010 
   1011     if (border_col.w > 0.0f)
   1012     {
   1013         window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
   1014         window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
   1015     }
   1016     else
   1017     {
   1018         window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
   1019     }
   1020 }
   1021 
   1022 // ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
   1023 // We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
   1024 bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col)
   1025 {
   1026     ImGuiContext& g = *GImGui;
   1027     ImGuiWindow* window = GetCurrentWindow();
   1028     if (window->SkipItems)
   1029         return false;
   1030 
   1031     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
   1032     ItemSize(bb);
   1033     if (!ItemAdd(bb, id))
   1034         return false;
   1035 
   1036     bool hovered, held;
   1037     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
   1038 
   1039     // Render
   1040     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
   1041     RenderNavHighlight(bb, id);
   1042     RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
   1043     if (bg_col.w > 0.0f)
   1044         window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
   1045     window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
   1046 
   1047     return pressed;
   1048 }
   1049 
   1050 // frame_padding < 0: uses FramePadding from style (default)
   1051 // frame_padding = 0: no framing
   1052 // frame_padding > 0: set framing size
   1053 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)
   1054 {
   1055     ImGuiContext& g = *GImGui;
   1056     ImGuiWindow* window = g.CurrentWindow;
   1057     if (window->SkipItems)
   1058         return false;
   1059 
   1060     // Default to using texture ID as ID. User can still push string/integer prefixes.
   1061     PushID((void*)(intptr_t)user_texture_id);
   1062     const ImGuiID id = window->GetID("#image");
   1063     PopID();
   1064 
   1065     const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding;
   1066     return ImageButtonEx(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col);
   1067 }
   1068 
   1069 bool ImGui::Checkbox(const char* label, bool* v)
   1070 {
   1071     ImGuiWindow* window = GetCurrentWindow();
   1072     if (window->SkipItems)
   1073         return false;
   1074 
   1075     ImGuiContext& g = *GImGui;
   1076     const ImGuiStyle& style = g.Style;
   1077     const ImGuiID id = window->GetID(label);
   1078     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   1079 
   1080     const float square_sz = GetFrameHeight();
   1081     const ImVec2 pos = window->DC.CursorPos;
   1082     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));
   1083     ItemSize(total_bb, style.FramePadding.y);
   1084     if (!ItemAdd(total_bb, id))
   1085     {
   1086         IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
   1087         return false;
   1088     }
   1089 
   1090     bool hovered, held;
   1091     bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
   1092     if (pressed)
   1093     {
   1094         *v = !(*v);
   1095         MarkItemEdited(id);
   1096     }
   1097 
   1098     const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
   1099     RenderNavHighlight(total_bb, id);
   1100     RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
   1101     ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
   1102     bool mixed_value = (g.CurrentItemFlags & ImGuiItemFlags_MixedValue) != 0;
   1103     if (mixed_value)
   1104     {
   1105         // Undocumented tristate/mixed/indeterminate checkbox (#2644)
   1106         // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
   1107         ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)));
   1108         window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
   1109     }
   1110     else if (*v)
   1111     {
   1112         const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
   1113         RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
   1114     }
   1115 
   1116     ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
   1117     if (g.LogEnabled)
   1118         LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
   1119     if (label_size.x > 0.0f)
   1120         RenderText(label_pos, label);
   1121 
   1122     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
   1123     return pressed;
   1124 }
   1125 
   1126 template<typename T>
   1127 bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
   1128 {
   1129     bool all_on = (*flags & flags_value) == flags_value;
   1130     bool any_on = (*flags & flags_value) != 0;
   1131     bool pressed;
   1132     if (!all_on && any_on)
   1133     {
   1134         ImGuiContext& g = *GImGui;
   1135         ImGuiItemFlags backup_item_flags = g.CurrentItemFlags;
   1136         g.CurrentItemFlags |= ImGuiItemFlags_MixedValue;
   1137         pressed = Checkbox(label, &all_on);
   1138         g.CurrentItemFlags = backup_item_flags;
   1139     }
   1140     else
   1141     {
   1142         pressed = Checkbox(label, &all_on);
   1143 
   1144     }
   1145     if (pressed)
   1146     {
   1147         if (all_on)
   1148             *flags |= flags_value;
   1149         else
   1150             *flags &= ~flags_value;
   1151     }
   1152     return pressed;
   1153 }
   1154 
   1155 bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
   1156 {
   1157     return CheckboxFlagsT(label, flags, flags_value);
   1158 }
   1159 
   1160 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
   1161 {
   1162     return CheckboxFlagsT(label, flags, flags_value);
   1163 }
   1164 
   1165 bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
   1166 {
   1167     return CheckboxFlagsT(label, flags, flags_value);
   1168 }
   1169 
   1170 bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
   1171 {
   1172     return CheckboxFlagsT(label, flags, flags_value);
   1173 }
   1174 
   1175 bool ImGui::RadioButton(const char* label, bool active)
   1176 {
   1177     ImGuiWindow* window = GetCurrentWindow();
   1178     if (window->SkipItems)
   1179         return false;
   1180 
   1181     ImGuiContext& g = *GImGui;
   1182     const ImGuiStyle& style = g.Style;
   1183     const ImGuiID id = window->GetID(label);
   1184     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   1185 
   1186     const float square_sz = GetFrameHeight();
   1187     const ImVec2 pos = window->DC.CursorPos;
   1188     const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
   1189     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));
   1190     ItemSize(total_bb, style.FramePadding.y);
   1191     if (!ItemAdd(total_bb, id))
   1192         return false;
   1193 
   1194     ImVec2 center = check_bb.GetCenter();
   1195     center.x = IM_ROUND(center.x);
   1196     center.y = IM_ROUND(center.y);
   1197     const float radius = (square_sz - 1.0f) * 0.5f;
   1198 
   1199     bool hovered, held;
   1200     bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
   1201     if (pressed)
   1202         MarkItemEdited(id);
   1203 
   1204     RenderNavHighlight(total_bb, id);
   1205     window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
   1206     if (active)
   1207     {
   1208         const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
   1209         window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);
   1210     }
   1211 
   1212     if (style.FrameBorderSize > 0.0f)
   1213     {
   1214         window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
   1215         window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
   1216     }
   1217 
   1218     ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
   1219     if (g.LogEnabled)
   1220         LogRenderedText(&label_pos, active ? "(x)" : "( )");
   1221     if (label_size.x > 0.0f)
   1222         RenderText(label_pos, label);
   1223 
   1224     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
   1225     return pressed;
   1226 }
   1227 
   1228 // 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..
   1229 bool ImGui::RadioButton(const char* label, int* v, int v_button)
   1230 {
   1231     const bool pressed = RadioButton(label, *v == v_button);
   1232     if (pressed)
   1233         *v = v_button;
   1234     return pressed;
   1235 }
   1236 
   1237 // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
   1238 void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
   1239 {
   1240     ImGuiWindow* window = GetCurrentWindow();
   1241     if (window->SkipItems)
   1242         return;
   1243 
   1244     ImGuiContext& g = *GImGui;
   1245     const ImGuiStyle& style = g.Style;
   1246 
   1247     ImVec2 pos = window->DC.CursorPos;
   1248     ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);
   1249     ImRect bb(pos, pos + size);
   1250     ItemSize(size, style.FramePadding.y);
   1251     if (!ItemAdd(bb, 0))
   1252         return;
   1253 
   1254     // Render
   1255     fraction = ImSaturate(fraction);
   1256     RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
   1257     bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
   1258     const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
   1259     RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
   1260 
   1261     // Default displaying the fraction as percentage string, but user can override it
   1262     char overlay_buf[32];
   1263     if (!overlay)
   1264     {
   1265         ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
   1266         overlay = overlay_buf;
   1267     }
   1268 
   1269     ImVec2 overlay_size = CalcTextSize(overlay, NULL);
   1270     if (overlay_size.x > 0.0f)
   1271         RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb);
   1272 }
   1273 
   1274 void ImGui::Bullet()
   1275 {
   1276     ImGuiWindow* window = GetCurrentWindow();
   1277     if (window->SkipItems)
   1278         return;
   1279 
   1280     ImGuiContext& g = *GImGui;
   1281     const ImGuiStyle& style = g.Style;
   1282     const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize);
   1283     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
   1284     ItemSize(bb);
   1285     if (!ItemAdd(bb, 0))
   1286     {
   1287         SameLine(0, style.FramePadding.x * 2);
   1288         return;
   1289     }
   1290 
   1291     // Render and stay on same line
   1292     ImU32 text_col = GetColorU32(ImGuiCol_Text);
   1293     RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col);
   1294     SameLine(0, style.FramePadding.x * 2.0f);
   1295 }
   1296 
   1297 //-------------------------------------------------------------------------
   1298 // [SECTION] Widgets: Low-level Layout helpers
   1299 //-------------------------------------------------------------------------
   1300 // - Spacing()
   1301 // - Dummy()
   1302 // - NewLine()
   1303 // - AlignTextToFramePadding()
   1304 // - SeparatorEx() [Internal]
   1305 // - Separator()
   1306 // - SplitterBehavior() [Internal]
   1307 // - ShrinkWidths() [Internal]
   1308 //-------------------------------------------------------------------------
   1309 
   1310 void ImGui::Spacing()
   1311 {
   1312     ImGuiWindow* window = GetCurrentWindow();
   1313     if (window->SkipItems)
   1314         return;
   1315     ItemSize(ImVec2(0, 0));
   1316 }
   1317 
   1318 void ImGui::Dummy(const ImVec2& size)
   1319 {
   1320     ImGuiWindow* window = GetCurrentWindow();
   1321     if (window->SkipItems)
   1322         return;
   1323 
   1324     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
   1325     ItemSize(size);
   1326     ItemAdd(bb, 0);
   1327 }
   1328 
   1329 void ImGui::NewLine()
   1330 {
   1331     ImGuiWindow* window = GetCurrentWindow();
   1332     if (window->SkipItems)
   1333         return;
   1334 
   1335     ImGuiContext& g = *GImGui;
   1336     const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
   1337     window->DC.LayoutType = ImGuiLayoutType_Vertical;
   1338     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.
   1339         ItemSize(ImVec2(0, 0));
   1340     else
   1341         ItemSize(ImVec2(0.0f, g.FontSize));
   1342     window->DC.LayoutType = backup_layout_type;
   1343 }
   1344 
   1345 void ImGui::AlignTextToFramePadding()
   1346 {
   1347     ImGuiWindow* window = GetCurrentWindow();
   1348     if (window->SkipItems)
   1349         return;
   1350 
   1351     ImGuiContext& g = *GImGui;
   1352     window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
   1353     window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
   1354 }
   1355 
   1356 // Horizontal/vertical separating line
   1357 void ImGui::SeparatorEx(ImGuiSeparatorFlags flags)
   1358 {
   1359     ImGuiWindow* window = GetCurrentWindow();
   1360     if (window->SkipItems)
   1361         return;
   1362 
   1363     ImGuiContext& g = *GImGui;
   1364     IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)));   // Check that only 1 option is selected
   1365 
   1366     float thickness_draw = 1.0f;
   1367     float thickness_layout = 0.0f;
   1368     if (flags & ImGuiSeparatorFlags_Vertical)
   1369     {
   1370         // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout.
   1371         float y1 = window->DC.CursorPos.y;
   1372         float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
   1373         const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness_draw, y2));
   1374         ItemSize(ImVec2(thickness_layout, 0.0f));
   1375         if (!ItemAdd(bb, 0))
   1376             return;
   1377 
   1378         // Draw
   1379         window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
   1380         if (g.LogEnabled)
   1381             LogText(" |");
   1382     }
   1383     else if (flags & ImGuiSeparatorFlags_Horizontal)
   1384     {
   1385         // Horizontal Separator
   1386         float x1 = window->Pos.x;
   1387         float x2 = window->Pos.x + window->Size.x;
   1388 
   1389         // FIXME-WORKRECT: old hack (#205) until we decide of consistent behavior with WorkRect/Indent and Separator
   1390         if (g.GroupStack.Size > 0 && g.GroupStack.back().WindowID == window->ID)
   1391             x1 += window->DC.Indent.x;
   1392 
   1393         ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
   1394         if (columns)
   1395             PushColumnsBackground();
   1396 
   1397         // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
   1398         const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness_draw));
   1399         ItemSize(ImVec2(0.0f, thickness_layout));
   1400         const bool item_visible = ItemAdd(bb, 0);
   1401         if (item_visible)
   1402         {
   1403             // Draw
   1404             window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Separator));
   1405             if (g.LogEnabled)
   1406                 LogRenderedText(&bb.Min, "--------------------------------\n");
   1407 
   1408         }
   1409         if (columns)
   1410         {
   1411             PopColumnsBackground();
   1412             columns->LineMinY = window->DC.CursorPos.y;
   1413         }
   1414     }
   1415 }
   1416 
   1417 void ImGui::Separator()
   1418 {
   1419     ImGuiContext& g = *GImGui;
   1420     ImGuiWindow* window = g.CurrentWindow;
   1421     if (window->SkipItems)
   1422         return;
   1423 
   1424     // Those flags should eventually be overridable by the user
   1425     ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
   1426     flags |= ImGuiSeparatorFlags_SpanAllColumns;
   1427     SeparatorEx(flags);
   1428 }
   1429 
   1430 // 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.
   1431 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)
   1432 {
   1433     ImGuiContext& g = *GImGui;
   1434     ImGuiWindow* window = g.CurrentWindow;
   1435 
   1436     const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags;
   1437     g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
   1438     bool item_add = ItemAdd(bb, id);
   1439     g.CurrentItemFlags = item_flags_backup;
   1440     if (!item_add)
   1441         return false;
   1442 
   1443     bool hovered, held;
   1444     ImRect bb_interact = bb;
   1445     bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
   1446     ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
   1447     if (g.ActiveId != id)
   1448         SetItemAllowOverlap();
   1449 
   1450     if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
   1451         SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
   1452 
   1453     ImRect bb_render = bb;
   1454     if (held)
   1455     {
   1456         ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
   1457         float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
   1458 
   1459         // Minimum pane size
   1460         float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
   1461         float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
   1462         if (mouse_delta < -size_1_maximum_delta)
   1463             mouse_delta = -size_1_maximum_delta;
   1464         if (mouse_delta > size_2_maximum_delta)
   1465             mouse_delta = size_2_maximum_delta;
   1466 
   1467         // Apply resize
   1468         if (mouse_delta != 0.0f)
   1469         {
   1470             if (mouse_delta < 0.0f)
   1471                 IM_ASSERT(*size1 + mouse_delta >= min_size1);
   1472             if (mouse_delta > 0.0f)
   1473                 IM_ASSERT(*size2 - mouse_delta >= min_size2);
   1474             *size1 += mouse_delta;
   1475             *size2 -= mouse_delta;
   1476             bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
   1477             MarkItemEdited(id);
   1478         }
   1479     }
   1480 
   1481     // Render
   1482     const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
   1483     window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
   1484 
   1485     return held;
   1486 }
   1487 
   1488 static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
   1489 {
   1490     const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
   1491     const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
   1492     if (int d = (int)(b->Width - a->Width))
   1493         return d;
   1494     return (b->Index - a->Index);
   1495 }
   1496 
   1497 // Shrink excess width from a set of item, by removing width from the larger items first.
   1498 // Set items Width to -1.0f to disable shrinking this item.
   1499 void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
   1500 {
   1501     if (count == 1)
   1502     {
   1503         if (items[0].Width >= 0.0f)
   1504             items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
   1505         return;
   1506     }
   1507     ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
   1508     int count_same_width = 1;
   1509     while (width_excess > 0.0f && count_same_width < count)
   1510     {
   1511         while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
   1512             count_same_width++;
   1513         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);
   1514         if (max_width_to_remove_per_item <= 0.0f)
   1515             break;
   1516         float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
   1517         for (int item_n = 0; item_n < count_same_width; item_n++)
   1518             items[item_n].Width -= width_to_remove_per_item;
   1519         width_excess -= width_to_remove_per_item * count_same_width;
   1520     }
   1521 
   1522     // Round width and redistribute remainder left-to-right (could make it an option of the function?)
   1523     // 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.
   1524     width_excess = 0.0f;
   1525     for (int n = 0; n < count; n++)
   1526     {
   1527         float width_rounded = ImFloor(items[n].Width);
   1528         width_excess += items[n].Width - width_rounded;
   1529         items[n].Width = width_rounded;
   1530     }
   1531     if (width_excess > 0.0f)
   1532         for (int n = 0; n < count; n++)
   1533             if (items[n].Index < (int)(width_excess + 0.01f))
   1534                 items[n].Width += 1.0f;
   1535 }
   1536 
   1537 //-------------------------------------------------------------------------
   1538 // [SECTION] Widgets: ComboBox
   1539 //-------------------------------------------------------------------------
   1540 // - BeginCombo()
   1541 // - EndCombo()
   1542 // - Combo()
   1543 //-------------------------------------------------------------------------
   1544 
   1545 static float CalcMaxPopupHeightFromItemCount(int items_count)
   1546 {
   1547     ImGuiContext& g = *GImGui;
   1548     if (items_count <= 0)
   1549         return FLT_MAX;
   1550     return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
   1551 }
   1552 
   1553 bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
   1554 {
   1555     // Always consume the SetNextWindowSizeConstraint() call in our early return paths
   1556     ImGuiContext& g = *GImGui;
   1557     bool has_window_size_constraint = (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) != 0;
   1558     g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint;
   1559 
   1560     ImGuiWindow* window = GetCurrentWindow();
   1561     if (window->SkipItems)
   1562         return false;
   1563 
   1564     IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
   1565 
   1566     const ImGuiStyle& style = g.Style;
   1567     const ImGuiID id = window->GetID(label);
   1568 
   1569     const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
   1570     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   1571     const float expected_w = CalcItemWidth();
   1572     const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w;
   1573     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
   1574     const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
   1575     ItemSize(total_bb, style.FramePadding.y);
   1576     if (!ItemAdd(total_bb, id, &frame_bb))
   1577         return false;
   1578 
   1579     bool hovered, held;
   1580     bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
   1581 
   1582     const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id);
   1583     bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);
   1584 
   1585     const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
   1586     const float value_x2 = ImMax(frame_bb.Min.x, frame_bb.Max.x - arrow_size);
   1587     RenderNavHighlight(frame_bb, id);
   1588     if (!(flags & ImGuiComboFlags_NoPreview))
   1589         window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(value_x2, frame_bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
   1590     if (!(flags & ImGuiComboFlags_NoArrowButton))
   1591     {
   1592         ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
   1593         ImU32 text_col = GetColorU32(ImGuiCol_Text);
   1594         window->DrawList->AddRectFilled(ImVec2(value_x2, frame_bb.Min.y), frame_bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
   1595         if (value_x2 + arrow_size - style.FramePadding.x <= frame_bb.Max.x)
   1596             RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
   1597     }
   1598     RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
   1599     if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
   1600     {
   1601         ImVec2 preview_pos = frame_bb.Min + style.FramePadding;
   1602         if (g.LogEnabled)
   1603             LogSetNextTextDecoration("{", "}");
   1604         RenderTextClipped(preview_pos, ImVec2(value_x2, frame_bb.Max.y), preview_value, NULL, NULL, ImVec2(0.0f, 0.0f));
   1605     }
   1606     if (label_size.x > 0)
   1607         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
   1608 
   1609     if ((pressed || g.NavActivateId == id) && !popup_open)
   1610     {
   1611         if (window->DC.NavLayerCurrent == 0)
   1612             window->NavLastIds[0] = id;
   1613         OpenPopupEx(popup_id, ImGuiPopupFlags_None);
   1614         popup_open = true;
   1615     }
   1616 
   1617     if (!popup_open)
   1618         return false;
   1619 
   1620     if (has_window_size_constraint)
   1621     {
   1622         g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint;
   1623         g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
   1624     }
   1625     else
   1626     {
   1627         if ((flags & ImGuiComboFlags_HeightMask_) == 0)
   1628             flags |= ImGuiComboFlags_HeightRegular;
   1629         IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_));    // Only one
   1630         int popup_max_height_in_items = -1;
   1631         if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
   1632         else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
   1633         else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
   1634         SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
   1635     }
   1636 
   1637     char name[16];
   1638     ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
   1639 
   1640     // Position the window given a custom constraint (peak into expected window size so we can position it)
   1641     // This might be easier to express with an hypothetical SetNextWindowPosConstraints() function.
   1642     if (ImGuiWindow* popup_window = FindWindowByName(name))
   1643         if (popup_window->WasActive)
   1644         {
   1645             // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
   1646             ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);
   1647             if (flags & ImGuiComboFlags_PopupAlignLeft)
   1648                 popup_window->AutoPosLastDirection = ImGuiDir_Left; // "Below, Toward Left"
   1649             else
   1650                 popup_window->AutoPosLastDirection = ImGuiDir_Down; // "Below, Toward Right (default)"
   1651             ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
   1652             ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
   1653             SetNextWindowPos(pos);
   1654         }
   1655 
   1656     // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
   1657     ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
   1658 
   1659     // Horizontally align ourselves with the framed text
   1660     PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
   1661     bool ret = Begin(name, NULL, window_flags);
   1662     PopStyleVar();
   1663     if (!ret)
   1664     {
   1665         EndPopup();
   1666         IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above
   1667         return false;
   1668     }
   1669     return true;
   1670 }
   1671 
   1672 void ImGui::EndCombo()
   1673 {
   1674     EndPopup();
   1675 }
   1676 
   1677 // Getter for the old Combo() API: const char*[]
   1678 static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
   1679 {
   1680     const char* const* items = (const char* const*)data;
   1681     if (out_text)
   1682         *out_text = items[idx];
   1683     return true;
   1684 }
   1685 
   1686 // Getter for the old Combo() API: "item1\0item2\0item3\0"
   1687 static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
   1688 {
   1689     // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
   1690     const char* items_separated_by_zeros = (const char*)data;
   1691     int items_count = 0;
   1692     const char* p = items_separated_by_zeros;
   1693     while (*p)
   1694     {
   1695         if (idx == items_count)
   1696             break;
   1697         p += strlen(p) + 1;
   1698         items_count++;
   1699     }
   1700     if (!*p)
   1701         return false;
   1702     if (out_text)
   1703         *out_text = p;
   1704     return true;
   1705 }
   1706 
   1707 // Old API, prefer using BeginCombo() nowadays if you can.
   1708 bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
   1709 {
   1710     ImGuiContext& g = *GImGui;
   1711 
   1712     // Call the getter to obtain the preview string which is a parameter to BeginCombo()
   1713     const char* preview_value = NULL;
   1714     if (*current_item >= 0 && *current_item < items_count)
   1715         items_getter(data, *current_item, &preview_value);
   1716 
   1717     // 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.
   1718     if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
   1719         SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
   1720 
   1721     if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
   1722         return false;
   1723 
   1724     // Display items
   1725     // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
   1726     bool value_changed = false;
   1727     for (int i = 0; i < items_count; i++)
   1728     {
   1729         PushID((void*)(intptr_t)i);
   1730         const bool item_selected = (i == *current_item);
   1731         const char* item_text;
   1732         if (!items_getter(data, i, &item_text))
   1733             item_text = "*Unknown item*";
   1734         if (Selectable(item_text, item_selected))
   1735         {
   1736             value_changed = true;
   1737             *current_item = i;
   1738         }
   1739         if (item_selected)
   1740             SetItemDefaultFocus();
   1741         PopID();
   1742     }
   1743 
   1744     EndCombo();
   1745     if (value_changed)
   1746         MarkItemEdited(g.CurrentWindow->DC.LastItemId);
   1747 
   1748     return value_changed;
   1749 }
   1750 
   1751 // Combo box helper allowing to pass an array of strings.
   1752 bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
   1753 {
   1754     const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
   1755     return value_changed;
   1756 }
   1757 
   1758 // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
   1759 bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
   1760 {
   1761     int items_count = 0;
   1762     const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
   1763     while (*p)
   1764     {
   1765         p += strlen(p) + 1;
   1766         items_count++;
   1767     }
   1768     bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
   1769     return value_changed;
   1770 }
   1771 
   1772 //-------------------------------------------------------------------------
   1773 // [SECTION] Data Type and Data Formatting Helpers [Internal]
   1774 //-------------------------------------------------------------------------
   1775 // - PatchFormatStringFloatToInt()
   1776 // - DataTypeGetInfo()
   1777 // - DataTypeFormatString()
   1778 // - DataTypeApplyOp()
   1779 // - DataTypeApplyOpFromText()
   1780 // - DataTypeClamp()
   1781 // - GetMinimumStepAtDecimalPrecision
   1782 // - RoundScalarWithFormat<>()
   1783 //-------------------------------------------------------------------------
   1784 
   1785 static const ImGuiDataTypeInfo GDataTypeInfo[] =
   1786 {
   1787     { sizeof(char),             "S8",   "%d",   "%d"    },  // ImGuiDataType_S8
   1788     { sizeof(unsigned char),    "U8",   "%u",   "%u"    },
   1789     { sizeof(short),            "S16",  "%d",   "%d"    },  // ImGuiDataType_S16
   1790     { sizeof(unsigned short),   "U16",  "%u",   "%u"    },
   1791     { sizeof(int),              "S32",  "%d",   "%d"    },  // ImGuiDataType_S32
   1792     { sizeof(unsigned int),     "U32",  "%u",   "%u"    },
   1793 #ifdef _MSC_VER
   1794     { sizeof(ImS64),            "S64",  "%I64d","%I64d" },  // ImGuiDataType_S64
   1795     { sizeof(ImU64),            "U64",  "%I64u","%I64u" },
   1796 #else
   1797     { sizeof(ImS64),            "S64",  "%lld", "%lld"  },  // ImGuiDataType_S64
   1798     { sizeof(ImU64),            "U64",  "%llu", "%llu"  },
   1799 #endif
   1800     { sizeof(float),            "float", "%.3f","%f"    },  // ImGuiDataType_Float (float are promoted to double in va_arg)
   1801     { sizeof(double),           "double","%f",  "%lf"   },  // ImGuiDataType_Double
   1802 };
   1803 IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
   1804 
   1805 // FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
   1806 // Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
   1807 // To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
   1808 static const char* PatchFormatStringFloatToInt(const char* fmt)
   1809 {
   1810     if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
   1811         return "%d";
   1812     const char* fmt_start = ImParseFormatFindStart(fmt);    // Find % (if any, and ignore %%)
   1813     const char* fmt_end = ImParseFormatFindEnd(fmt_start);  // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
   1814     if (fmt_end > fmt_start && fmt_end[-1] == 'f')
   1815     {
   1816 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   1817         if (fmt_start == fmt && fmt_end[0] == 0)
   1818             return "%d";
   1819         ImGuiContext& g = *GImGui;
   1820         ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
   1821         return g.TempBuffer;
   1822 #else
   1823         IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
   1824 #endif
   1825     }
   1826     return fmt;
   1827 }
   1828 
   1829 const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
   1830 {
   1831     IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
   1832     return &GDataTypeInfo[data_type];
   1833 }
   1834 
   1835 int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
   1836 {
   1837     // Signedness doesn't matter when pushing integer arguments
   1838     if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
   1839         return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
   1840     if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
   1841         return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
   1842     if (data_type == ImGuiDataType_Float)
   1843         return ImFormatString(buf, buf_size, format, *(const float*)p_data);
   1844     if (data_type == ImGuiDataType_Double)
   1845         return ImFormatString(buf, buf_size, format, *(const double*)p_data);
   1846     if (data_type == ImGuiDataType_S8)
   1847         return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
   1848     if (data_type == ImGuiDataType_U8)
   1849         return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
   1850     if (data_type == ImGuiDataType_S16)
   1851         return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
   1852     if (data_type == ImGuiDataType_U16)
   1853         return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
   1854     IM_ASSERT(0);
   1855     return 0;
   1856 }
   1857 
   1858 void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
   1859 {
   1860     IM_ASSERT(op == '+' || op == '-');
   1861     switch (data_type)
   1862     {
   1863         case ImGuiDataType_S8:
   1864             if (op == '+') { *(ImS8*)output  = ImAddClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
   1865             if (op == '-') { *(ImS8*)output  = ImSubClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
   1866             return;
   1867         case ImGuiDataType_U8:
   1868             if (op == '+') { *(ImU8*)output  = ImAddClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
   1869             if (op == '-') { *(ImU8*)output  = ImSubClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
   1870             return;
   1871         case ImGuiDataType_S16:
   1872             if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
   1873             if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
   1874             return;
   1875         case ImGuiDataType_U16:
   1876             if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
   1877             if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
   1878             return;
   1879         case ImGuiDataType_S32:
   1880             if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
   1881             if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
   1882             return;
   1883         case ImGuiDataType_U32:
   1884             if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
   1885             if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
   1886             return;
   1887         case ImGuiDataType_S64:
   1888             if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
   1889             if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
   1890             return;
   1891         case ImGuiDataType_U64:
   1892             if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
   1893             if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
   1894             return;
   1895         case ImGuiDataType_Float:
   1896             if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
   1897             if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
   1898             return;
   1899         case ImGuiDataType_Double:
   1900             if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
   1901             if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
   1902             return;
   1903         case ImGuiDataType_COUNT: break;
   1904     }
   1905     IM_ASSERT(0);
   1906 }
   1907 
   1908 // User can input math operators (e.g. +100) to edit a numerical values.
   1909 // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
   1910 bool ImGui::DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* p_data, const char* format)
   1911 {
   1912     while (ImCharIsBlankA(*buf))
   1913         buf++;
   1914 
   1915     // We don't support '-' op because it would conflict with inputing negative value.
   1916     // Instead you can use +-100 to subtract from an existing value
   1917     char op = buf[0];
   1918     if (op == '+' || op == '*' || op == '/')
   1919     {
   1920         buf++;
   1921         while (ImCharIsBlankA(*buf))
   1922             buf++;
   1923     }
   1924     else
   1925     {
   1926         op = 0;
   1927     }
   1928     if (!buf[0])
   1929         return false;
   1930 
   1931     // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
   1932     const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
   1933     ImGuiDataTypeTempStorage data_backup;
   1934     memcpy(&data_backup, p_data, type_info->Size);
   1935 
   1936     if (format == NULL)
   1937         format = type_info->ScanFmt;
   1938 
   1939     // FIXME-LEGACY: The aim is to remove those operators and write a proper expression evaluator at some point..
   1940     int arg1i = 0;
   1941     if (data_type == ImGuiDataType_S32)
   1942     {
   1943         int* v = (int*)p_data;
   1944         int arg0i = *v;
   1945         float arg1f = 0.0f;
   1946         if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
   1947             return false;
   1948         // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision
   1949         if (op == '+')      { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); }                   // Add (use "+-" to subtract)
   1950         else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); }                   // Multiply
   1951         else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }  // Divide
   1952         else                { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; }                           // Assign constant
   1953     }
   1954     else if (data_type == ImGuiDataType_Float)
   1955     {
   1956         // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
   1957         format = "%f";
   1958         float* v = (float*)p_data;
   1959         float arg0f = *v, arg1f = 0.0f;
   1960         if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
   1961             return false;
   1962         if (sscanf(buf, format, &arg1f) < 1)
   1963             return false;
   1964         if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
   1965         else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
   1966         else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
   1967         else                { *v = arg1f; }                            // Assign constant
   1968     }
   1969     else if (data_type == ImGuiDataType_Double)
   1970     {
   1971         format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
   1972         double* v = (double*)p_data;
   1973         double arg0f = *v, arg1f = 0.0;
   1974         if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
   1975             return false;
   1976         if (sscanf(buf, format, &arg1f) < 1)
   1977             return false;
   1978         if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
   1979         else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
   1980         else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
   1981         else                { *v = arg1f; }                            // Assign constant
   1982     }
   1983     else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
   1984     {
   1985         // All other types assign constant
   1986         // We don't bother handling support for legacy operators since they are a little too crappy. Instead we will later implement a proper expression evaluator in the future.
   1987         if (sscanf(buf, format, p_data) < 1)
   1988             return false;
   1989     }
   1990     else
   1991     {
   1992         // Small types need a 32-bit buffer to receive the result from scanf()
   1993         int v32;
   1994         if (sscanf(buf, format, &v32) < 1)
   1995             return false;
   1996         if (data_type == ImGuiDataType_S8)
   1997             *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
   1998         else if (data_type == ImGuiDataType_U8)
   1999             *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
   2000         else if (data_type == ImGuiDataType_S16)
   2001             *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
   2002         else if (data_type == ImGuiDataType_U16)
   2003             *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
   2004         else
   2005             IM_ASSERT(0);
   2006     }
   2007 
   2008     return memcmp(&data_backup, p_data, type_info->Size) != 0;
   2009 }
   2010 
   2011 template<typename T>
   2012 static int DataTypeCompareT(const T* lhs, const T* rhs)
   2013 {
   2014     if (*lhs < *rhs) return -1;
   2015     if (*lhs > *rhs) return +1;
   2016     return 0;
   2017 }
   2018 
   2019 int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
   2020 {
   2021     switch (data_type)
   2022     {
   2023     case ImGuiDataType_S8:     return DataTypeCompareT<ImS8  >((const ImS8*  )arg_1, (const ImS8*  )arg_2);
   2024     case ImGuiDataType_U8:     return DataTypeCompareT<ImU8  >((const ImU8*  )arg_1, (const ImU8*  )arg_2);
   2025     case ImGuiDataType_S16:    return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);
   2026     case ImGuiDataType_U16:    return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);
   2027     case ImGuiDataType_S32:    return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);
   2028     case ImGuiDataType_U32:    return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);
   2029     case ImGuiDataType_S64:    return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);
   2030     case ImGuiDataType_U64:    return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);
   2031     case ImGuiDataType_Float:  return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);
   2032     case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);
   2033     case ImGuiDataType_COUNT:  break;
   2034     }
   2035     IM_ASSERT(0);
   2036     return 0;
   2037 }
   2038 
   2039 template<typename T>
   2040 static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
   2041 {
   2042     // Clamp, both sides are optional, return true if modified
   2043     if (v_min && *v < *v_min) { *v = *v_min; return true; }
   2044     if (v_max && *v > *v_max) { *v = *v_max; return true; }
   2045     return false;
   2046 }
   2047 
   2048 bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
   2049 {
   2050     switch (data_type)
   2051     {
   2052     case ImGuiDataType_S8:     return DataTypeClampT<ImS8  >((ImS8*  )p_data, (const ImS8*  )p_min, (const ImS8*  )p_max);
   2053     case ImGuiDataType_U8:     return DataTypeClampT<ImU8  >((ImU8*  )p_data, (const ImU8*  )p_min, (const ImU8*  )p_max);
   2054     case ImGuiDataType_S16:    return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);
   2055     case ImGuiDataType_U16:    return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);
   2056     case ImGuiDataType_S32:    return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);
   2057     case ImGuiDataType_U32:    return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);
   2058     case ImGuiDataType_S64:    return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);
   2059     case ImGuiDataType_U64:    return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);
   2060     case ImGuiDataType_Float:  return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);
   2061     case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);
   2062     case ImGuiDataType_COUNT:  break;
   2063     }
   2064     IM_ASSERT(0);
   2065     return false;
   2066 }
   2067 
   2068 static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
   2069 {
   2070     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 };
   2071     if (decimal_precision < 0)
   2072         return FLT_MIN;
   2073     return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
   2074 }
   2075 
   2076 template<typename TYPE>
   2077 static const char* ImAtoi(const char* src, TYPE* output)
   2078 {
   2079     int negative = 0;
   2080     if (*src == '-') { negative = 1; src++; }
   2081     if (*src == '+') { src++; }
   2082     TYPE v = 0;
   2083     while (*src >= '0' && *src <= '9')
   2084         v = (v * 10) + (*src++ - '0');
   2085     *output = negative ? -v : v;
   2086     return src;
   2087 }
   2088 
   2089 // Sanitize format
   2090 // - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
   2091 // - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
   2092 static void SanitizeFormatString(const char* fmt, char* fmt_out, size_t fmt_out_size)
   2093 {
   2094     IM_UNUSED(fmt_out_size);
   2095     const char* fmt_end = ImParseFormatFindEnd(fmt);
   2096     IM_ASSERT((size_t)(fmt_end - fmt + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
   2097     while (fmt < fmt_end)
   2098     {
   2099         char c = *(fmt++);
   2100         if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
   2101             *(fmt_out++) = c;
   2102     }
   2103     *fmt_out = 0; // Zero-terminate
   2104 }
   2105 
   2106 template<typename TYPE, typename SIGNEDTYPE>
   2107 TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
   2108 {
   2109     const char* fmt_start = ImParseFormatFindStart(format);
   2110     if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
   2111         return v;
   2112 
   2113     // Sanitize format
   2114     char fmt_sanitized[32];
   2115     SanitizeFormatString(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
   2116     fmt_start = fmt_sanitized;
   2117 
   2118     // Format value with our rounding, and read back
   2119     char v_str[64];
   2120     ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
   2121     const char* p = v_str;
   2122     while (*p == ' ')
   2123         p++;
   2124     if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
   2125         v = (TYPE)ImAtof(p);
   2126     else
   2127         ImAtoi(p, (SIGNEDTYPE*)&v);
   2128     return v;
   2129 }
   2130 
   2131 //-------------------------------------------------------------------------
   2132 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
   2133 //-------------------------------------------------------------------------
   2134 // - DragBehaviorT<>() [Internal]
   2135 // - DragBehavior() [Internal]
   2136 // - DragScalar()
   2137 // - DragScalarN()
   2138 // - DragFloat()
   2139 // - DragFloat2()
   2140 // - DragFloat3()
   2141 // - DragFloat4()
   2142 // - DragFloatRange2()
   2143 // - DragInt()
   2144 // - DragInt2()
   2145 // - DragInt3()
   2146 // - DragInt4()
   2147 // - DragIntRange2()
   2148 //-------------------------------------------------------------------------
   2149 
   2150 // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
   2151 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
   2152 bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
   2153 {
   2154     ImGuiContext& g = *GImGui;
   2155     const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
   2156     const bool is_clamped = (v_min < v_max);
   2157     const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
   2158     const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
   2159 
   2160     // Default tweak speed
   2161     if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX))
   2162         v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
   2163 
   2164     // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
   2165     float adjust_delta = 0.0f;
   2166     if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
   2167     {
   2168         adjust_delta = g.IO.MouseDelta[axis];
   2169         if (g.IO.KeyAlt)
   2170             adjust_delta *= 1.0f / 100.0f;
   2171         if (g.IO.KeyShift)
   2172             adjust_delta *= 10.0f;
   2173     }
   2174     else if (g.ActiveIdSource == ImGuiInputSource_Nav)
   2175     {
   2176         const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
   2177         adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
   2178         v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
   2179     }
   2180     adjust_delta *= v_speed;
   2181 
   2182     // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
   2183     if (axis == ImGuiAxis_Y)
   2184         adjust_delta = -adjust_delta;
   2185 
   2186     // For logarithmic use our range is effectively 0..1 so scale the delta into that range
   2187     if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
   2188         adjust_delta /= (float)(v_max - v_min);
   2189 
   2190     // Clear current value on activation
   2191     // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300.
   2192     bool is_just_activated = g.ActiveIdIsJustActivated;
   2193     bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
   2194     if (is_just_activated || is_already_past_limits_and_pushing_outward)
   2195     {
   2196         g.DragCurrentAccum = 0.0f;
   2197         g.DragCurrentAccumDirty = false;
   2198     }
   2199     else if (adjust_delta != 0.0f)
   2200     {
   2201         g.DragCurrentAccum += adjust_delta;
   2202         g.DragCurrentAccumDirty = true;
   2203     }
   2204 
   2205     if (!g.DragCurrentAccumDirty)
   2206         return false;
   2207 
   2208     TYPE v_cur = *v;
   2209     FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
   2210 
   2211     float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
   2212     const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
   2213     if (is_logarithmic)
   2214     {
   2215         // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.
   2216         const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
   2217         logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
   2218 
   2219         // Convert to parametric space, apply delta, convert back
   2220         float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2221         float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
   2222         v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2223         v_old_ref_for_accum_remainder = v_old_parametric;
   2224     }
   2225     else
   2226     {
   2227         v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
   2228     }
   2229 
   2230     // Round to user desired precision based on format string
   2231     if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
   2232         v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
   2233 
   2234     // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
   2235     g.DragCurrentAccumDirty = false;
   2236     if (is_logarithmic)
   2237     {
   2238         // Convert to parametric space, apply delta, convert back
   2239         float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2240         g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
   2241     }
   2242     else
   2243     {
   2244         g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
   2245     }
   2246 
   2247     // Lose zero sign for float/double
   2248     if (v_cur == (TYPE)-0)
   2249         v_cur = (TYPE)0;
   2250 
   2251     // Clamp values (+ handle overflow/wrap-around for integer types)
   2252     if (*v != v_cur && is_clamped)
   2253     {
   2254         if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
   2255             v_cur = v_min;
   2256         if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
   2257             v_cur = v_max;
   2258     }
   2259 
   2260     // Apply result
   2261     if (*v == v_cur)
   2262         return false;
   2263     *v = v_cur;
   2264     return true;
   2265 }
   2266 
   2267 bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
   2268 {
   2269     // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
   2270     IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
   2271 
   2272     ImGuiContext& g = *GImGui;
   2273     if (g.ActiveId == id)
   2274     {
   2275         if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
   2276             ClearActiveID();
   2277         else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
   2278             ClearActiveID();
   2279     }
   2280     if (g.ActiveId != id)
   2281         return false;
   2282     if ((g.CurrentItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
   2283         return false;
   2284 
   2285     switch (data_type)
   2286     {
   2287     case ImGuiDataType_S8:     { ImS32 v32 = (ImS32)*(ImS8*)p_v;  bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN,  p_max ? *(const ImS8*)p_max  : IM_S8_MAX,  format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }
   2288     case ImGuiDataType_U8:     { ImU32 v32 = (ImU32)*(ImU8*)p_v;  bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU8*) p_min : IM_U8_MIN,  p_max ? *(const ImU8*)p_max  : IM_U8_MAX,  format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; }
   2289     case ImGuiDataType_S16:    { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS16*)p_min : IM_S16_MIN, p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
   2290     case ImGuiDataType_U16:    { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU16*)p_min : IM_U16_MIN, p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
   2291     case ImGuiDataType_S32:    return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)p_v,  v_speed, p_min ? *(const ImS32* )p_min : IM_S32_MIN, p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags);
   2292     case ImGuiDataType_U32:    return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)p_v,  v_speed, p_min ? *(const ImU32* )p_min : IM_U32_MIN, p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags);
   2293     case ImGuiDataType_S64:    return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)p_v,  v_speed, p_min ? *(const ImS64* )p_min : IM_S64_MIN, p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags);
   2294     case ImGuiDataType_U64:    return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)p_v,  v_speed, p_min ? *(const ImU64* )p_min : IM_U64_MIN, p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags);
   2295     case ImGuiDataType_Float:  return DragBehaviorT<float, float, float >(data_type, (float*)p_v,  v_speed, p_min ? *(const float* )p_min : -FLT_MAX,   p_max ? *(const float* )p_max : FLT_MAX,    format, flags);
   2296     case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)p_v, v_speed, p_min ? *(const double*)p_min : -DBL_MAX,   p_max ? *(const double*)p_max : DBL_MAX,    format, flags);
   2297     case ImGuiDataType_COUNT:  break;
   2298     }
   2299     IM_ASSERT(0);
   2300     return false;
   2301 }
   2302 
   2303 // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional.
   2304 // Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
   2305 bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
   2306 {
   2307     ImGuiWindow* window = GetCurrentWindow();
   2308     if (window->SkipItems)
   2309         return false;
   2310 
   2311     ImGuiContext& g = *GImGui;
   2312     const ImGuiStyle& style = g.Style;
   2313     const ImGuiID id = window->GetID(label);
   2314     const float w = CalcItemWidth();
   2315 
   2316     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   2317     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
   2318     const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
   2319 
   2320     const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
   2321     ItemSize(total_bb, style.FramePadding.y);
   2322     if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0))
   2323         return false;
   2324 
   2325     // Default format string when passing NULL
   2326     if (format == NULL)
   2327         format = DataTypeGetInfo(data_type)->PrintFmt;
   2328     else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
   2329         format = PatchFormatStringFloatToInt(format);
   2330 
   2331     // Tabbing or CTRL-clicking on Drag turns it into an InputText
   2332     const bool hovered = ItemHoverable(frame_bb, id);
   2333     bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
   2334     if (!temp_input_is_active)
   2335     {
   2336         const bool focus_requested = temp_input_allowed && (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Focused) != 0;
   2337         const bool clicked = (hovered && g.IO.MouseClicked[0]);
   2338         const bool double_clicked = (hovered && g.IO.MouseDoubleClicked[0]);
   2339         if (focus_requested || clicked || double_clicked || g.NavActivateId == id || g.NavInputId == id)
   2340         {
   2341             SetActiveID(id, window);
   2342             SetFocusID(id, window);
   2343             FocusWindow(window);
   2344             g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
   2345             if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavInputId == id))
   2346                 temp_input_is_active = true;
   2347         }
   2348         // Experimental: simple click (without moving) turns Drag into an InputText
   2349         // FIXME: Currently polling ImGuiConfigFlags_IsTouchScreen, may either poll an hypothetical ImGuiBackendFlags_HasKeyboard and/or an explicit drag settings.
   2350         if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
   2351             if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
   2352             {
   2353                 g.NavInputId = id;
   2354                 temp_input_is_active = true;
   2355             }
   2356     }
   2357 
   2358     if (temp_input_is_active)
   2359     {
   2360         // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
   2361         const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, p_min, p_max) < 0);
   2362         return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
   2363     }
   2364 
   2365     // Draw frame
   2366     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
   2367     RenderNavHighlight(frame_bb, id);
   2368     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
   2369 
   2370     // Drag behavior
   2371     const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
   2372     if (value_changed)
   2373         MarkItemEdited(id);
   2374 
   2375     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
   2376     char value_buf[64];
   2377     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
   2378     if (g.LogEnabled)
   2379         LogSetNextTextDecoration("{", "}");
   2380     RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
   2381 
   2382     if (label_size.x > 0.0f)
   2383         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
   2384 
   2385     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
   2386     return value_changed;
   2387 }
   2388 
   2389 bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
   2390 {
   2391     ImGuiWindow* window = GetCurrentWindow();
   2392     if (window->SkipItems)
   2393         return false;
   2394 
   2395     ImGuiContext& g = *GImGui;
   2396     bool value_changed = false;
   2397     BeginGroup();
   2398     PushID(label);
   2399     PushMultiItemsWidths(components, CalcItemWidth());
   2400     size_t type_size = GDataTypeInfo[data_type].Size;
   2401     for (int i = 0; i < components; i++)
   2402     {
   2403         PushID(i);
   2404         if (i > 0)
   2405             SameLine(0, g.Style.ItemInnerSpacing.x);
   2406         value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
   2407         PopID();
   2408         PopItemWidth();
   2409         p_data = (void*)((char*)p_data + type_size);
   2410     }
   2411     PopID();
   2412 
   2413     const char* label_end = FindRenderedTextEnd(label);
   2414     if (label != label_end)
   2415     {
   2416         SameLine(0, g.Style.ItemInnerSpacing.x);
   2417         TextEx(label, label_end);
   2418     }
   2419 
   2420     EndGroup();
   2421     return value_changed;
   2422 }
   2423 
   2424 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   2425 {
   2426     return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
   2427 }
   2428 
   2429 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   2430 {
   2431     return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);
   2432 }
   2433 
   2434 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   2435 {
   2436     return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);
   2437 }
   2438 
   2439 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   2440 {
   2441     return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags);
   2442 }
   2443 
   2444 // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
   2445 bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, ImGuiSliderFlags flags)
   2446 {
   2447     ImGuiWindow* window = GetCurrentWindow();
   2448     if (window->SkipItems)
   2449         return false;
   2450 
   2451     ImGuiContext& g = *GImGui;
   2452     PushID(label);
   2453     BeginGroup();
   2454     PushMultiItemsWidths(2, CalcItemWidth());
   2455 
   2456     float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
   2457     float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
   2458     ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
   2459     bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags);
   2460     PopItemWidth();
   2461     SameLine(0, g.Style.ItemInnerSpacing.x);
   2462 
   2463     float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
   2464     float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
   2465     ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
   2466     value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags);
   2467     PopItemWidth();
   2468     SameLine(0, g.Style.ItemInnerSpacing.x);
   2469 
   2470     TextEx(label, FindRenderedTextEnd(label));
   2471     EndGroup();
   2472     PopID();
   2473     return value_changed;
   2474 }
   2475 
   2476 // NB: v_speed is float to allow adjusting the drag speed with more precision
   2477 bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   2478 {
   2479     return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);
   2480 }
   2481 
   2482 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   2483 {
   2484     return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);
   2485 }
   2486 
   2487 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   2488 {
   2489     return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);
   2490 }
   2491 
   2492 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   2493 {
   2494     return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);
   2495 }
   2496 
   2497 // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
   2498 bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max, ImGuiSliderFlags flags)
   2499 {
   2500     ImGuiWindow* window = GetCurrentWindow();
   2501     if (window->SkipItems)
   2502         return false;
   2503 
   2504     ImGuiContext& g = *GImGui;
   2505     PushID(label);
   2506     BeginGroup();
   2507     PushMultiItemsWidths(2, CalcItemWidth());
   2508 
   2509     int min_min = (v_min >= v_max) ? INT_MIN : v_min;
   2510     int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
   2511     ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
   2512     bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags);
   2513     PopItemWidth();
   2514     SameLine(0, g.Style.ItemInnerSpacing.x);
   2515 
   2516     int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
   2517     int max_max = (v_min >= v_max) ? INT_MAX : v_max;
   2518     ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
   2519     value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags);
   2520     PopItemWidth();
   2521     SameLine(0, g.Style.ItemInnerSpacing.x);
   2522 
   2523     TextEx(label, FindRenderedTextEnd(label));
   2524     EndGroup();
   2525     PopID();
   2526 
   2527     return value_changed;
   2528 }
   2529 
   2530 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   2531 
   2532 // Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details.
   2533 bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
   2534 {
   2535     ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None;
   2536     if (power != 1.0f)
   2537     {
   2538         IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
   2539         IM_ASSERT(p_min != NULL && p_max != NULL);  // When using a power curve the drag needs to have known bounds
   2540         drag_flags |= ImGuiSliderFlags_Logarithmic;   // Fallback for non-asserting paths
   2541     }
   2542     return DragScalar(label, data_type, p_data, v_speed, p_min, p_max, format, drag_flags);
   2543 }
   2544 
   2545 bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
   2546 {
   2547     ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None;
   2548     if (power != 1.0f)
   2549     {
   2550         IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
   2551         IM_ASSERT(p_min != NULL && p_max != NULL);  // When using a power curve the drag needs to have known bounds
   2552         drag_flags |= ImGuiSliderFlags_Logarithmic;   // Fallback for non-asserting paths
   2553     }
   2554     return DragScalarN(label, data_type, p_data, components, v_speed, p_min, p_max, format, drag_flags);
   2555 }
   2556 
   2557 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   2558 
   2559 //-------------------------------------------------------------------------
   2560 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
   2561 //-------------------------------------------------------------------------
   2562 // - ScaleRatioFromValueT<> [Internal]
   2563 // - ScaleValueFromRatioT<> [Internal]
   2564 // - SliderBehaviorT<>() [Internal]
   2565 // - SliderBehavior() [Internal]
   2566 // - SliderScalar()
   2567 // - SliderScalarN()
   2568 // - SliderFloat()
   2569 // - SliderFloat2()
   2570 // - SliderFloat3()
   2571 // - SliderFloat4()
   2572 // - SliderAngle()
   2573 // - SliderInt()
   2574 // - SliderInt2()
   2575 // - SliderInt3()
   2576 // - SliderInt4()
   2577 // - VSliderScalar()
   2578 // - VSliderFloat()
   2579 // - VSliderInt()
   2580 //-------------------------------------------------------------------------
   2581 
   2582 // Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
   2583 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
   2584 float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
   2585 {
   2586     if (v_min == v_max)
   2587         return 0.0f;
   2588     IM_UNUSED(data_type);
   2589 
   2590     const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
   2591     if (is_logarithmic)
   2592     {
   2593         bool flipped = v_max < v_min;
   2594 
   2595         if (flipped) // Handle the case where the range is backwards
   2596             ImSwap(v_min, v_max);
   2597 
   2598         // Fudge min/max to avoid getting close to log(0)
   2599         FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
   2600         FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
   2601 
   2602         // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
   2603         if ((v_min == 0.0f) && (v_max < 0.0f))
   2604             v_min_fudged = -logarithmic_zero_epsilon;
   2605         else if ((v_max == 0.0f) && (v_min < 0.0f))
   2606             v_max_fudged = -logarithmic_zero_epsilon;
   2607 
   2608         float result;
   2609 
   2610         if (v_clamped <= v_min_fudged)
   2611             result = 0.0f; // Workaround for values that are in-range but below our fudge
   2612         else if (v_clamped >= v_max_fudged)
   2613             result = 1.0f; // Workaround for values that are in-range but above our fudge
   2614         else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
   2615         {
   2616             float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space.  There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine)
   2617             float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
   2618             float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
   2619             if (v == 0.0f)
   2620                 result = zero_point_center; // Special case for exactly zero
   2621             else if (v < 0.0f)
   2622                 result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
   2623             else
   2624                 result = zero_point_snap_R + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) * (1.0f - zero_point_snap_R));
   2625         }
   2626         else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
   2627             result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
   2628         else
   2629             result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
   2630 
   2631         return flipped ? (1.0f - result) : result;
   2632     }
   2633 
   2634     // Linear slider
   2635     return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
   2636 }
   2637 
   2638 // Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
   2639 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
   2640 TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
   2641 {
   2642     if (v_min == v_max)
   2643         return v_min;
   2644     const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
   2645 
   2646     TYPE result;
   2647     if (is_logarithmic)
   2648     {
   2649         // We special-case the extents because otherwise our fudging can lead to "mathematically correct" but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value
   2650         if (t <= 0.0f)
   2651             result = v_min;
   2652         else if (t >= 1.0f)
   2653             result = v_max;
   2654         else
   2655         {
   2656             bool flipped = v_max < v_min; // Check if range is "backwards"
   2657 
   2658             // Fudge min/max to avoid getting silly results close to zero
   2659             FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
   2660             FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
   2661 
   2662             if (flipped)
   2663                 ImSwap(v_min_fudged, v_max_fudged);
   2664 
   2665             // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
   2666             if ((v_max == 0.0f) && (v_min < 0.0f))
   2667                 v_max_fudged = -logarithmic_zero_epsilon;
   2668 
   2669             float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
   2670 
   2671             if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
   2672             {
   2673                 float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
   2674                 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
   2675                 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
   2676                 if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
   2677                     result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
   2678                 else if (t_with_flip < zero_point_center)
   2679                     result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
   2680                 else
   2681                     result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R))));
   2682             }
   2683             else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
   2684                 result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
   2685             else
   2686                 result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
   2687         }
   2688     }
   2689     else
   2690     {
   2691         // Linear slider
   2692         if (is_floating_point)
   2693         {
   2694             result = ImLerp(v_min, v_max, t);
   2695         }
   2696         else
   2697         {
   2698             // - For integer values we want the clicking position to match the grab box so we round above
   2699             //   This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
   2700             // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64
   2701             //   range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
   2702             if (t < 1.0)
   2703             {
   2704                 FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
   2705                 result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
   2706             }
   2707             else
   2708             {
   2709                 result = v_max;
   2710             }
   2711         }
   2712     }
   2713 
   2714     return result;
   2715 }
   2716 
   2717 // FIXME: Move more of the code into SliderBehavior()
   2718 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
   2719 bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
   2720 {
   2721     ImGuiContext& g = *GImGui;
   2722     const ImGuiStyle& style = g.Style;
   2723 
   2724     const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
   2725     const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
   2726     const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
   2727 
   2728     const float grab_padding = 2.0f;
   2729     const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
   2730     float grab_sz = style.GrabMinSize;
   2731     SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
   2732     if (!is_floating_point && v_range >= 0)                                             // v_range < 0 may happen on integer overflows
   2733         grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize);  // For integer sliders: if possible have the grab size represent 1 unit
   2734     grab_sz = ImMin(grab_sz, slider_sz);
   2735     const float slider_usable_sz = slider_sz - grab_sz;
   2736     const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
   2737     const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
   2738 
   2739     float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
   2740     float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true
   2741     if (is_logarithmic)
   2742     {
   2743         // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.
   2744         const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
   2745         logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
   2746         zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);
   2747     }
   2748 
   2749     // Process interacting with the slider
   2750     bool value_changed = false;
   2751     if (g.ActiveId == id)
   2752     {
   2753         bool set_new_value = false;
   2754         float clicked_t = 0.0f;
   2755         if (g.ActiveIdSource == ImGuiInputSource_Mouse)
   2756         {
   2757             if (!g.IO.MouseDown[0])
   2758             {
   2759                 ClearActiveID();
   2760             }
   2761             else
   2762             {
   2763                 const float mouse_abs_pos = g.IO.MousePos[axis];
   2764                 clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
   2765                 if (axis == ImGuiAxis_Y)
   2766                     clicked_t = 1.0f - clicked_t;
   2767                 set_new_value = true;
   2768             }
   2769         }
   2770         else if (g.ActiveIdSource == ImGuiInputSource_Nav)
   2771         {
   2772             if (g.ActiveIdIsJustActivated)
   2773             {
   2774                 g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation
   2775                 g.SliderCurrentAccumDirty = false;
   2776             }
   2777 
   2778             const ImVec2 input_delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
   2779             float input_delta = (axis == ImGuiAxis_X) ? input_delta2.x : -input_delta2.y;
   2780             if (input_delta != 0.0f)
   2781             {
   2782                 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
   2783                 if (decimal_precision > 0)
   2784                 {
   2785                     input_delta /= 100.0f;    // Gamepad/keyboard tweak speeds in % of slider bounds
   2786                     if (IsNavInputDown(ImGuiNavInput_TweakSlow))
   2787                         input_delta /= 10.0f;
   2788                 }
   2789                 else
   2790                 {
   2791                     if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
   2792                         input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
   2793                     else
   2794                         input_delta /= 100.0f;
   2795                 }
   2796                 if (IsNavInputDown(ImGuiNavInput_TweakFast))
   2797                     input_delta *= 10.0f;
   2798 
   2799                 g.SliderCurrentAccum += input_delta;
   2800                 g.SliderCurrentAccumDirty = true;
   2801             }
   2802 
   2803             float delta = g.SliderCurrentAccum;
   2804             if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
   2805             {
   2806                 ClearActiveID();
   2807             }
   2808             else if (g.SliderCurrentAccumDirty)
   2809             {
   2810                 clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2811 
   2812                 if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits
   2813                 {
   2814                     set_new_value = false;
   2815                     g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate
   2816                 }
   2817                 else
   2818                 {
   2819                     set_new_value = true;
   2820                     float old_clicked_t = clicked_t;
   2821                     clicked_t = ImSaturate(clicked_t + delta);
   2822 
   2823                     // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
   2824                     TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2825                     if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
   2826                         v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
   2827                     float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2828 
   2829                     if (delta > 0)
   2830                         g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);
   2831                     else
   2832                         g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);
   2833                 }
   2834 
   2835                 g.SliderCurrentAccumDirty = false;
   2836             }
   2837         }
   2838 
   2839         if (set_new_value)
   2840         {
   2841             TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2842 
   2843             // Round to user desired precision based on format string
   2844             if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
   2845                 v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
   2846 
   2847             // Apply result
   2848             if (*v != v_new)
   2849             {
   2850                 *v = v_new;
   2851                 value_changed = true;
   2852             }
   2853         }
   2854     }
   2855 
   2856     if (slider_sz < 1.0f)
   2857     {
   2858         *out_grab_bb = ImRect(bb.Min, bb.Min);
   2859     }
   2860     else
   2861     {
   2862         // Output grab position so it can be displayed by the caller
   2863         float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2864         if (axis == ImGuiAxis_Y)
   2865             grab_t = 1.0f - grab_t;
   2866         const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
   2867         if (axis == ImGuiAxis_X)
   2868             *out_grab_bb = ImRect(grab_pos - grab_sz * 0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz * 0.5f, bb.Max.y - grab_padding);
   2869         else
   2870             *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5f);
   2871     }
   2872 
   2873     return value_changed;
   2874 }
   2875 
   2876 // For 32-bit and larger types, slider bounds are limited to half the natural type range.
   2877 // So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.
   2878 // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
   2879 bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
   2880 {
   2881     // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
   2882     IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag!  Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
   2883 
   2884     ImGuiContext& g = *GImGui;
   2885     if ((g.CurrentItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
   2886         return false;
   2887 
   2888     switch (data_type)
   2889     {
   2890     case ImGuiDataType_S8:  { ImS32 v32 = (ImS32)*(ImS8*)p_v;  bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min,  *(const ImS8*)p_max,  format, flags, out_grab_bb); if (r) *(ImS8*)p_v  = (ImS8)v32;  return r; }
   2891     case ImGuiDataType_U8:  { ImU32 v32 = (ImU32)*(ImU8*)p_v;  bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU8*)p_min,  *(const ImU8*)p_max,  format, flags, out_grab_bb); if (r) *(ImU8*)p_v  = (ImU8)v32;  return r; }
   2892     case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS16*)p_min, *(const ImS16*)p_max, format, flags, out_grab_bb); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
   2893     case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU16*)p_min, *(const ImU16*)p_max, format, flags, out_grab_bb); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
   2894     case ImGuiDataType_S32:
   2895         IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2);
   2896         return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)p_v,  *(const ImS32*)p_min,  *(const ImS32*)p_max,  format, flags, out_grab_bb);
   2897     case ImGuiDataType_U32:
   2898         IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);
   2899         return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)p_v,  *(const ImU32*)p_min,  *(const ImU32*)p_max,  format, flags, out_grab_bb);
   2900     case ImGuiDataType_S64:
   2901         IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2);
   2902         return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)p_v,  *(const ImS64*)p_min,  *(const ImS64*)p_max,  format, flags, out_grab_bb);
   2903     case ImGuiDataType_U64:
   2904         IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);
   2905         return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)p_v,  *(const ImU64*)p_min,  *(const ImU64*)p_max,  format, flags, out_grab_bb);
   2906     case ImGuiDataType_Float:
   2907         IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f);
   2908         return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)p_v,  *(const float*)p_min,  *(const float*)p_max,  format, flags, out_grab_bb);
   2909     case ImGuiDataType_Double:
   2910         IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f);
   2911         return SliderBehaviorT<double, double, double>(bb, id, data_type, (double*)p_v, *(const double*)p_min, *(const double*)p_max, format, flags, out_grab_bb);
   2912     case ImGuiDataType_COUNT: break;
   2913     }
   2914     IM_ASSERT(0);
   2915     return false;
   2916 }
   2917 
   2918 // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.
   2919 // Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
   2920 bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
   2921 {
   2922     ImGuiWindow* window = GetCurrentWindow();
   2923     if (window->SkipItems)
   2924         return false;
   2925 
   2926     ImGuiContext& g = *GImGui;
   2927     const ImGuiStyle& style = g.Style;
   2928     const ImGuiID id = window->GetID(label);
   2929     const float w = CalcItemWidth();
   2930 
   2931     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   2932     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
   2933     const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
   2934 
   2935     const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
   2936     ItemSize(total_bb, style.FramePadding.y);
   2937     if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0))
   2938         return false;
   2939 
   2940     // Default format string when passing NULL
   2941     if (format == NULL)
   2942         format = DataTypeGetInfo(data_type)->PrintFmt;
   2943     else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
   2944         format = PatchFormatStringFloatToInt(format);
   2945 
   2946     // Tabbing or CTRL-clicking on Slider turns it into an input box
   2947     const bool hovered = ItemHoverable(frame_bb, id);
   2948     bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
   2949     if (!temp_input_is_active)
   2950     {
   2951         const bool focus_requested = temp_input_allowed && (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Focused) != 0;
   2952         const bool clicked = (hovered && g.IO.MouseClicked[0]);
   2953         if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id)
   2954         {
   2955             SetActiveID(id, window);
   2956             SetFocusID(id, window);
   2957             FocusWindow(window);
   2958             g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
   2959             if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id))
   2960                 temp_input_is_active = true;
   2961         }
   2962     }
   2963 
   2964     if (temp_input_is_active)
   2965     {
   2966         // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
   2967         const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0;
   2968         return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
   2969     }
   2970 
   2971     // Draw frame
   2972     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
   2973     RenderNavHighlight(frame_bb, id);
   2974     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
   2975 
   2976     // Slider behavior
   2977     ImRect grab_bb;
   2978     const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb);
   2979     if (value_changed)
   2980         MarkItemEdited(id);
   2981 
   2982     // Render grab
   2983     if (grab_bb.Max.x > grab_bb.Min.x)
   2984         window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
   2985 
   2986     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
   2987     char value_buf[64];
   2988     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
   2989     if (g.LogEnabled)
   2990         LogSetNextTextDecoration("{", "}");
   2991     RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
   2992 
   2993     if (label_size.x > 0.0f)
   2994         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
   2995 
   2996     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
   2997     return value_changed;
   2998 }
   2999 
   3000 // Add multiple sliders on 1 line for compact edition of multiple components
   3001 bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, ImGuiSliderFlags flags)
   3002 {
   3003     ImGuiWindow* window = GetCurrentWindow();
   3004     if (window->SkipItems)
   3005         return false;
   3006 
   3007     ImGuiContext& g = *GImGui;
   3008     bool value_changed = false;
   3009     BeginGroup();
   3010     PushID(label);
   3011     PushMultiItemsWidths(components, CalcItemWidth());
   3012     size_t type_size = GDataTypeInfo[data_type].Size;
   3013     for (int i = 0; i < components; i++)
   3014     {
   3015         PushID(i);
   3016         if (i > 0)
   3017             SameLine(0, g.Style.ItemInnerSpacing.x);
   3018         value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
   3019         PopID();
   3020         PopItemWidth();
   3021         v = (void*)((char*)v + type_size);
   3022     }
   3023     PopID();
   3024 
   3025     const char* label_end = FindRenderedTextEnd(label);
   3026     if (label != label_end)
   3027     {
   3028         SameLine(0, g.Style.ItemInnerSpacing.x);
   3029         TextEx(label, label_end);
   3030     }
   3031 
   3032     EndGroup();
   3033     return value_changed;
   3034 }
   3035 
   3036 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3037 {
   3038     return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
   3039 }
   3040 
   3041 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3042 {
   3043     return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);
   3044 }
   3045 
   3046 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3047 {
   3048     return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);
   3049 }
   3050 
   3051 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3052 {
   3053     return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);
   3054 }
   3055 
   3056 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags)
   3057 {
   3058     if (format == NULL)
   3059         format = "%.0f deg";
   3060     float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
   3061     bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
   3062     *v_rad = v_deg * (2 * IM_PI) / 360.0f;
   3063     return value_changed;
   3064 }
   3065 
   3066 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3067 {
   3068     return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
   3069 }
   3070 
   3071 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3072 {
   3073     return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);
   3074 }
   3075 
   3076 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3077 {
   3078     return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);
   3079 }
   3080 
   3081 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3082 {
   3083     return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);
   3084 }
   3085 
   3086 bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
   3087 {
   3088     ImGuiWindow* window = GetCurrentWindow();
   3089     if (window->SkipItems)
   3090         return false;
   3091 
   3092     ImGuiContext& g = *GImGui;
   3093     const ImGuiStyle& style = g.Style;
   3094     const ImGuiID id = window->GetID(label);
   3095 
   3096     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   3097     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
   3098     const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
   3099 
   3100     ItemSize(bb, style.FramePadding.y);
   3101     if (!ItemAdd(frame_bb, id))
   3102         return false;
   3103 
   3104     // Default format string when passing NULL
   3105     if (format == NULL)
   3106         format = DataTypeGetInfo(data_type)->PrintFmt;
   3107     else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
   3108         format = PatchFormatStringFloatToInt(format);
   3109 
   3110     const bool hovered = ItemHoverable(frame_bb, id);
   3111     if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
   3112     {
   3113         SetActiveID(id, window);
   3114         SetFocusID(id, window);
   3115         FocusWindow(window);
   3116         g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
   3117     }
   3118 
   3119     // Draw frame
   3120     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
   3121     RenderNavHighlight(frame_bb, id);
   3122     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
   3123 
   3124     // Slider behavior
   3125     ImRect grab_bb;
   3126     const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb);
   3127     if (value_changed)
   3128         MarkItemEdited(id);
   3129 
   3130     // Render grab
   3131     if (grab_bb.Max.y > grab_bb.Min.y)
   3132         window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
   3133 
   3134     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
   3135     // For the vertical slider we allow centered text to overlap the frame padding
   3136     char value_buf[64];
   3137     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
   3138     RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f));
   3139     if (label_size.x > 0.0f)
   3140         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
   3141 
   3142     return value_changed;
   3143 }
   3144 
   3145 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3146 {
   3147     return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
   3148 }
   3149 
   3150 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3151 {
   3152     return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
   3153 }
   3154 
   3155 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   3156 
   3157 // Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details.
   3158 bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power)
   3159 {
   3160     ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None;
   3161     if (power != 1.0f)
   3162     {
   3163         IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
   3164         slider_flags |= ImGuiSliderFlags_Logarithmic;   // Fallback for non-asserting paths
   3165     }
   3166     return SliderScalar(label, data_type, p_data, p_min, p_max, format, slider_flags);
   3167 }
   3168 
   3169 bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power)
   3170 {
   3171     ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None;
   3172     if (power != 1.0f)
   3173     {
   3174         IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
   3175         slider_flags |= ImGuiSliderFlags_Logarithmic;   // Fallback for non-asserting paths
   3176     }
   3177     return SliderScalarN(label, data_type, v, components, v_min, v_max, format, slider_flags);
   3178 }
   3179 
   3180 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   3181 
   3182 //-------------------------------------------------------------------------
   3183 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
   3184 //-------------------------------------------------------------------------
   3185 // - ImParseFormatFindStart() [Internal]
   3186 // - ImParseFormatFindEnd() [Internal]
   3187 // - ImParseFormatTrimDecorations() [Internal]
   3188 // - ImParseFormatPrecision() [Internal]
   3189 // - TempInputTextScalar() [Internal]
   3190 // - InputScalar()
   3191 // - InputScalarN()
   3192 // - InputFloat()
   3193 // - InputFloat2()
   3194 // - InputFloat3()
   3195 // - InputFloat4()
   3196 // - InputInt()
   3197 // - InputInt2()
   3198 // - InputInt3()
   3199 // - InputInt4()
   3200 // - InputDouble()
   3201 //-------------------------------------------------------------------------
   3202 
   3203 // We don't use strchr() because our strings are usually very short and often start with '%'
   3204 const char* ImParseFormatFindStart(const char* fmt)
   3205 {
   3206     while (char c = fmt[0])
   3207     {
   3208         if (c == '%' && fmt[1] != '%')
   3209             return fmt;
   3210         else if (c == '%')
   3211             fmt++;
   3212         fmt++;
   3213     }
   3214     return fmt;
   3215 }
   3216 
   3217 const char* ImParseFormatFindEnd(const char* fmt)
   3218 {
   3219     // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
   3220     if (fmt[0] != '%')
   3221         return fmt;
   3222     const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
   3223     const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
   3224     for (char c; (c = *fmt) != 0; fmt++)
   3225     {
   3226         if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
   3227             return fmt + 1;
   3228         if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
   3229             return fmt + 1;
   3230     }
   3231     return fmt;
   3232 }
   3233 
   3234 // Extract the format out of a format string with leading or trailing decorations
   3235 //  fmt = "blah blah"  -> return fmt
   3236 //  fmt = "%.3f"       -> return fmt
   3237 //  fmt = "hello %.3f" -> return fmt + 6
   3238 //  fmt = "%.3f hello" -> return buf written with "%.3f"
   3239 const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
   3240 {
   3241     const char* fmt_start = ImParseFormatFindStart(fmt);
   3242     if (fmt_start[0] != '%')
   3243         return fmt;
   3244     const char* fmt_end = ImParseFormatFindEnd(fmt_start);
   3245     if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
   3246         return fmt_start;
   3247     ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
   3248     return buf;
   3249 }
   3250 
   3251 // Parse display precision back from the display format string
   3252 // FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
   3253 int ImParseFormatPrecision(const char* fmt, int default_precision)
   3254 {
   3255     fmt = ImParseFormatFindStart(fmt);
   3256     if (fmt[0] != '%')
   3257         return default_precision;
   3258     fmt++;
   3259     while (*fmt >= '0' && *fmt <= '9')
   3260         fmt++;
   3261     int precision = INT_MAX;
   3262     if (*fmt == '.')
   3263     {
   3264         fmt = ImAtoi<int>(fmt + 1, &precision);
   3265         if (precision < 0 || precision > 99)
   3266             precision = default_precision;
   3267     }
   3268     if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
   3269         precision = -1;
   3270     if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
   3271         precision = -1;
   3272     return (precision == INT_MAX) ? default_precision : precision;
   3273 }
   3274 
   3275 // Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
   3276 // FIXME: Facilitate using this in variety of other situations.
   3277 bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags)
   3278 {
   3279     // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
   3280     // We clear ActiveID on the first frame to allow the InputText() taking it back.
   3281     ImGuiContext& g = *GImGui;
   3282     const bool init = (g.TempInputId != id);
   3283     if (init)
   3284         ClearActiveID();
   3285 
   3286     g.CurrentWindow->DC.CursorPos = bb.Min;
   3287     bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);
   3288     if (init)
   3289     {
   3290         // First frame we started displaying the InputText widget, we expect it to take the active id.
   3291         IM_ASSERT(g.ActiveId == id);
   3292         g.TempInputId = g.ActiveId;
   3293     }
   3294     return value_changed;
   3295 }
   3296 
   3297 // Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
   3298 // This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
   3299 // However this may not be ideal for all uses, as some user code may break on out of bound values.
   3300 bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max)
   3301 {
   3302     ImGuiContext& g = *GImGui;
   3303 
   3304     char fmt_buf[32];
   3305     char data_buf[32];
   3306     format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
   3307     DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
   3308     ImStrTrimBlanks(data_buf);
   3309 
   3310     ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited;
   3311     flags |= ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
   3312     bool value_changed = false;
   3313     if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
   3314     {
   3315         // Backup old value
   3316         size_t data_type_size = DataTypeGetInfo(data_type)->Size;
   3317         ImGuiDataTypeTempStorage data_backup;
   3318         memcpy(&data_backup, p_data, data_type_size);
   3319 
   3320         // Apply new value (or operations) then clamp
   3321         DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, p_data, NULL);
   3322         if (p_clamp_min || p_clamp_max)
   3323         {
   3324             if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
   3325                 ImSwap(p_clamp_min, p_clamp_max);
   3326             DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
   3327         }
   3328 
   3329         // Only mark as edited if new value is different
   3330         value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;
   3331         if (value_changed)
   3332             MarkItemEdited(id);
   3333     }
   3334     return value_changed;
   3335 }
   3336 
   3337 // Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional.
   3338 // Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
   3339 bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
   3340 {
   3341     ImGuiWindow* window = GetCurrentWindow();
   3342     if (window->SkipItems)
   3343         return false;
   3344 
   3345     ImGuiContext& g = *GImGui;
   3346     ImGuiStyle& style = g.Style;
   3347 
   3348     if (format == NULL)
   3349         format = DataTypeGetInfo(data_type)->PrintFmt;
   3350 
   3351     char buf[64];
   3352     DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
   3353 
   3354     bool value_changed = false;
   3355     if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
   3356         flags |= ImGuiInputTextFlags_CharsDecimal;
   3357     flags |= ImGuiInputTextFlags_AutoSelectAll;
   3358     flags |= ImGuiInputTextFlags_NoMarkEdited;  // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
   3359 
   3360     if (p_step != NULL)
   3361     {
   3362         const float button_size = GetFrameHeight();
   3363 
   3364         BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
   3365         PushID(label);
   3366         SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
   3367         if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
   3368             value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format);
   3369 
   3370         // Step buttons
   3371         const ImVec2 backup_frame_padding = style.FramePadding;
   3372         style.FramePadding.x = style.FramePadding.y;
   3373         ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
   3374         if (flags & ImGuiInputTextFlags_ReadOnly)
   3375             button_flags |= ImGuiButtonFlags_Disabled;
   3376         SameLine(0, style.ItemInnerSpacing.x);
   3377         if (ButtonEx("-", ImVec2(button_size, button_size), button_flags))
   3378         {
   3379             DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
   3380             value_changed = true;
   3381         }
   3382         SameLine(0, style.ItemInnerSpacing.x);
   3383         if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))
   3384         {
   3385             DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
   3386             value_changed = true;
   3387         }
   3388 
   3389         const char* label_end = FindRenderedTextEnd(label);
   3390         if (label != label_end)
   3391         {
   3392             SameLine(0, style.ItemInnerSpacing.x);
   3393             TextEx(label, label_end);
   3394         }
   3395         style.FramePadding = backup_frame_padding;
   3396 
   3397         PopID();
   3398         EndGroup();
   3399     }
   3400     else
   3401     {
   3402         if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
   3403             value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format);
   3404     }
   3405     if (value_changed)
   3406         MarkItemEdited(window->DC.LastItemId);
   3407 
   3408     return value_changed;
   3409 }
   3410 
   3411 bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
   3412 {
   3413     ImGuiWindow* window = GetCurrentWindow();
   3414     if (window->SkipItems)
   3415         return false;
   3416 
   3417     ImGuiContext& g = *GImGui;
   3418     bool value_changed = false;
   3419     BeginGroup();
   3420     PushID(label);
   3421     PushMultiItemsWidths(components, CalcItemWidth());
   3422     size_t type_size = GDataTypeInfo[data_type].Size;
   3423     for (int i = 0; i < components; i++)
   3424     {
   3425         PushID(i);
   3426         if (i > 0)
   3427             SameLine(0, g.Style.ItemInnerSpacing.x);
   3428         value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
   3429         PopID();
   3430         PopItemWidth();
   3431         p_data = (void*)((char*)p_data + type_size);
   3432     }
   3433     PopID();
   3434 
   3435     const char* label_end = FindRenderedTextEnd(label);
   3436     if (label != label_end)
   3437     {
   3438         SameLine(0.0f, g.Style.ItemInnerSpacing.x);
   3439         TextEx(label, label_end);
   3440     }
   3441 
   3442     EndGroup();
   3443     return value_changed;
   3444 }
   3445 
   3446 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
   3447 {
   3448     flags |= ImGuiInputTextFlags_CharsScientific;
   3449     return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags);
   3450 }
   3451 
   3452 bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
   3453 {
   3454     return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
   3455 }
   3456 
   3457 bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
   3458 {
   3459     return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
   3460 }
   3461 
   3462 bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
   3463 {
   3464     return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
   3465 }
   3466 
   3467 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
   3468 {
   3469     // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.
   3470     const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
   3471     return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags);
   3472 }
   3473 
   3474 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
   3475 {
   3476     return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
   3477 }
   3478 
   3479 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
   3480 {
   3481     return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
   3482 }
   3483 
   3484 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
   3485 {
   3486     return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
   3487 }
   3488 
   3489 bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
   3490 {
   3491     flags |= ImGuiInputTextFlags_CharsScientific;
   3492     return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags);
   3493 }
   3494 
   3495 //-------------------------------------------------------------------------
   3496 // [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
   3497 //-------------------------------------------------------------------------
   3498 // - InputText()
   3499 // - InputTextWithHint()
   3500 // - InputTextMultiline()
   3501 // - InputTextEx() [Internal]
   3502 //-------------------------------------------------------------------------
   3503 
   3504 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
   3505 {
   3506     IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
   3507     return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
   3508 }
   3509 
   3510 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
   3511 {
   3512     return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
   3513 }
   3514 
   3515 bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
   3516 {
   3517     IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
   3518     return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
   3519 }
   3520 
   3521 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
   3522 {
   3523     int line_count = 0;
   3524     const char* s = text_begin;
   3525     while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
   3526         if (c == '\n')
   3527             line_count++;
   3528     s--;
   3529     if (s[0] != '\n' && s[0] != '\r')
   3530         line_count++;
   3531     *out_text_end = s;
   3532     return line_count;
   3533 }
   3534 
   3535 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
   3536 {
   3537     ImGuiContext& g = *GImGui;
   3538     ImFont* font = g.Font;
   3539     const float line_height = g.FontSize;
   3540     const float scale = line_height / font->FontSize;
   3541 
   3542     ImVec2 text_size = ImVec2(0, 0);
   3543     float line_width = 0.0f;
   3544 
   3545     const ImWchar* s = text_begin;
   3546     while (s < text_end)
   3547     {
   3548         unsigned int c = (unsigned int)(*s++);
   3549         if (c == '\n')
   3550         {
   3551             text_size.x = ImMax(text_size.x, line_width);
   3552             text_size.y += line_height;
   3553             line_width = 0.0f;
   3554             if (stop_on_new_line)
   3555                 break;
   3556             continue;
   3557         }
   3558         if (c == '\r')
   3559             continue;
   3560 
   3561         const float char_width = font->GetCharAdvance((ImWchar)c) * scale;
   3562         line_width += char_width;
   3563     }
   3564 
   3565     if (text_size.x < line_width)
   3566         text_size.x = line_width;
   3567 
   3568     if (out_offset)
   3569         *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
   3570 
   3571     if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
   3572         text_size.y += line_height;
   3573 
   3574     if (remaining)
   3575         *remaining = s;
   3576 
   3577     return text_size;
   3578 }
   3579 
   3580 // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
   3581 namespace ImStb
   3582 {
   3583 
   3584 static int     STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj)                             { return obj->CurLenW; }
   3585 static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx)                      { return obj->TextW[idx]; }
   3586 static float   STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx)  { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *GImGui; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); }
   3587 static int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                    { return key >= 0x200000 ? 0 : key; }
   3588 static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
   3589 static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
   3590 {
   3591     const ImWchar* text = obj->TextW.Data;
   3592     const ImWchar* text_remaining = NULL;
   3593     const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
   3594     r->x0 = 0.0f;
   3595     r->x1 = size.x;
   3596     r->baseline_y_delta = size.y;
   3597     r->ymin = 0.0f;
   3598     r->ymax = size.y;
   3599     r->num_chars = (int)(text_remaining - (text + line_start_idx));
   3600 }
   3601 
   3602 // When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators.
   3603 static bool is_separator(unsigned int c)                                        { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
   3604 static int  is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)      { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (is_separator(obj->TextW[idx - 1]) && !is_separator(obj->TextW[idx]) ) : 1; }
   3605 static int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx)   { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
   3606 #ifdef __APPLE__    // FIXME: Move setting to IO structure
   3607 static int  is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)       { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx]) ) : 1; }
   3608 static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
   3609 #else
   3610 static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
   3611 #endif
   3612 #define STB_TEXTEDIT_MOVEWORDLEFT   STB_TEXTEDIT_MOVEWORDLEFT_IMPL    // They need to be #define for stb_textedit.h
   3613 #define STB_TEXTEDIT_MOVEWORDRIGHT  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
   3614 
   3615 static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
   3616 {
   3617     ImWchar* dst = obj->TextW.Data + pos;
   3618 
   3619     // We maintain our buffer length in both UTF-8 and wchar formats
   3620     obj->Edited = true;
   3621     obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
   3622     obj->CurLenW -= n;
   3623 
   3624     // Offset remaining text (FIXME-OPT: Use memmove)
   3625     const ImWchar* src = obj->TextW.Data + pos + n;
   3626     while (ImWchar c = *src++)
   3627         *dst++ = c;
   3628     *dst = '\0';
   3629 }
   3630 
   3631 static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len)
   3632 {
   3633     const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
   3634     const int text_len = obj->CurLenW;
   3635     IM_ASSERT(pos <= text_len);
   3636 
   3637     const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
   3638     if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
   3639         return false;
   3640 
   3641     // Grow internal buffer if needed
   3642     if (new_text_len + text_len + 1 > obj->TextW.Size)
   3643     {
   3644         if (!is_resizable)
   3645             return false;
   3646         IM_ASSERT(text_len < obj->TextW.Size);
   3647         obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
   3648     }
   3649 
   3650     ImWchar* text = obj->TextW.Data;
   3651     if (pos != text_len)
   3652         memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
   3653     memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
   3654 
   3655     obj->Edited = true;
   3656     obj->CurLenW += new_text_len;
   3657     obj->CurLenA += new_text_len_utf8;
   3658     obj->TextW[obj->CurLenW] = '\0';
   3659 
   3660     return true;
   3661 }
   3662 
   3663 // We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
   3664 #define STB_TEXTEDIT_K_LEFT         0x200000 // keyboard input to move cursor left
   3665 #define STB_TEXTEDIT_K_RIGHT        0x200001 // keyboard input to move cursor right
   3666 #define STB_TEXTEDIT_K_UP           0x200002 // keyboard input to move cursor up
   3667 #define STB_TEXTEDIT_K_DOWN         0x200003 // keyboard input to move cursor down
   3668 #define STB_TEXTEDIT_K_LINESTART    0x200004 // keyboard input to move cursor to start of line
   3669 #define STB_TEXTEDIT_K_LINEEND      0x200005 // keyboard input to move cursor to end of line
   3670 #define STB_TEXTEDIT_K_TEXTSTART    0x200006 // keyboard input to move cursor to start of text
   3671 #define STB_TEXTEDIT_K_TEXTEND      0x200007 // keyboard input to move cursor to end of text
   3672 #define STB_TEXTEDIT_K_DELETE       0x200008 // keyboard input to delete selection or character under cursor
   3673 #define STB_TEXTEDIT_K_BACKSPACE    0x200009 // keyboard input to delete selection or character left of cursor
   3674 #define STB_TEXTEDIT_K_UNDO         0x20000A // keyboard input to perform undo
   3675 #define STB_TEXTEDIT_K_REDO         0x20000B // keyboard input to perform redo
   3676 #define STB_TEXTEDIT_K_WORDLEFT     0x20000C // keyboard input to move cursor left one word
   3677 #define STB_TEXTEDIT_K_WORDRIGHT    0x20000D // keyboard input to move cursor right one word
   3678 #define STB_TEXTEDIT_K_PGUP         0x20000E // keyboard input to move cursor up a page
   3679 #define STB_TEXTEDIT_K_PGDOWN       0x20000F // keyboard input to move cursor down a page
   3680 #define STB_TEXTEDIT_K_SHIFT        0x400000
   3681 
   3682 #define STB_TEXTEDIT_IMPLEMENTATION
   3683 #include "imstb_textedit.h"
   3684 
   3685 // stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
   3686 // the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
   3687 static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const STB_TEXTEDIT_CHARTYPE* text, int text_len)
   3688 {
   3689     stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len);
   3690     ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW);
   3691     if (text_len <= 0)
   3692         return;
   3693     if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
   3694     {
   3695         state->cursor = text_len;
   3696         state->has_preferred_x = 0;
   3697         return;
   3698     }
   3699     IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
   3700 }
   3701 
   3702 } // namespace ImStb
   3703 
   3704 void ImGuiInputTextState::OnKeyPressed(int key)
   3705 {
   3706     stb_textedit_key(this, &Stb, key);
   3707     CursorFollow = true;
   3708     CursorAnimReset();
   3709 }
   3710 
   3711 ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
   3712 {
   3713     memset(this, 0, sizeof(*this));
   3714 }
   3715 
   3716 // Public API to manipulate UTF-8 text
   3717 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
   3718 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
   3719 void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
   3720 {
   3721     IM_ASSERT(pos + bytes_count <= BufTextLen);
   3722     char* dst = Buf + pos;
   3723     const char* src = Buf + pos + bytes_count;
   3724     while (char c = *src++)
   3725         *dst++ = c;
   3726     *dst = '\0';
   3727 
   3728     if (CursorPos >= pos + bytes_count)
   3729         CursorPos -= bytes_count;
   3730     else if (CursorPos >= pos)
   3731         CursorPos = pos;
   3732     SelectionStart = SelectionEnd = CursorPos;
   3733     BufDirty = true;
   3734     BufTextLen -= bytes_count;
   3735 }
   3736 
   3737 void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
   3738 {
   3739     const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
   3740     const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
   3741     if (new_text_len + BufTextLen >= BufSize)
   3742     {
   3743         if (!is_resizable)
   3744             return;
   3745 
   3746         // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!)
   3747         ImGuiContext& g = *GImGui;
   3748         ImGuiInputTextState* edit_state = &g.InputTextState;
   3749         IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
   3750         IM_ASSERT(Buf == edit_state->TextA.Data);
   3751         int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
   3752         edit_state->TextA.reserve(new_buf_size + 1);
   3753         Buf = edit_state->TextA.Data;
   3754         BufSize = edit_state->BufCapacityA = new_buf_size;
   3755     }
   3756 
   3757     if (BufTextLen != pos)
   3758         memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
   3759     memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
   3760     Buf[BufTextLen + new_text_len] = '\0';
   3761 
   3762     if (CursorPos >= pos)
   3763         CursorPos += new_text_len;
   3764     SelectionStart = SelectionEnd = CursorPos;
   3765     BufDirty = true;
   3766     BufTextLen += new_text_len;
   3767 }
   3768 
   3769 // Return false to discard a character.
   3770 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source)
   3771 {
   3772     IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard);
   3773     unsigned int c = *p_char;
   3774 
   3775     // Filter non-printable (NB: isprint is unreliable! see #2467)
   3776     if (c < 0x20)
   3777     {
   3778         bool pass = false;
   3779         pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
   3780         pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
   3781         if (!pass)
   3782             return false;
   3783     }
   3784 
   3785     if (input_source != ImGuiInputSource_Clipboard)
   3786     {
   3787         // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
   3788         if (c == 127)
   3789             return false;
   3790 
   3791         // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
   3792         if (c >= 0xE000 && c <= 0xF8FF)
   3793             return false;
   3794     }
   3795 
   3796     // Filter Unicode ranges we are not handling in this build
   3797     if (c > IM_UNICODE_CODEPOINT_MAX)
   3798         return false;
   3799 
   3800     // Generic named filters
   3801     if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
   3802     {
   3803         // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf.
   3804         // The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
   3805         // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point.
   3806         // Change the default decimal_point with:
   3807         //   ImGui::GetCurrentContext()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point;
   3808         ImGuiContext& g = *GImGui;
   3809         const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint;
   3810 
   3811         // Allow 0-9 . - + * /
   3812         if (flags & ImGuiInputTextFlags_CharsDecimal)
   3813             if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
   3814                 return false;
   3815 
   3816         // Allow 0-9 . - + * / e E
   3817         if (flags & ImGuiInputTextFlags_CharsScientific)
   3818             if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
   3819                 return false;
   3820 
   3821         // Allow 0-9 a-F A-F
   3822         if (flags & ImGuiInputTextFlags_CharsHexadecimal)
   3823             if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
   3824                 return false;
   3825 
   3826         // Turn a-z into A-Z
   3827         if (flags & ImGuiInputTextFlags_CharsUppercase)
   3828             if (c >= 'a' && c <= 'z')
   3829                 *p_char = (c += (unsigned int)('A' - 'a'));
   3830 
   3831         if (flags & ImGuiInputTextFlags_CharsNoBlank)
   3832             if (ImCharIsBlankW(c))
   3833                 return false;
   3834     }
   3835 
   3836     // Custom callback filter
   3837     if (flags & ImGuiInputTextFlags_CallbackCharFilter)
   3838     {
   3839         ImGuiInputTextCallbackData callback_data;
   3840         memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
   3841         callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
   3842         callback_data.EventChar = (ImWchar)c;
   3843         callback_data.Flags = flags;
   3844         callback_data.UserData = user_data;
   3845         if (callback(&callback_data) != 0)
   3846             return false;
   3847         *p_char = callback_data.EventChar;
   3848         if (!callback_data.EventChar)
   3849             return false;
   3850     }
   3851 
   3852     return true;
   3853 }
   3854 
   3855 // Edit a string of text
   3856 // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
   3857 //   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
   3858 //   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
   3859 // - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
   3860 // - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
   3861 // (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
   3862 //  doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
   3863 bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
   3864 {
   3865     ImGuiWindow* window = GetCurrentWindow();
   3866     if (window->SkipItems)
   3867         return false;
   3868 
   3869     IM_ASSERT(buf != NULL && buf_size >= 0);
   3870     IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
   3871     IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
   3872 
   3873     ImGuiContext& g = *GImGui;
   3874     ImGuiIO& io = g.IO;
   3875     const ImGuiStyle& style = g.Style;
   3876 
   3877     const bool RENDER_SELECTION_WHEN_INACTIVE = false;
   3878     const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
   3879     const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
   3880     const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
   3881     const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
   3882     const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
   3883     if (is_resizable)
   3884         IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
   3885 
   3886     if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
   3887         BeginGroup();
   3888     const ImGuiID id = window->GetID(label);
   3889     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   3890     const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line
   3891     const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
   3892 
   3893     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
   3894     const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
   3895 
   3896     ImGuiWindow* draw_window = window;
   3897     ImVec2 inner_size = frame_size;
   3898     if (is_multiline)
   3899     {
   3900         if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable))
   3901         {
   3902             ItemSize(total_bb, style.FramePadding.y);
   3903             EndGroup();
   3904             return false;
   3905         }
   3906 
   3907         // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
   3908         PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
   3909         PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
   3910         PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
   3911         bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove);
   3912         PopStyleVar(2);
   3913         PopStyleColor();
   3914         if (!child_visible)
   3915         {
   3916             EndChild();
   3917             EndGroup();
   3918             return false;
   3919         }
   3920         draw_window = g.CurrentWindow; // Child window
   3921         draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it.
   3922         draw_window->DC.CursorPos += style.FramePadding;
   3923         inner_size.x -= draw_window->ScrollbarSizes.x;
   3924     }
   3925     else
   3926     {
   3927         // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
   3928         ItemSize(total_bb, style.FramePadding.y);
   3929         if (!(flags & ImGuiInputTextFlags_MergedItem))
   3930             if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable))
   3931                 return false;
   3932     }
   3933     const bool hovered = ItemHoverable(frame_bb, id);
   3934     if (hovered)
   3935         g.MouseCursor = ImGuiMouseCursor_TextInput;
   3936 
   3937     // We are only allowed to access the state if we are already the active widget.
   3938     ImGuiInputTextState* state = GetInputTextState(id);
   3939 
   3940     const bool focus_requested_by_code = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_FocusedByCode) != 0;
   3941     const bool focus_requested_by_tabbing = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
   3942 
   3943     const bool user_clicked = hovered && io.MouseClicked[0];
   3944     const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_Keyboard));
   3945     const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
   3946     const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
   3947 
   3948     bool clear_active_id = false;
   3949     bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
   3950 
   3951     float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
   3952 
   3953     const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline);
   3954     const bool init_make_active = (user_clicked || user_scroll_finish || user_nav_input_start || focus_requested_by_code || focus_requested_by_tabbing);
   3955     const bool init_state = (init_make_active || user_scroll_active);
   3956     if ((init_state && g.ActiveId != id) || init_changed_specs)
   3957     {
   3958         // Access state even if we don't own it yet.
   3959         state = &g.InputTextState;
   3960         state->CursorAnimReset();
   3961 
   3962         // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
   3963         // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
   3964         const int buf_len = (int)strlen(buf);
   3965         state->InitialTextA.resize(buf_len + 1);    // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
   3966         memcpy(state->InitialTextA.Data, buf, buf_len + 1);
   3967 
   3968         // Start edition
   3969         const char* buf_end = NULL;
   3970         state->TextW.resize(buf_size + 1);          // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
   3971         state->TextA.resize(0);
   3972         state->TextAIsValid = false;                // TextA is not valid yet (we will display buf until then)
   3973         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
   3974         state->CurLenA = (int)(buf_end - buf);      // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
   3975 
   3976         // Preserve cursor position and undo/redo stack if we come back to same widget
   3977         // FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed.
   3978         const bool recycle_state = (state->ID == id && !init_changed_specs);
   3979         if (recycle_state)
   3980         {
   3981             // Recycle existing cursor/selection/undo stack but clamp position
   3982             // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
   3983             state->CursorClamp();
   3984         }
   3985         else
   3986         {
   3987             state->ID = id;
   3988             state->ScrollX = 0.0f;
   3989             stb_textedit_initialize_state(&state->Stb, !is_multiline);
   3990             if (!is_multiline && focus_requested_by_code)
   3991                 select_all = true;
   3992         }
   3993         if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
   3994             state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863)
   3995         if (!is_multiline && (focus_requested_by_tabbing || (user_clicked && io.KeyCtrl)))
   3996             select_all = true;
   3997     }
   3998 
   3999     if (g.ActiveId != id && init_make_active)
   4000     {
   4001         IM_ASSERT(state && state->ID == id);
   4002         SetActiveID(id, window);
   4003         SetFocusID(id, window);
   4004         FocusWindow(window);
   4005 
   4006         // Declare our inputs
   4007         IM_ASSERT(ImGuiNavInput_COUNT < 32);
   4008         g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
   4009         if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
   4010             g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
   4011         g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);
   4012         g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Home) | ((ImU64)1 << ImGuiKey_End);
   4013         if (is_multiline)
   4014             g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown);
   4015         if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput))  // Disable keyboard tabbing out as we will use the \t character.
   4016             g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Tab);
   4017     }
   4018 
   4019     // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
   4020     if (g.ActiveId == id && state == NULL)
   4021         ClearActiveID();
   4022 
   4023     // Release focus when we click outside
   4024     if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
   4025         clear_active_id = true;
   4026 
   4027     // Lock the decision of whether we are going to take the path displaying the cursor or selection
   4028     const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
   4029     bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
   4030     bool value_changed = false;
   4031     bool enter_pressed = false;
   4032 
   4033     // When read-only we always use the live data passed to the function
   4034     // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
   4035     if (is_readonly && state != NULL && (render_cursor || render_selection))
   4036     {
   4037         const char* buf_end = NULL;
   4038         state->TextW.resize(buf_size + 1);
   4039         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end);
   4040         state->CurLenA = (int)(buf_end - buf);
   4041         state->CursorClamp();
   4042         render_selection &= state->HasSelection();
   4043     }
   4044 
   4045     // Select the buffer to render.
   4046     const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid;
   4047     const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
   4048 
   4049     // Password pushes a temporary font with only a fallback glyph
   4050     if (is_password && !is_displaying_hint)
   4051     {
   4052         const ImFontGlyph* glyph = g.Font->FindGlyph('*');
   4053         ImFont* password_font = &g.InputTextPasswordFont;
   4054         password_font->FontSize = g.Font->FontSize;
   4055         password_font->Scale = g.Font->Scale;
   4056         password_font->Ascent = g.Font->Ascent;
   4057         password_font->Descent = g.Font->Descent;
   4058         password_font->ContainerAtlas = g.Font->ContainerAtlas;
   4059         password_font->FallbackGlyph = glyph;
   4060         password_font->FallbackAdvanceX = glyph->AdvanceX;
   4061         IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
   4062         PushFont(password_font);
   4063     }
   4064 
   4065     // Process mouse inputs and character inputs
   4066     int backup_current_text_length = 0;
   4067     if (g.ActiveId == id)
   4068     {
   4069         IM_ASSERT(state != NULL);
   4070         backup_current_text_length = state->CurLenA;
   4071         state->Edited = false;
   4072         state->BufCapacityA = buf_size;
   4073         state->Flags = flags;
   4074         state->UserCallback = callback;
   4075         state->UserCallbackData = callback_user_data;
   4076 
   4077         // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
   4078         // Down the line we should have a cleaner library-wide concept of Selected vs Active.
   4079         g.ActiveIdAllowOverlap = !io.MouseDown[0];
   4080         g.WantTextInputNextFrame = 1;
   4081 
   4082         // Edit in progress
   4083         const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX;
   4084         const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
   4085 
   4086         const bool is_osx = io.ConfigMacOSXBehaviors;
   4087         if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
   4088         {
   4089             state->SelectAll();
   4090             state->SelectedAllMouseLock = true;
   4091         }
   4092         else if (hovered && is_osx && io.MouseDoubleClicked[0])
   4093         {
   4094             // Double-click select a word only, OS X style (by simulating keystrokes)
   4095             state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
   4096             state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
   4097         }
   4098         else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
   4099         {
   4100             if (hovered)
   4101             {
   4102                 stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
   4103                 state->CursorAnimReset();
   4104             }
   4105         }
   4106         else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
   4107         {
   4108             stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
   4109             state->CursorAnimReset();
   4110             state->CursorFollow = true;
   4111         }
   4112         if (state->SelectedAllMouseLock && !io.MouseDown[0])
   4113             state->SelectedAllMouseLock = false;
   4114 
   4115         // It is ill-defined whether the backend needs to send a \t character when pressing the TAB keys.
   4116         // Win32 and GLFW naturally do it but not SDL.
   4117         const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
   4118         if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && !is_readonly)
   4119             if (!io.InputQueueCharacters.contains('\t'))
   4120             {
   4121                 unsigned int c = '\t'; // Insert TAB
   4122                 if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
   4123                     state->OnKeyPressed((int)c);
   4124             }
   4125 
   4126         // Process regular text input (before we check for Return because using some IME will effectively send a Return?)
   4127         // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
   4128         if (io.InputQueueCharacters.Size > 0)
   4129         {
   4130             if (!ignore_char_inputs && !is_readonly && !user_nav_input_start)
   4131                 for (int n = 0; n < io.InputQueueCharacters.Size; n++)
   4132                 {
   4133                     // Insert character if they pass filtering
   4134                     unsigned int c = (unsigned int)io.InputQueueCharacters[n];
   4135                     if (c == '\t' && io.KeyShift)
   4136                         continue;
   4137                     if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
   4138                         state->OnKeyPressed((int)c);
   4139                 }
   4140 
   4141             // Consume characters
   4142             io.InputQueueCharacters.resize(0);
   4143         }
   4144     }
   4145 
   4146     // Process other shortcuts/key-presses
   4147     bool cancel_edit = false;
   4148     if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
   4149     {
   4150         IM_ASSERT(state != NULL);
   4151         IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here.
   4152 
   4153         const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
   4154         state->Stb.row_count_per_page = row_count_per_page;
   4155 
   4156         const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
   4157         const bool is_osx = io.ConfigMacOSXBehaviors;
   4158         const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift));
   4159         const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
   4160         const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt;  // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
   4161         const bool is_ctrl_key_only = (io.KeyMods == ImGuiKeyModFlags_Ctrl);
   4162         const bool is_shift_key_only = (io.KeyMods == ImGuiKeyModFlags_Shift);
   4163         const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl);
   4164 
   4165         const bool is_cut   = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
   4166         const bool is_copy  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only  && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection());
   4167         const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_readonly;
   4168         const bool is_undo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && !is_readonly && is_undoable);
   4169         const bool is_redo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && !is_readonly && is_undoable;
   4170 
   4171         if (IsKeyPressedMap(ImGuiKey_LeftArrow))                        { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
   4172         else if (IsKeyPressedMap(ImGuiKey_RightArrow))                  { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
   4173         else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline)     { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
   4174         else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline)   { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
   4175         else if (IsKeyPressedMap(ImGuiKey_PageUp) && is_multiline)      { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
   4176         else if (IsKeyPressedMap(ImGuiKey_PageDown) && is_multiline)    { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
   4177         else if (IsKeyPressedMap(ImGuiKey_Home))                        { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
   4178         else if (IsKeyPressedMap(ImGuiKey_End))                         { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
   4179         else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly)      { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
   4180         else if (IsKeyPressedMap(ImGuiKey_Backspace) && !is_readonly)
   4181         {
   4182             if (!state->HasSelection())
   4183             {
   4184                 if (is_wordmove_key_down)
   4185                     state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
   4186                 else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl)
   4187                     state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
   4188             }
   4189             state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
   4190         }
   4191         else if (IsKeyPressedMap(ImGuiKey_Enter) || IsKeyPressedMap(ImGuiKey_KeyPadEnter))
   4192         {
   4193             bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
   4194             if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
   4195             {
   4196                 enter_pressed = clear_active_id = true;
   4197             }
   4198             else if (!is_readonly)
   4199             {
   4200                 unsigned int c = '\n'; // Insert new line
   4201                 if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
   4202                     state->OnKeyPressed((int)c);
   4203             }
   4204         }
   4205         else if (IsKeyPressedMap(ImGuiKey_Escape))
   4206         {
   4207             clear_active_id = cancel_edit = true;
   4208         }
   4209         else if (is_undo || is_redo)
   4210         {
   4211             state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
   4212             state->ClearSelection();
   4213         }
   4214         else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
   4215         {
   4216             state->SelectAll();
   4217             state->CursorFollow = true;
   4218         }
   4219         else if (is_cut || is_copy)
   4220         {
   4221             // Cut, Copy
   4222             if (io.SetClipboardTextFn)
   4223             {
   4224                 const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0;
   4225                 const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW;
   4226                 const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1;
   4227                 char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char));
   4228                 ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie);
   4229                 SetClipboardText(clipboard_data);
   4230                 MemFree(clipboard_data);
   4231             }
   4232             if (is_cut)
   4233             {
   4234                 if (!state->HasSelection())
   4235                     state->SelectAll();
   4236                 state->CursorFollow = true;
   4237                 stb_textedit_cut(state, &state->Stb);
   4238             }
   4239         }
   4240         else if (is_paste)
   4241         {
   4242             if (const char* clipboard = GetClipboardText())
   4243             {
   4244                 // Filter pasted buffer
   4245                 const int clipboard_len = (int)strlen(clipboard);
   4246                 ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar));
   4247                 int clipboard_filtered_len = 0;
   4248                 for (const char* s = clipboard; *s; )
   4249                 {
   4250                     unsigned int c;
   4251                     s += ImTextCharFromUtf8(&c, s, NULL);
   4252                     if (c == 0)
   4253                         break;
   4254                     if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard))
   4255                         continue;
   4256                     clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
   4257                 }
   4258                 clipboard_filtered[clipboard_filtered_len] = 0;
   4259                 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
   4260                 {
   4261                     stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len);
   4262                     state->CursorFollow = true;
   4263                 }
   4264                 MemFree(clipboard_filtered);
   4265             }
   4266         }
   4267 
   4268         // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
   4269         render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
   4270     }
   4271 
   4272     // Process callbacks and apply result back to user's buffer.
   4273     if (g.ActiveId == id)
   4274     {
   4275         IM_ASSERT(state != NULL);
   4276         const char* apply_new_text = NULL;
   4277         int apply_new_text_length = 0;
   4278         if (cancel_edit)
   4279         {
   4280             // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
   4281             if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0)
   4282             {
   4283                 // Push records into the undo stack so we can CTRL+Z the revert operation itself
   4284                 apply_new_text = state->InitialTextA.Data;
   4285                 apply_new_text_length = state->InitialTextA.Size - 1;
   4286                 ImVector<ImWchar> w_text;
   4287                 if (apply_new_text_length > 0)
   4288                 {
   4289                     w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1);
   4290                     ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length);
   4291                 }
   4292                 stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0);
   4293             }
   4294         }
   4295 
   4296         // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
   4297         // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
   4298         // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
   4299         bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
   4300         if (apply_edit_back_to_user_buffer)
   4301         {
   4302             // Apply new value immediately - copy modified buffer back
   4303             // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
   4304             // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
   4305             // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
   4306             if (!is_readonly)
   4307             {
   4308                 state->TextAIsValid = true;
   4309                 state->TextA.resize(state->TextW.Size * 4 + 1);
   4310                 ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
   4311             }
   4312 
   4313             // User callback
   4314             if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
   4315             {
   4316                 IM_ASSERT(callback != NULL);
   4317 
   4318                 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
   4319                 ImGuiInputTextFlags event_flag = 0;
   4320                 ImGuiKey event_key = ImGuiKey_COUNT;
   4321                 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
   4322                 {
   4323                     event_flag = ImGuiInputTextFlags_CallbackCompletion;
   4324                     event_key = ImGuiKey_Tab;
   4325                 }
   4326                 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
   4327                 {
   4328                     event_flag = ImGuiInputTextFlags_CallbackHistory;
   4329                     event_key = ImGuiKey_UpArrow;
   4330                 }
   4331                 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
   4332                 {
   4333                     event_flag = ImGuiInputTextFlags_CallbackHistory;
   4334                     event_key = ImGuiKey_DownArrow;
   4335                 }
   4336                 else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
   4337                 {
   4338                     event_flag = ImGuiInputTextFlags_CallbackEdit;
   4339                 }
   4340                 else if (flags & ImGuiInputTextFlags_CallbackAlways)
   4341                 {
   4342                     event_flag = ImGuiInputTextFlags_CallbackAlways;
   4343                 }
   4344 
   4345                 if (event_flag)
   4346                 {
   4347                     ImGuiInputTextCallbackData callback_data;
   4348                     memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
   4349                     callback_data.EventFlag = event_flag;
   4350                     callback_data.Flags = flags;
   4351                     callback_data.UserData = callback_user_data;
   4352 
   4353                     callback_data.EventKey = event_key;
   4354                     callback_data.Buf = state->TextA.Data;
   4355                     callback_data.BufTextLen = state->CurLenA;
   4356                     callback_data.BufSize = state->BufCapacityA;
   4357                     callback_data.BufDirty = false;
   4358 
   4359                     // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
   4360                     ImWchar* text = state->TextW.Data;
   4361                     const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor);
   4362                     const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start);
   4363                     const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end);
   4364 
   4365                     // Call user code
   4366                     callback(&callback_data);
   4367 
   4368                     // Read back what user may have modified
   4369                     IM_ASSERT(callback_data.Buf == state->TextA.Data);  // Invalid to modify those fields
   4370                     IM_ASSERT(callback_data.BufSize == state->BufCapacityA);
   4371                     IM_ASSERT(callback_data.Flags == flags);
   4372                     const bool buf_dirty = callback_data.BufDirty;
   4373                     if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty)            { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; }
   4374                     if (callback_data.SelectionStart != utf8_selection_start || buf_dirty)  { state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb.cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
   4375                     if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty)      { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
   4376                     if (buf_dirty)
   4377                     {
   4378                         IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
   4379                         if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
   4380                             state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
   4381                         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL);
   4382                         state->CurLenA = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
   4383                         state->CursorAnimReset();
   4384                     }
   4385                 }
   4386             }
   4387 
   4388             // Will copy result string if modified
   4389             if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
   4390             {
   4391                 apply_new_text = state->TextA.Data;
   4392                 apply_new_text_length = state->CurLenA;
   4393             }
   4394         }
   4395 
   4396         // Copy result to user buffer
   4397         if (apply_new_text)
   4398         {
   4399             // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
   4400             // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
   4401             // without any storage on user's side.
   4402             IM_ASSERT(apply_new_text_length >= 0);
   4403             if (is_resizable)
   4404             {
   4405                 ImGuiInputTextCallbackData callback_data;
   4406                 callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
   4407                 callback_data.Flags = flags;
   4408                 callback_data.Buf = buf;
   4409                 callback_data.BufTextLen = apply_new_text_length;
   4410                 callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
   4411                 callback_data.UserData = callback_user_data;
   4412                 callback(&callback_data);
   4413                 buf = callback_data.Buf;
   4414                 buf_size = callback_data.BufSize;
   4415                 apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
   4416                 IM_ASSERT(apply_new_text_length <= buf_size);
   4417             }
   4418             //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
   4419 
   4420             // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
   4421             ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
   4422             value_changed = true;
   4423         }
   4424 
   4425         // Clear temporary user storage
   4426         state->Flags = ImGuiInputTextFlags_None;
   4427         state->UserCallback = NULL;
   4428         state->UserCallbackData = NULL;
   4429     }
   4430 
   4431     // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
   4432     if (clear_active_id && g.ActiveId == id)
   4433         ClearActiveID();
   4434 
   4435     // Render frame
   4436     if (!is_multiline)
   4437     {
   4438         RenderNavHighlight(frame_bb, id);
   4439         RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
   4440     }
   4441 
   4442     const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size
   4443     ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
   4444     ImVec2 text_size(0.0f, 0.0f);
   4445 
   4446     // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
   4447     // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
   4448     // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
   4449     const int buf_display_max_length = 2 * 1024 * 1024;
   4450     const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
   4451     const char* buf_display_end = NULL; // We have specialized paths below for setting the length
   4452     if (is_displaying_hint)
   4453     {
   4454         buf_display = hint;
   4455         buf_display_end = hint + strlen(hint);
   4456     }
   4457 
   4458     // Render text. We currently only render selection when the widget is active or while scrolling.
   4459     // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.
   4460     if (render_cursor || render_selection)
   4461     {
   4462         IM_ASSERT(state != NULL);
   4463         if (!is_displaying_hint)
   4464             buf_display_end = buf_display + state->CurLenA;
   4465 
   4466         // Render text (with cursor and selection)
   4467         // This is going to be messy. We need to:
   4468         // - Display the text (this alone can be more easily clipped)
   4469         // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
   4470         // - Measure text height (for scrollbar)
   4471         // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
   4472         // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
   4473         const ImWchar* text_begin = state->TextW.Data;
   4474         ImVec2 cursor_offset, select_start_offset;
   4475 
   4476         {
   4477             // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions.
   4478             const ImWchar* searches_input_ptr[2] = { NULL, NULL };
   4479             int searches_result_line_no[2] = { -1000, -1000 };
   4480             int searches_remaining = 0;
   4481             if (render_cursor)
   4482             {
   4483                 searches_input_ptr[0] = text_begin + state->Stb.cursor;
   4484                 searches_result_line_no[0] = -1;
   4485                 searches_remaining++;
   4486             }
   4487             if (render_selection)
   4488             {
   4489                 searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
   4490                 searches_result_line_no[1] = -1;
   4491                 searches_remaining++;
   4492             }
   4493 
   4494             // Iterate all lines to find our line numbers
   4495             // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
   4496             searches_remaining += is_multiline ? 1 : 0;
   4497             int line_count = 0;
   4498             //for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++)  // FIXME-OPT: Could use this when wchar_t are 16-bit
   4499             for (const ImWchar* s = text_begin; *s != 0; s++)
   4500                 if (*s == '\n')
   4501                 {
   4502                     line_count++;
   4503                     if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; }
   4504                     if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; }
   4505                 }
   4506             line_count++;
   4507             if (searches_result_line_no[0] == -1)
   4508                 searches_result_line_no[0] = line_count;
   4509             if (searches_result_line_no[1] == -1)
   4510                 searches_result_line_no[1] = line_count;
   4511 
   4512             // Calculate 2d position by finding the beginning of the line and measuring distance
   4513             cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
   4514             cursor_offset.y = searches_result_line_no[0] * g.FontSize;
   4515             if (searches_result_line_no[1] >= 0)
   4516             {
   4517                 select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
   4518                 select_start_offset.y = searches_result_line_no[1] * g.FontSize;
   4519             }
   4520 
   4521             // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
   4522             if (is_multiline)
   4523                 text_size = ImVec2(inner_size.x, line_count * g.FontSize);
   4524         }
   4525 
   4526         // Scroll
   4527         if (render_cursor && state->CursorFollow)
   4528         {
   4529             // Horizontal scroll in chunks of quarter width
   4530             if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
   4531             {
   4532                 const float scroll_increment_x = inner_size.x * 0.25f;
   4533                 const float visible_width = inner_size.x - style.FramePadding.x;
   4534                 if (cursor_offset.x < state->ScrollX)
   4535                     state->ScrollX = IM_FLOOR(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
   4536                 else if (cursor_offset.x - visible_width >= state->ScrollX)
   4537                     state->ScrollX = IM_FLOOR(cursor_offset.x - visible_width + scroll_increment_x);
   4538             }
   4539             else
   4540             {
   4541                 state->ScrollX = 0.0f;
   4542             }
   4543 
   4544             // Vertical scroll
   4545             if (is_multiline)
   4546             {
   4547                 // Test if cursor is vertically visible
   4548                 if (cursor_offset.y - g.FontSize < scroll_y)
   4549                     scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
   4550                 else if (cursor_offset.y - inner_size.y >= scroll_y)
   4551                     scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
   4552                 const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
   4553                 scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
   4554                 draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag
   4555                 draw_window->Scroll.y = scroll_y;
   4556             }
   4557 
   4558             state->CursorFollow = false;
   4559         }
   4560 
   4561         // Draw selection
   4562         const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f);
   4563         if (render_selection)
   4564         {
   4565             const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
   4566             const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end);
   4567 
   4568             ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests.
   4569             float bg_offy_up = is_multiline ? 0.0f : -1.0f;    // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
   4570             float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
   4571             ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;
   4572             for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
   4573             {
   4574                 if (rect_pos.y > clip_rect.w + g.FontSize)
   4575                     break;
   4576                 if (rect_pos.y < clip_rect.y)
   4577                 {
   4578                     //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p);  // FIXME-OPT: Could use this when wchar_t are 16-bit
   4579                     //p = p ? p + 1 : text_selected_end;
   4580                     while (p < text_selected_end)
   4581                         if (*p++ == '\n')
   4582                             break;
   4583                 }
   4584                 else
   4585                 {
   4586                     ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
   4587                     if (rect_size.x <= 0.0f) rect_size.x = IM_FLOOR(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
   4588                     ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
   4589                     rect.ClipWith(clip_rect);
   4590                     if (rect.Overlaps(clip_rect))
   4591                         draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
   4592                 }
   4593                 rect_pos.x = draw_pos.x - draw_scroll.x;
   4594                 rect_pos.y += g.FontSize;
   4595             }
   4596         }
   4597 
   4598         // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash.
   4599         if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
   4600         {
   4601             ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
   4602             draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
   4603         }
   4604 
   4605         // Draw blinking cursor
   4606         if (render_cursor)
   4607         {
   4608             state->CursorAnim += io.DeltaTime;
   4609             bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
   4610             ImVec2 cursor_screen_pos = ImFloor(draw_pos + cursor_offset - draw_scroll);
   4611             ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f);
   4612             if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
   4613                 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
   4614 
   4615             // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
   4616             if (!is_readonly)
   4617                 g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
   4618         }
   4619     }
   4620     else
   4621     {
   4622         // Render text only (no selection, no cursor)
   4623         if (is_multiline)
   4624             text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width
   4625         else if (!is_displaying_hint && g.ActiveId == id)
   4626             buf_display_end = buf_display + state->CurLenA;
   4627         else if (!is_displaying_hint)
   4628             buf_display_end = buf_display + strlen(buf_display);
   4629 
   4630         if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
   4631         {
   4632             ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
   4633             draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
   4634         }
   4635     }
   4636 
   4637     if (is_password && !is_displaying_hint)
   4638         PopFont();
   4639 
   4640     if (is_multiline)
   4641     {
   4642         Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
   4643         EndChild();
   4644         EndGroup();
   4645     }
   4646 
   4647     // Log as text
   4648     if (g.LogEnabled && (!is_password || is_displaying_hint))
   4649     {
   4650         LogSetNextTextDecoration("{", "}");
   4651         LogRenderedText(&draw_pos, buf_display, buf_display_end);
   4652     }
   4653 
   4654     if (label_size.x > 0)
   4655         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
   4656 
   4657     if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited))
   4658         MarkItemEdited(id);
   4659 
   4660     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
   4661     if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
   4662         return enter_pressed;
   4663     else
   4664         return value_changed;
   4665 }
   4666 
   4667 //-------------------------------------------------------------------------
   4668 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
   4669 //-------------------------------------------------------------------------
   4670 // - ColorEdit3()
   4671 // - ColorEdit4()
   4672 // - ColorPicker3()
   4673 // - RenderColorRectWithAlphaCheckerboard() [Internal]
   4674 // - ColorPicker4()
   4675 // - ColorButton()
   4676 // - SetColorEditOptions()
   4677 // - ColorTooltip() [Internal]
   4678 // - ColorEditOptionsPopup() [Internal]
   4679 // - ColorPickerOptionsPopup() [Internal]
   4680 //-------------------------------------------------------------------------
   4681 
   4682 bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
   4683 {
   4684     return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
   4685 }
   4686 
   4687 // Edit colors components (each component in 0.0f..1.0f range).
   4688 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
   4689 // With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
   4690 bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
   4691 {
   4692     ImGuiWindow* window = GetCurrentWindow();
   4693     if (window->SkipItems)
   4694         return false;
   4695 
   4696     ImGuiContext& g = *GImGui;
   4697     const ImGuiStyle& style = g.Style;
   4698     const float square_sz = GetFrameHeight();
   4699     const float w_full = CalcItemWidth();
   4700     const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
   4701     const float w_inputs = w_full - w_button;
   4702     const char* label_display_end = FindRenderedTextEnd(label);
   4703     g.NextItemData.ClearFlags();
   4704 
   4705     BeginGroup();
   4706     PushID(label);
   4707 
   4708     // If we're not showing any slider there's no point in doing any HSV conversions
   4709     const ImGuiColorEditFlags flags_untouched = flags;
   4710     if (flags & ImGuiColorEditFlags_NoInputs)
   4711         flags = (flags & (~ImGuiColorEditFlags__DisplayMask)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
   4712 
   4713     // Context menu: display and modify options (before defaults are applied)
   4714     if (!(flags & ImGuiColorEditFlags_NoOptions))
   4715         ColorEditOptionsPopup(col, flags);
   4716 
   4717     // Read stored options
   4718     if (!(flags & ImGuiColorEditFlags__DisplayMask))
   4719         flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DisplayMask);
   4720     if (!(flags & ImGuiColorEditFlags__DataTypeMask))
   4721         flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
   4722     if (!(flags & ImGuiColorEditFlags__PickerMask))
   4723         flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
   4724     if (!(flags & ImGuiColorEditFlags__InputMask))
   4725         flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputMask);
   4726     flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask));
   4727     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask)); // Check that only 1 is selected
   4728     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask));   // Check that only 1 is selected
   4729 
   4730     const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
   4731     const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
   4732     const int components = alpha ? 4 : 3;
   4733 
   4734     // Convert to the formats we need
   4735     float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
   4736     if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
   4737         ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
   4738     else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
   4739     {
   4740         // Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
   4741         ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
   4742         if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
   4743         {
   4744             if (f[1] == 0)
   4745                 f[0] = g.ColorEditLastHue;
   4746             if (f[2] == 0)
   4747                 f[1] = g.ColorEditLastSat;
   4748         }
   4749     }
   4750     int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };
   4751 
   4752     bool value_changed = false;
   4753     bool value_changed_as_float = false;
   4754 
   4755     const ImVec2 pos = window->DC.CursorPos;
   4756     const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
   4757     window->DC.CursorPos.x = pos.x + inputs_offset_x;
   4758 
   4759     if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
   4760     {
   4761         // RGB/HSV 0..255 Sliders
   4762         const float w_item_one  = ImMax(1.0f, IM_FLOOR((w_inputs - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
   4763         const float w_item_last = ImMax(1.0f, IM_FLOOR(w_inputs - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
   4764 
   4765         const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
   4766         static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
   4767         static const char* fmt_table_int[3][4] =
   4768         {
   4769             {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
   4770             { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
   4771             { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
   4772         };
   4773         static const char* fmt_table_float[3][4] =
   4774         {
   4775             {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
   4776             { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
   4777             { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
   4778         };
   4779         const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
   4780 
   4781         for (int n = 0; n < components; n++)
   4782         {
   4783             if (n > 0)
   4784                 SameLine(0, style.ItemInnerSpacing.x);
   4785             SetNextItemWidth((n + 1 < components) ? w_item_one : w_item_last);
   4786 
   4787             // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
   4788             if (flags & ImGuiColorEditFlags_Float)
   4789             {
   4790                 value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
   4791                 value_changed_as_float |= value_changed;
   4792             }
   4793             else
   4794             {
   4795                 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
   4796             }
   4797             if (!(flags & ImGuiColorEditFlags_NoOptions))
   4798                 OpenPopupOnItemClick("context");
   4799         }
   4800     }
   4801     else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
   4802     {
   4803         // RGB Hexadecimal Input
   4804         char buf[64];
   4805         if (alpha)
   4806             ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255));
   4807         else
   4808             ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
   4809         SetNextItemWidth(w_inputs);
   4810         if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
   4811         {
   4812             value_changed = true;
   4813             char* p = buf;
   4814             while (*p == '#' || ImCharIsBlankA(*p))
   4815                 p++;
   4816             i[0] = i[1] = i[2] = 0;
   4817             i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)
   4818             int r;
   4819             if (alpha)
   4820                 r = sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
   4821             else
   4822                 r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
   4823             IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.
   4824         }
   4825         if (!(flags & ImGuiColorEditFlags_NoOptions))
   4826             OpenPopupOnItemClick("context");
   4827     }
   4828 
   4829     ImGuiWindow* picker_active_window = NULL;
   4830     if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
   4831     {
   4832         const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
   4833         window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
   4834 
   4835         const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
   4836         if (ColorButton("##ColorButton", col_v4, flags))
   4837         {
   4838             if (!(flags & ImGuiColorEditFlags_NoPicker))
   4839             {
   4840                 // Store current color and open a picker
   4841                 g.ColorPickerRef = col_v4;
   4842                 OpenPopup("picker");
   4843                 SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1, style.ItemSpacing.y));
   4844             }
   4845         }
   4846         if (!(flags & ImGuiColorEditFlags_NoOptions))
   4847             OpenPopupOnItemClick("context");
   4848 
   4849         if (BeginPopup("picker"))
   4850         {
   4851             picker_active_window = g.CurrentWindow;
   4852             if (label != label_display_end)
   4853             {
   4854                 TextEx(label, label_display_end);
   4855                 Spacing();
   4856             }
   4857             ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
   4858             ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
   4859             SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
   4860             value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
   4861             EndPopup();
   4862         }
   4863     }
   4864 
   4865     if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
   4866     {
   4867         const float text_offset_x = (flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x;
   4868         window->DC.CursorPos = ImVec2(pos.x + text_offset_x, pos.y + style.FramePadding.y);
   4869         TextEx(label, label_display_end);
   4870     }
   4871 
   4872     // Convert back
   4873     if (value_changed && picker_active_window == NULL)
   4874     {
   4875         if (!value_changed_as_float)
   4876             for (int n = 0; n < 4; n++)
   4877                 f[n] = i[n] / 255.0f;
   4878         if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
   4879         {
   4880             g.ColorEditLastHue = f[0];
   4881             g.ColorEditLastSat = f[1];
   4882             ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
   4883             memcpy(g.ColorEditLastColor, f, sizeof(float) * 3);
   4884         }
   4885         if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
   4886             ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
   4887 
   4888         col[0] = f[0];
   4889         col[1] = f[1];
   4890         col[2] = f[2];
   4891         if (alpha)
   4892             col[3] = f[3];
   4893     }
   4894 
   4895     PopID();
   4896     EndGroup();
   4897 
   4898     // Drag and Drop Target
   4899     // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
   4900     if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
   4901     {
   4902         bool accepted_drag_drop = false;
   4903         if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
   4904         {
   4905             memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512
   4906             value_changed = accepted_drag_drop = true;
   4907         }
   4908         if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
   4909         {
   4910             memcpy((float*)col, payload->Data, sizeof(float) * components);
   4911             value_changed = accepted_drag_drop = true;
   4912         }
   4913 
   4914         // Drag-drop payloads are always RGB
   4915         if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
   4916             ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
   4917         EndDragDropTarget();
   4918     }
   4919 
   4920     // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
   4921     if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
   4922         window->DC.LastItemId = g.ActiveId;
   4923 
   4924     if (value_changed)
   4925         MarkItemEdited(window->DC.LastItemId);
   4926 
   4927     return value_changed;
   4928 }
   4929 
   4930 bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
   4931 {
   4932     float col4[4] = { col[0], col[1], col[2], 1.0f };
   4933     if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
   4934         return false;
   4935     col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
   4936     return true;
   4937 }
   4938 
   4939 // Helper for ColorPicker4()
   4940 static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
   4941 {
   4942     ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
   4943     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1,         pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32(0,0,0,alpha8));
   4944     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32(255,255,255,alpha8));
   4945     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left,  IM_COL32(0,0,0,alpha8));
   4946     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32(255,255,255,alpha8));
   4947 }
   4948 
   4949 // Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
   4950 // (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.)
   4951 // FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
   4952 // FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0)
   4953 bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
   4954 {
   4955     ImGuiContext& g = *GImGui;
   4956     ImGuiWindow* window = GetCurrentWindow();
   4957     if (window->SkipItems)
   4958         return false;
   4959 
   4960     ImDrawList* draw_list = window->DrawList;
   4961     ImGuiStyle& style = g.Style;
   4962     ImGuiIO& io = g.IO;
   4963 
   4964     const float width = CalcItemWidth();
   4965     g.NextItemData.ClearFlags();
   4966 
   4967     PushID(label);
   4968     BeginGroup();
   4969 
   4970     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
   4971         flags |= ImGuiColorEditFlags_NoSmallPreview;
   4972 
   4973     // Context menu: display and store options.
   4974     if (!(flags & ImGuiColorEditFlags_NoOptions))
   4975         ColorPickerOptionsPopup(col, flags);
   4976 
   4977     // Read stored options
   4978     if (!(flags & ImGuiColorEditFlags__PickerMask))
   4979         flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
   4980     if (!(flags & ImGuiColorEditFlags__InputMask))
   4981         flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__InputMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__InputMask;
   4982     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask)); // Check that only 1 is selected
   4983     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask));  // Check that only 1 is selected
   4984     if (!(flags & ImGuiColorEditFlags_NoOptions))
   4985         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
   4986 
   4987     // Setup
   4988     int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
   4989     bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
   4990     ImVec2 picker_pos = window->DC.CursorPos;
   4991     float square_sz = GetFrameHeight();
   4992     float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
   4993     float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
   4994     float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
   4995     float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
   4996     float bars_triangles_half_sz = IM_FLOOR(bars_width * 0.20f);
   4997 
   4998     float backup_initial_col[4];
   4999     memcpy(backup_initial_col, col, components * sizeof(float));
   5000 
   5001     float wheel_thickness = sv_picker_size * 0.08f;
   5002     float wheel_r_outer = sv_picker_size * 0.50f;
   5003     float wheel_r_inner = wheel_r_outer - wheel_thickness;
   5004     ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f);
   5005 
   5006     // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
   5007     float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
   5008     ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
   5009     ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
   5010     ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
   5011 
   5012     float H = col[0], S = col[1], V = col[2];
   5013     float R = col[0], G = col[1], B = col[2];
   5014     if (flags & ImGuiColorEditFlags_InputRGB)
   5015     {
   5016         // Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
   5017         ColorConvertRGBtoHSV(R, G, B, H, S, V);
   5018         if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
   5019         {
   5020             if (S == 0)
   5021                 H = g.ColorEditLastHue;
   5022             if (V == 0)
   5023                 S = g.ColorEditLastSat;
   5024         }
   5025     }
   5026     else if (flags & ImGuiColorEditFlags_InputHSV)
   5027     {
   5028         ColorConvertHSVtoRGB(H, S, V, R, G, B);
   5029     }
   5030 
   5031     bool value_changed = false, value_changed_h = false, value_changed_sv = false;
   5032 
   5033     PushItemFlag(ImGuiItemFlags_NoNav, true);
   5034     if (flags & ImGuiColorEditFlags_PickerHueWheel)
   5035     {
   5036         // Hue wheel + SV triangle logic
   5037         InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
   5038         if (IsItemActive())
   5039         {
   5040             ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
   5041             ImVec2 current_off = g.IO.MousePos - wheel_center;
   5042             float initial_dist2 = ImLengthSqr(initial_off);
   5043             if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))
   5044             {
   5045                 // Interactive with Hue wheel
   5046                 H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
   5047                 if (H < 0.0f)
   5048                     H += 1.0f;
   5049                 value_changed = value_changed_h = true;
   5050             }
   5051             float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
   5052             float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
   5053             if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
   5054             {
   5055                 // Interacting with SV triangle
   5056                 ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
   5057                 if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
   5058                     current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
   5059                 float uu, vv, ww;
   5060                 ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
   5061                 V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
   5062                 S = ImClamp(uu / V, 0.0001f, 1.0f);
   5063                 value_changed = value_changed_sv = true;
   5064             }
   5065         }
   5066         if (!(flags & ImGuiColorEditFlags_NoOptions))
   5067             OpenPopupOnItemClick("context");
   5068     }
   5069     else if (flags & ImGuiColorEditFlags_PickerHueBar)
   5070     {
   5071         // SV rectangle logic
   5072         InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
   5073         if (IsItemActive())
   5074         {
   5075             S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
   5076             V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
   5077             value_changed = value_changed_sv = true;
   5078         }
   5079         if (!(flags & ImGuiColorEditFlags_NoOptions))
   5080             OpenPopupOnItemClick("context");
   5081 
   5082         // Hue bar logic
   5083         SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
   5084         InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
   5085         if (IsItemActive())
   5086         {
   5087             H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
   5088             value_changed = value_changed_h = true;
   5089         }
   5090     }
   5091 
   5092     // Alpha bar logic
   5093     if (alpha_bar)
   5094     {
   5095         SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
   5096         InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
   5097         if (IsItemActive())
   5098         {
   5099             col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
   5100             value_changed = true;
   5101         }
   5102     }
   5103     PopItemFlag(); // ImGuiItemFlags_NoNav
   5104 
   5105     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
   5106     {
   5107         SameLine(0, style.ItemInnerSpacing.x);
   5108         BeginGroup();
   5109     }
   5110 
   5111     if (!(flags & ImGuiColorEditFlags_NoLabel))
   5112     {
   5113         const char* label_display_end = FindRenderedTextEnd(label);
   5114         if (label != label_display_end)
   5115         {
   5116             if ((flags & ImGuiColorEditFlags_NoSidePreview))
   5117                 SameLine(0, style.ItemInnerSpacing.x);
   5118             TextEx(label, label_display_end);
   5119         }
   5120     }
   5121 
   5122     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
   5123     {
   5124         PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
   5125         ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
   5126         if ((flags & ImGuiColorEditFlags_NoLabel))
   5127             Text("Current");
   5128 
   5129         ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip;
   5130         ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
   5131         if (ref_col != NULL)
   5132         {
   5133             Text("Original");
   5134             ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
   5135             if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
   5136             {
   5137                 memcpy(col, ref_col, components * sizeof(float));
   5138                 value_changed = true;
   5139             }
   5140         }
   5141         PopItemFlag();
   5142         EndGroup();
   5143     }
   5144 
   5145     // Convert back color to RGB
   5146     if (value_changed_h || value_changed_sv)
   5147     {
   5148         if (flags & ImGuiColorEditFlags_InputRGB)
   5149         {
   5150             ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10 * 1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]);
   5151             g.ColorEditLastHue = H;
   5152             g.ColorEditLastSat = S;
   5153             memcpy(g.ColorEditLastColor, col, sizeof(float) * 3);
   5154         }
   5155         else if (flags & ImGuiColorEditFlags_InputHSV)
   5156         {
   5157             col[0] = H;
   5158             col[1] = S;
   5159             col[2] = V;
   5160         }
   5161     }
   5162 
   5163     // R,G,B and H,S,V slider color editor
   5164     bool value_changed_fix_hue_wrap = false;
   5165     if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
   5166     {
   5167         PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
   5168         ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
   5169         ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
   5170         if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags__DisplayMask) == 0)
   5171             if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
   5172             {
   5173                 // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
   5174                 // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)
   5175                 value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
   5176                 value_changed = true;
   5177             }
   5178         if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags__DisplayMask) == 0)
   5179             value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
   5180         if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags__DisplayMask) == 0)
   5181             value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
   5182         PopItemWidth();
   5183     }
   5184 
   5185     // Try to cancel hue wrap (after ColorEdit4 call), if any
   5186     if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
   5187     {
   5188         float new_H, new_S, new_V;
   5189         ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
   5190         if (new_H <= 0 && H > 0)
   5191         {
   5192             if (new_V <= 0 && V != new_V)
   5193                 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
   5194             else if (new_S <= 0)
   5195                 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
   5196         }
   5197     }
   5198 
   5199     if (value_changed)
   5200     {
   5201         if (flags & ImGuiColorEditFlags_InputRGB)
   5202         {
   5203             R = col[0];
   5204             G = col[1];
   5205             B = col[2];
   5206             ColorConvertRGBtoHSV(R, G, B, H, S, V);
   5207             if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) // Fix local Hue as display below will use it immediately.
   5208             {
   5209                 if (S == 0)
   5210                     H = g.ColorEditLastHue;
   5211                 if (V == 0)
   5212                     S = g.ColorEditLastSat;
   5213             }
   5214         }
   5215         else if (flags & ImGuiColorEditFlags_InputHSV)
   5216         {
   5217             H = col[0];
   5218             S = col[1];
   5219             V = col[2];
   5220             ColorConvertHSVtoRGB(H, S, V, R, G, B);
   5221         }
   5222     }
   5223 
   5224     const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
   5225     const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
   5226     const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
   5227     const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
   5228     const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) };
   5229 
   5230     ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
   5231     ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
   5232     ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!
   5233 
   5234     ImVec2 sv_cursor_pos;
   5235 
   5236     if (flags & ImGuiColorEditFlags_PickerHueWheel)
   5237     {
   5238         // Render Hue Wheel
   5239         const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
   5240         const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
   5241         for (int n = 0; n < 6; n++)
   5242         {
   5243             const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
   5244             const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
   5245             const int vert_start_idx = draw_list->VtxBuffer.Size;
   5246             draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
   5247             draw_list->PathStroke(col_white, 0, wheel_thickness);
   5248             const int vert_end_idx = draw_list->VtxBuffer.Size;
   5249 
   5250             // Paint colors over existing vertices
   5251             ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
   5252             ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
   5253             ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]);
   5254         }
   5255 
   5256         // Render Cursor + preview on Hue Wheel
   5257         float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
   5258         float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
   5259         ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f);
   5260         float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
   5261         int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
   5262         draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
   5263         draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);
   5264         draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
   5265 
   5266         // Render SV triangle (rotated according to hue)
   5267         ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
   5268         ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
   5269         ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
   5270         ImVec2 uv_white = GetFontTexUvWhitePixel();
   5271         draw_list->PrimReserve(6, 6);
   5272         draw_list->PrimVtx(tra, uv_white, hue_color32);
   5273         draw_list->PrimVtx(trb, uv_white, hue_color32);
   5274         draw_list->PrimVtx(trc, uv_white, col_white);
   5275         draw_list->PrimVtx(tra, uv_white, 0);
   5276         draw_list->PrimVtx(trb, uv_white, col_black);
   5277         draw_list->PrimVtx(trc, uv_white, 0);
   5278         draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
   5279         sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
   5280     }
   5281     else if (flags & ImGuiColorEditFlags_PickerHueBar)
   5282     {
   5283         // Render SV Square
   5284         draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
   5285         draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
   5286         RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
   5287         sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S)     * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
   5288         sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
   5289 
   5290         // Render Hue Bar
   5291         for (int i = 0; i < 6; ++i)
   5292             draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]);
   5293         float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
   5294         RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
   5295         RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha);
   5296     }
   5297 
   5298     // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
   5299     float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
   5300     draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, 12);
   5301     draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, 12);
   5302     draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, 12);
   5303 
   5304     // Render alpha bar
   5305     if (alpha_bar)
   5306     {
   5307         float alpha = ImSaturate(col[3]);
   5308         ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
   5309         RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
   5310         draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK);
   5311         float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
   5312         RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
   5313         RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha);
   5314     }
   5315 
   5316     EndGroup();
   5317 
   5318     if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
   5319         value_changed = false;
   5320     if (value_changed)
   5321         MarkItemEdited(window->DC.LastItemId);
   5322 
   5323     PopID();
   5324 
   5325     return value_changed;
   5326 }
   5327 
   5328 // A little color square. Return true when clicked.
   5329 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
   5330 // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
   5331 // Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
   5332 bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
   5333 {
   5334     ImGuiWindow* window = GetCurrentWindow();
   5335     if (window->SkipItems)
   5336         return false;
   5337 
   5338     ImGuiContext& g = *GImGui;
   5339     const ImGuiID id = window->GetID(desc_id);
   5340     float default_size = GetFrameHeight();
   5341     if (size.x == 0.0f)
   5342         size.x = default_size;
   5343     if (size.y == 0.0f)
   5344         size.y = default_size;
   5345     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
   5346     ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
   5347     if (!ItemAdd(bb, id))
   5348         return false;
   5349 
   5350     bool hovered, held;
   5351     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
   5352 
   5353     if (flags & ImGuiColorEditFlags_NoAlpha)
   5354         flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
   5355 
   5356     ImVec4 col_rgb = col;
   5357     if (flags & ImGuiColorEditFlags_InputHSV)
   5358         ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);
   5359 
   5360     ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
   5361     float grid_step = ImMin(size.x, size.y) / 2.99f;
   5362     float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
   5363     ImRect bb_inner = bb;
   5364     float off = 0.0f;
   5365     if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
   5366     {
   5367         off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
   5368         bb_inner.Expand(off);
   5369     }
   5370     if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
   5371     {
   5372         float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
   5373         RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight);
   5374         window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft);
   5375     }
   5376     else
   5377     {
   5378         // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
   5379         ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha;
   5380         if (col_source.w < 1.0f)
   5381             RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
   5382         else
   5383             window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);
   5384     }
   5385     RenderNavHighlight(bb, id);
   5386     if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
   5387     {
   5388         if (g.Style.FrameBorderSize > 0.0f)
   5389             RenderFrameBorder(bb.Min, bb.Max, rounding);
   5390         else
   5391             window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
   5392     }
   5393 
   5394     // Drag and Drop Source
   5395     // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
   5396     if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
   5397     {
   5398         if (flags & ImGuiColorEditFlags_NoAlpha)
   5399             SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
   5400         else
   5401             SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
   5402         ColorButton(desc_id, col, flags);
   5403         SameLine();
   5404         TextEx("Color");
   5405         EndDragDropSource();
   5406     }
   5407 
   5408     // Tooltip
   5409     if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
   5410         ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
   5411 
   5412     return pressed;
   5413 }
   5414 
   5415 // Initialize/override default color options
   5416 void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
   5417 {
   5418     ImGuiContext& g = *GImGui;
   5419     if ((flags & ImGuiColorEditFlags__DisplayMask) == 0)
   5420         flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DisplayMask;
   5421     if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
   5422         flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
   5423     if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
   5424         flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
   5425     if ((flags & ImGuiColorEditFlags__InputMask) == 0)
   5426         flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputMask;
   5427     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask));    // Check only 1 option is selected
   5428     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DataTypeMask));   // Check only 1 option is selected
   5429     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask));     // Check only 1 option is selected
   5430     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask));      // Check only 1 option is selected
   5431     g.ColorEditOptions = flags;
   5432 }
   5433 
   5434 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
   5435 void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
   5436 {
   5437     ImGuiContext& g = *GImGui;
   5438 
   5439     BeginTooltipEx(0, ImGuiTooltipFlags_OverridePreviousTooltip);
   5440     const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
   5441     if (text_end > text)
   5442     {
   5443         TextEx(text, text_end);
   5444         Separator();
   5445     }
   5446 
   5447     ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
   5448     ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
   5449     int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
   5450     ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
   5451     SameLine();
   5452     if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags__InputMask))
   5453     {
   5454         if (flags & ImGuiColorEditFlags_NoAlpha)
   5455             Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);
   5456         else
   5457             Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);
   5458     }
   5459     else if (flags & ImGuiColorEditFlags_InputHSV)
   5460     {
   5461         if (flags & ImGuiColorEditFlags_NoAlpha)
   5462             Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
   5463         else
   5464             Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]);
   5465     }
   5466     EndTooltip();
   5467 }
   5468 
   5469 void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
   5470 {
   5471     bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__DisplayMask);
   5472     bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
   5473     if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
   5474         return;
   5475     ImGuiContext& g = *GImGui;
   5476     ImGuiColorEditFlags opts = g.ColorEditOptions;
   5477     if (allow_opt_inputs)
   5478     {
   5479         if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayRGB;
   5480         if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHSV;
   5481         if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHex;
   5482     }
   5483     if (allow_opt_datatype)
   5484     {
   5485         if (allow_opt_inputs) Separator();
   5486         if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
   5487         if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
   5488     }
   5489 
   5490     if (allow_opt_inputs || allow_opt_datatype)
   5491         Separator();
   5492     if (Button("Copy as..", ImVec2(-1, 0)))
   5493         OpenPopup("Copy");
   5494     if (BeginPopup("Copy"))
   5495     {
   5496         int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
   5497         char buf[64];
   5498         ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
   5499         if (Selectable(buf))
   5500             SetClipboardText(buf);
   5501         ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
   5502         if (Selectable(buf))
   5503             SetClipboardText(buf);
   5504         ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
   5505         if (Selectable(buf))
   5506             SetClipboardText(buf);
   5507         if (!(flags & ImGuiColorEditFlags_NoAlpha))
   5508         {
   5509             ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
   5510             if (Selectable(buf))
   5511                 SetClipboardText(buf);
   5512         }
   5513         EndPopup();
   5514     }
   5515 
   5516     g.ColorEditOptions = opts;
   5517     EndPopup();
   5518 }
   5519 
   5520 void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
   5521 {
   5522     bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
   5523     bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
   5524     if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
   5525         return;
   5526     ImGuiContext& g = *GImGui;
   5527     if (allow_opt_picker)
   5528     {
   5529         ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
   5530         PushItemWidth(picker_size.x);
   5531         for (int picker_type = 0; picker_type < 2; picker_type++)
   5532         {
   5533             // Draw small/thumbnail version of each picker type (over an invisible button for selection)
   5534             if (picker_type > 0) Separator();
   5535             PushID(picker_type);
   5536             ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
   5537             if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
   5538             if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
   5539             ImVec2 backup_pos = GetCursorScreenPos();
   5540             if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
   5541                 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
   5542             SetCursorScreenPos(backup_pos);
   5543             ImVec4 previewing_ref_col;
   5544             memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
   5545             ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags);
   5546             PopID();
   5547         }
   5548         PopItemWidth();
   5549     }
   5550     if (allow_opt_alpha_bar)
   5551     {
   5552         if (allow_opt_picker) Separator();
   5553         CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
   5554     }
   5555     EndPopup();
   5556 }
   5557 
   5558 //-------------------------------------------------------------------------
   5559 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
   5560 //-------------------------------------------------------------------------
   5561 // - TreeNode()
   5562 // - TreeNodeV()
   5563 // - TreeNodeEx()
   5564 // - TreeNodeExV()
   5565 // - TreeNodeBehavior() [Internal]
   5566 // - TreePush()
   5567 // - TreePop()
   5568 // - GetTreeNodeToLabelSpacing()
   5569 // - SetNextItemOpen()
   5570 // - CollapsingHeader()
   5571 //-------------------------------------------------------------------------
   5572 
   5573 bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
   5574 {
   5575     va_list args;
   5576     va_start(args, fmt);
   5577     bool is_open = TreeNodeExV(str_id, 0, fmt, args);
   5578     va_end(args);
   5579     return is_open;
   5580 }
   5581 
   5582 bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
   5583 {
   5584     va_list args;
   5585     va_start(args, fmt);
   5586     bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
   5587     va_end(args);
   5588     return is_open;
   5589 }
   5590 
   5591 bool ImGui::TreeNode(const char* label)
   5592 {
   5593     ImGuiWindow* window = GetCurrentWindow();
   5594     if (window->SkipItems)
   5595         return false;
   5596     return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
   5597 }
   5598 
   5599 bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
   5600 {
   5601     return TreeNodeExV(str_id, 0, fmt, args);
   5602 }
   5603 
   5604 bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
   5605 {
   5606     return TreeNodeExV(ptr_id, 0, fmt, args);
   5607 }
   5608 
   5609 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
   5610 {
   5611     ImGuiWindow* window = GetCurrentWindow();
   5612     if (window->SkipItems)
   5613         return false;
   5614 
   5615     return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
   5616 }
   5617 
   5618 bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
   5619 {
   5620     va_list args;
   5621     va_start(args, fmt);
   5622     bool is_open = TreeNodeExV(str_id, flags, fmt, args);
   5623     va_end(args);
   5624     return is_open;
   5625 }
   5626 
   5627 bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
   5628 {
   5629     va_list args;
   5630     va_start(args, fmt);
   5631     bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
   5632     va_end(args);
   5633     return is_open;
   5634 }
   5635 
   5636 bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
   5637 {
   5638     ImGuiWindow* window = GetCurrentWindow();
   5639     if (window->SkipItems)
   5640         return false;
   5641 
   5642     ImGuiContext& g = *GImGui;
   5643     const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
   5644     return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
   5645 }
   5646 
   5647 bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
   5648 {
   5649     ImGuiWindow* window = GetCurrentWindow();
   5650     if (window->SkipItems)
   5651         return false;
   5652 
   5653     ImGuiContext& g = *GImGui;
   5654     const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
   5655     return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
   5656 }
   5657 
   5658 bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
   5659 {
   5660     if (flags & ImGuiTreeNodeFlags_Leaf)
   5661         return true;
   5662 
   5663     // We only write to the tree storage if the user clicks (or explicitly use the SetNextItemOpen function)
   5664     ImGuiContext& g = *GImGui;
   5665     ImGuiWindow* window = g.CurrentWindow;
   5666     ImGuiStorage* storage = window->DC.StateStorage;
   5667 
   5668     bool is_open;
   5669     if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen)
   5670     {
   5671         if (g.NextItemData.OpenCond & ImGuiCond_Always)
   5672         {
   5673             is_open = g.NextItemData.OpenVal;
   5674             storage->SetInt(id, is_open);
   5675         }
   5676         else
   5677         {
   5678             // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
   5679             const int stored_value = storage->GetInt(id, -1);
   5680             if (stored_value == -1)
   5681             {
   5682                 is_open = g.NextItemData.OpenVal;
   5683                 storage->SetInt(id, is_open);
   5684             }
   5685             else
   5686             {
   5687                 is_open = stored_value != 0;
   5688             }
   5689         }
   5690     }
   5691     else
   5692     {
   5693         is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
   5694     }
   5695 
   5696     // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
   5697     // NB- If we are above max depth we still allow manually opened nodes to be logged.
   5698     if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
   5699         is_open = true;
   5700 
   5701     return is_open;
   5702 }
   5703 
   5704 bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
   5705 {
   5706     ImGuiWindow* window = GetCurrentWindow();
   5707     if (window->SkipItems)
   5708         return false;
   5709 
   5710     ImGuiContext& g = *GImGui;
   5711     const ImGuiStyle& style = g.Style;
   5712     const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
   5713     const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
   5714 
   5715     if (!label_end)
   5716         label_end = FindRenderedTextEnd(label);
   5717     const ImVec2 label_size = CalcTextSize(label, label_end, false);
   5718 
   5719     // We vertically grow up to current line height up the typical widget height.
   5720     const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
   5721     ImRect frame_bb;
   5722     frame_bb.Min.x = (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
   5723     frame_bb.Min.y = window->DC.CursorPos.y;
   5724     frame_bb.Max.x = window->WorkRect.Max.x;
   5725     frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
   5726     if (display_frame)
   5727     {
   5728         // Framed header expand a little outside the default padding, to the edge of InnerClipRect
   5729         // (FIXME: May remove this at some point and make InnerClipRect align with WindowPadding.x instead of WindowPadding.x*0.5f)
   5730         frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f);
   5731         frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f);
   5732     }
   5733 
   5734     const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2);           // Collapser arrow width + Spacing
   5735     const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset);                    // Latch before ItemSize changes it
   5736     const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f);  // Include collapser
   5737     ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);
   5738     ItemSize(ImVec2(text_width, frame_height), padding.y);
   5739 
   5740     // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
   5741     ImRect interact_bb = frame_bb;
   5742     if (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth)) == 0)
   5743         interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f;
   5744 
   5745     // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
   5746     // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
   5747     // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
   5748     const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
   5749     bool is_open = TreeNodeBehaviorIsOpen(id, flags);
   5750     if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
   5751         window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
   5752 
   5753     bool item_add = ItemAdd(interact_bb, id);
   5754     window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
   5755     window->DC.LastItemDisplayRect = frame_bb;
   5756 
   5757     if (!item_add)
   5758     {
   5759         if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
   5760             TreePushOverrideID(id);
   5761         IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
   5762         return is_open;
   5763     }
   5764 
   5765     ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
   5766     if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
   5767         button_flags |= ImGuiButtonFlags_AllowItemOverlap;
   5768     if (!is_leaf)
   5769         button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
   5770 
   5771     // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
   5772     // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
   5773     // When clicking on the rest of the tree node we always disallow keyboard modifiers.
   5774     const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
   5775     const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;
   5776     const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);
   5777     if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
   5778         button_flags |= ImGuiButtonFlags_NoKeyModifiers;
   5779 
   5780     // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
   5781     // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
   5782     // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
   5783     // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
   5784     // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
   5785     // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
   5786     // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
   5787     // It is rather standard that arrow click react on Down rather than Up.
   5788     // We set ImGuiButtonFlags_PressedOnClickRelease on OpenOnDoubleClick because we want the item to be active on the initial MouseDown in order for drag and drop to work.
   5789     if (is_mouse_x_over_arrow)
   5790         button_flags |= ImGuiButtonFlags_PressedOnClick;
   5791     else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
   5792         button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
   5793     else
   5794         button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
   5795 
   5796     bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
   5797     const bool was_selected = selected;
   5798 
   5799     bool hovered, held;
   5800     bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
   5801     bool toggled = false;
   5802     if (!is_leaf)
   5803     {
   5804         if (pressed && g.DragDropHoldJustPressedId != id)
   5805         {
   5806             if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id))
   5807                 toggled = true;
   5808             if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
   5809                 toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
   5810             if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseDoubleClicked[0])
   5811                 toggled = true;
   5812         }
   5813         else if (pressed && g.DragDropHoldJustPressedId == id)
   5814         {
   5815             IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);
   5816             if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
   5817                 toggled = true;
   5818         }
   5819 
   5820         if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
   5821         {
   5822             toggled = true;
   5823             NavMoveRequestCancel();
   5824         }
   5825         if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
   5826         {
   5827             toggled = true;
   5828             NavMoveRequestCancel();
   5829         }
   5830 
   5831         if (toggled)
   5832         {
   5833             is_open = !is_open;
   5834             window->DC.StateStorage->SetInt(id, is_open);
   5835             window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
   5836         }
   5837     }
   5838     if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
   5839         SetItemAllowOverlap();
   5840 
   5841     // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger.
   5842     if (selected != was_selected) //-V547
   5843         window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
   5844 
   5845     // Render
   5846     const ImU32 text_col = GetColorU32(ImGuiCol_Text);
   5847     ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
   5848     if (display_frame)
   5849     {
   5850         // Framed type
   5851         const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
   5852         RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
   5853         RenderNavHighlight(frame_bb, id, nav_highlight_flags);
   5854         if (flags & ImGuiTreeNodeFlags_Bullet)
   5855             RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
   5856         else if (!is_leaf)
   5857             RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
   5858         else // Leaf without bullet, left-adjusted text
   5859             text_pos.x -= text_offset_x;
   5860         if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
   5861             frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
   5862 
   5863         if (g.LogEnabled)
   5864             LogSetNextTextDecoration("###", "###");
   5865         RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
   5866     }
   5867     else
   5868     {
   5869         // Unframed typed for tree nodes
   5870         if (hovered || selected)
   5871         {
   5872             const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
   5873             RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
   5874             RenderNavHighlight(frame_bb, id, nav_highlight_flags);
   5875         }
   5876         if (flags & ImGuiTreeNodeFlags_Bullet)
   5877             RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
   5878         else if (!is_leaf)
   5879             RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
   5880         if (g.LogEnabled)
   5881             LogSetNextTextDecoration(">", NULL);
   5882         RenderText(text_pos, label, label_end, false);
   5883     }
   5884 
   5885     if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
   5886         TreePushOverrideID(id);
   5887     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
   5888     return is_open;
   5889 }
   5890 
   5891 void ImGui::TreePush(const char* str_id)
   5892 {
   5893     ImGuiWindow* window = GetCurrentWindow();
   5894     Indent();
   5895     window->DC.TreeDepth++;
   5896     PushID(str_id ? str_id : "#TreePush");
   5897 }
   5898 
   5899 void ImGui::TreePush(const void* ptr_id)
   5900 {
   5901     ImGuiWindow* window = GetCurrentWindow();
   5902     Indent();
   5903     window->DC.TreeDepth++;
   5904     PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
   5905 }
   5906 
   5907 void ImGui::TreePushOverrideID(ImGuiID id)
   5908 {
   5909     ImGuiContext& g = *GImGui;
   5910     ImGuiWindow* window = g.CurrentWindow;
   5911     Indent();
   5912     window->DC.TreeDepth++;
   5913     window->IDStack.push_back(id);
   5914 }
   5915 
   5916 void ImGui::TreePop()
   5917 {
   5918     ImGuiContext& g = *GImGui;
   5919     ImGuiWindow* window = g.CurrentWindow;
   5920     Unindent();
   5921 
   5922     window->DC.TreeDepth--;
   5923     ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
   5924 
   5925     // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
   5926     if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
   5927         if (g.NavIdIsAlive && (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask))
   5928         {
   5929             SetNavID(window->IDStack.back(), g.NavLayer, 0, ImRect());
   5930             NavMoveRequestCancel();
   5931         }
   5932     window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1;
   5933 
   5934     IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
   5935     PopID();
   5936 }
   5937 
   5938 // Horizontal distance preceding label when using TreeNode() or Bullet()
   5939 float ImGui::GetTreeNodeToLabelSpacing()
   5940 {
   5941     ImGuiContext& g = *GImGui;
   5942     return g.FontSize + (g.Style.FramePadding.x * 2.0f);
   5943 }
   5944 
   5945 // Set next TreeNode/CollapsingHeader open state.
   5946 void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
   5947 {
   5948     ImGuiContext& g = *GImGui;
   5949     if (g.CurrentWindow->SkipItems)
   5950         return;
   5951     g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen;
   5952     g.NextItemData.OpenVal = is_open;
   5953     g.NextItemData.OpenCond = cond ? cond : ImGuiCond_Always;
   5954 }
   5955 
   5956 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
   5957 // This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
   5958 bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
   5959 {
   5960     ImGuiWindow* window = GetCurrentWindow();
   5961     if (window->SkipItems)
   5962         return false;
   5963 
   5964     return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
   5965 }
   5966 
   5967 // p_visible == NULL                        : regular collapsing header
   5968 // p_visible != NULL && *p_visible == true  : show a small close button on the corner of the header, clicking the button will set *p_visible = false
   5969 // p_visible != NULL && *p_visible == false : do not show the header at all
   5970 // Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.
   5971 bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)
   5972 {
   5973     ImGuiWindow* window = GetCurrentWindow();
   5974     if (window->SkipItems)
   5975         return false;
   5976 
   5977     if (p_visible && !*p_visible)
   5978         return false;
   5979 
   5980     ImGuiID id = window->GetID(label);
   5981     flags |= ImGuiTreeNodeFlags_CollapsingHeader;
   5982     if (p_visible)
   5983         flags |= ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_ClipLabelForTrailingButton;
   5984     bool is_open = TreeNodeBehavior(id, flags, label);
   5985     if (p_visible != NULL)
   5986     {
   5987         // Create a small overlapping close button
   5988         // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
   5989         // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
   5990         ImGuiContext& g = *GImGui;
   5991         ImGuiLastItemDataBackup last_item_backup;
   5992         float button_size = g.FontSize;
   5993         float button_x = ImMax(window->DC.LastItemRect.Min.x, window->DC.LastItemRect.Max.x - g.Style.FramePadding.x * 2.0f - button_size);
   5994         float button_y = window->DC.LastItemRect.Min.y;
   5995         ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
   5996         if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
   5997             *p_visible = false;
   5998         last_item_backup.Restore();
   5999     }
   6000 
   6001     return is_open;
   6002 }
   6003 
   6004 //-------------------------------------------------------------------------
   6005 // [SECTION] Widgets: Selectable
   6006 //-------------------------------------------------------------------------
   6007 // - Selectable()
   6008 //-------------------------------------------------------------------------
   6009 
   6010 // Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image.
   6011 // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
   6012 // With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowItemOverlap are also frequently used flags.
   6013 // FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.
   6014 bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
   6015 {
   6016     ImGuiWindow* window = GetCurrentWindow();
   6017     if (window->SkipItems)
   6018         return false;
   6019 
   6020     ImGuiContext& g = *GImGui;
   6021     const ImGuiStyle& style = g.Style;
   6022 
   6023     // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
   6024     ImGuiID id = window->GetID(label);
   6025     ImVec2 label_size = CalcTextSize(label, NULL, true);
   6026     ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
   6027     ImVec2 pos = window->DC.CursorPos;
   6028     pos.y += window->DC.CurrLineTextBaseOffset;
   6029     ItemSize(size, 0.0f);
   6030 
   6031     // Fill horizontal space
   6032     // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
   6033     const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
   6034     const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
   6035     const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
   6036     if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
   6037         size.x = ImMax(label_size.x, max_x - min_x);
   6038 
   6039     // Text stays at the submission position, but bounding box may be extended on both sides
   6040     const ImVec2 text_min = pos;
   6041     const ImVec2 text_max(min_x + size.x, pos.y + size.y);
   6042 
   6043     // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
   6044     ImRect bb(min_x, pos.y, text_max.x, text_max.y);
   6045     if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
   6046     {
   6047         const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
   6048         const float spacing_y = style.ItemSpacing.y;
   6049         const float spacing_L = IM_FLOOR(spacing_x * 0.50f);
   6050         const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
   6051         bb.Min.x -= spacing_L;
   6052         bb.Min.y -= spacing_U;
   6053         bb.Max.x += (spacing_x - spacing_L);
   6054         bb.Max.y += (spacing_y - spacing_U);
   6055     }
   6056     //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
   6057 
   6058     // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable..
   6059     const float backup_clip_rect_min_x = window->ClipRect.Min.x;
   6060     const float backup_clip_rect_max_x = window->ClipRect.Max.x;
   6061     if (span_all_columns)
   6062     {
   6063         window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
   6064         window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
   6065     }
   6066 
   6067     bool item_add;
   6068     if (flags & ImGuiSelectableFlags_Disabled)
   6069     {
   6070         ImGuiItemFlags backup_item_flags = g.CurrentItemFlags;
   6071         g.CurrentItemFlags |= ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNavDefaultFocus;
   6072         item_add = ItemAdd(bb, id);
   6073         g.CurrentItemFlags = backup_item_flags;
   6074     }
   6075     else
   6076     {
   6077         item_add = ItemAdd(bb, id);
   6078     }
   6079 
   6080     if (span_all_columns)
   6081     {
   6082         window->ClipRect.Min.x = backup_clip_rect_min_x;
   6083         window->ClipRect.Max.x = backup_clip_rect_max_x;
   6084     }
   6085 
   6086     if (!item_add)
   6087         return false;
   6088 
   6089     // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
   6090     // which would be advantageous since most selectable are not selected.
   6091     if (span_all_columns && window->DC.CurrentColumns)
   6092         PushColumnsBackground();
   6093     else if (span_all_columns && g.CurrentTable)
   6094         TablePushBackgroundChannel();
   6095 
   6096     // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
   6097     ImGuiButtonFlags button_flags = 0;
   6098     if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
   6099     if (flags & ImGuiSelectableFlags_SelectOnClick)     { button_flags |= ImGuiButtonFlags_PressedOnClick; }
   6100     if (flags & ImGuiSelectableFlags_SelectOnRelease)   { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
   6101     if (flags & ImGuiSelectableFlags_Disabled)          { button_flags |= ImGuiButtonFlags_Disabled; }
   6102     if (flags & ImGuiSelectableFlags_AllowDoubleClick)  { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
   6103     if (flags & ImGuiSelectableFlags_AllowItemOverlap)  { button_flags |= ImGuiButtonFlags_AllowItemOverlap; }
   6104 
   6105     if (flags & ImGuiSelectableFlags_Disabled)
   6106         selected = false;
   6107 
   6108     const bool was_selected = selected;
   6109     bool hovered, held;
   6110     bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
   6111 
   6112     // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
   6113     if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
   6114     {
   6115         if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
   6116         {
   6117             SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos));
   6118             g.NavDisableHighlight = true;
   6119         }
   6120     }
   6121     if (pressed)
   6122         MarkItemEdited(id);
   6123 
   6124     if (flags & ImGuiSelectableFlags_AllowItemOverlap)
   6125         SetItemAllowOverlap();
   6126 
   6127     // In this branch, Selectable() cannot toggle the selection so this will never trigger.
   6128     if (selected != was_selected) //-V547
   6129         window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
   6130 
   6131     // Render
   6132     if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld))
   6133         hovered = true;
   6134     if (hovered || selected)
   6135     {
   6136         const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
   6137         RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
   6138         RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
   6139     }
   6140 
   6141     if (span_all_columns && window->DC.CurrentColumns)
   6142         PopColumnsBackground();
   6143     else if (span_all_columns && g.CurrentTable)
   6144         TablePopBackgroundChannel();
   6145 
   6146     if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
   6147     RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
   6148     if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
   6149 
   6150     // Automatically close popups
   6151     if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.CurrentItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
   6152         CloseCurrentPopup();
   6153 
   6154     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
   6155     return pressed;
   6156 }
   6157 
   6158 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
   6159 {
   6160     if (Selectable(label, *p_selected, flags, size_arg))
   6161     {
   6162         *p_selected = !*p_selected;
   6163         return true;
   6164     }
   6165     return false;
   6166 }
   6167 
   6168 //-------------------------------------------------------------------------
   6169 // [SECTION] Widgets: ListBox
   6170 //-------------------------------------------------------------------------
   6171 // - BeginListBox()
   6172 // - EndListBox()
   6173 // - ListBox()
   6174 //-------------------------------------------------------------------------
   6175 
   6176 // Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
   6177 // Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height).
   6178 bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
   6179 {
   6180     ImGuiContext& g = *GImGui;
   6181     ImGuiWindow* window = GetCurrentWindow();
   6182     if (window->SkipItems)
   6183         return false;
   6184 
   6185     const ImGuiStyle& style = g.Style;
   6186     const ImGuiID id = GetID(label);
   6187     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   6188 
   6189     // Size default to hold ~7.25 items.
   6190     // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
   6191     ImVec2 size = ImFloor(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));
   6192     ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
   6193     ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
   6194     ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
   6195     g.NextItemData.ClearFlags();
   6196 
   6197     if (!IsRectVisible(bb.Min, bb.Max))
   6198     {
   6199         ItemSize(bb.GetSize(), style.FramePadding.y);
   6200         ItemAdd(bb, 0, &frame_bb);
   6201         return false;
   6202     }
   6203 
   6204     // FIXME-OPT: We could omit the BeginGroup() if label_size.x but would need to omit the EndGroup() as well.
   6205     BeginGroup();
   6206     if (label_size.x > 0.0f)
   6207     {
   6208         ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
   6209         RenderText(label_pos, label);
   6210         window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
   6211     }
   6212 
   6213     BeginChildFrame(id, frame_bb.GetSize());
   6214     return true;
   6215 }
   6216 
   6217 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   6218 // OBSOLETED in 1.81 (from February 2021)
   6219 bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
   6220 {
   6221     // If height_in_items == -1, default height is maximum 7.
   6222     ImGuiContext& g = *GImGui;
   6223     float height_in_items_f = (height_in_items < 0 ? ImMin(items_count, 7) : height_in_items) + 0.25f;
   6224     ImVec2 size;
   6225     size.x = 0.0f;
   6226     size.y = GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f;
   6227     return BeginListBox(label, size);
   6228 }
   6229 #endif
   6230 
   6231 void ImGui::EndListBox()
   6232 {
   6233     ImGuiContext& g = *GImGui;
   6234     ImGuiWindow* window = g.CurrentWindow;
   6235     IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?");
   6236     IM_UNUSED(window);
   6237 
   6238     EndChildFrame();
   6239     EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label
   6240 }
   6241 
   6242 bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
   6243 {
   6244     const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
   6245     return value_changed;
   6246 }
   6247 
   6248 // This is merely a helper around BeginListBox(), EndListBox().
   6249 // Considering using those directly to submit custom data or store selection differently.
   6250 bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
   6251 {
   6252     ImGuiContext& g = *GImGui;
   6253 
   6254     // Calculate size from "height_in_items"
   6255     if (height_in_items < 0)
   6256         height_in_items = ImMin(items_count, 7);
   6257     float height_in_items_f = height_in_items + 0.25f;
   6258     ImVec2 size(0.0f, ImFloor(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));
   6259 
   6260     if (!BeginListBox(label, size))
   6261         return false;
   6262 
   6263     // Assume all items have even height (= 1 line of text). If you need items of different height,
   6264     // you can create a custom version of ListBox() in your code without using the clipper.
   6265     bool value_changed = false;
   6266     ImGuiListClipper clipper;
   6267     clipper.Begin(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
   6268     while (clipper.Step())
   6269         for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
   6270         {
   6271             const char* item_text;
   6272             if (!items_getter(data, i, &item_text))
   6273                 item_text = "*Unknown item*";
   6274 
   6275             PushID(i);
   6276             const bool item_selected = (i == *current_item);
   6277             if (Selectable(item_text, item_selected))
   6278             {
   6279                 *current_item = i;
   6280                 value_changed = true;
   6281             }
   6282             if (item_selected)
   6283                 SetItemDefaultFocus();
   6284             PopID();
   6285         }
   6286     EndListBox();
   6287     if (value_changed)
   6288         MarkItemEdited(g.CurrentWindow->DC.LastItemId);
   6289 
   6290     return value_changed;
   6291 }
   6292 
   6293 //-------------------------------------------------------------------------
   6294 // [SECTION] Widgets: PlotLines, PlotHistogram
   6295 //-------------------------------------------------------------------------
   6296 // - PlotEx() [Internal]
   6297 // - PlotLines()
   6298 // - PlotHistogram()
   6299 //-------------------------------------------------------------------------
   6300 // Plot/Graph widgets are not very good.
   6301 // Consider writing your own, or using a third-party one, see:
   6302 // - ImPlot https://github.com/epezent/implot
   6303 // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions
   6304 //-------------------------------------------------------------------------
   6305 
   6306 int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size)
   6307 {
   6308     ImGuiContext& g = *GImGui;
   6309     ImGuiWindow* window = GetCurrentWindow();
   6310     if (window->SkipItems)
   6311         return -1;
   6312 
   6313     const ImGuiStyle& style = g.Style;
   6314     const ImGuiID id = window->GetID(label);
   6315 
   6316     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   6317     if (frame_size.x == 0.0f)
   6318         frame_size.x = CalcItemWidth();
   6319     if (frame_size.y == 0.0f)
   6320         frame_size.y = label_size.y + (style.FramePadding.y * 2);
   6321 
   6322     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
   6323     const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
   6324     const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
   6325     ItemSize(total_bb, style.FramePadding.y);
   6326     if (!ItemAdd(total_bb, 0, &frame_bb))
   6327         return -1;
   6328     const bool hovered = ItemHoverable(frame_bb, id);
   6329 
   6330     // Determine scale from values if not specified
   6331     if (scale_min == FLT_MAX || scale_max == FLT_MAX)
   6332     {
   6333         float v_min = FLT_MAX;
   6334         float v_max = -FLT_MAX;
   6335         for (int i = 0; i < values_count; i++)
   6336         {
   6337             const float v = values_getter(data, i);
   6338             if (v != v) // Ignore NaN values
   6339                 continue;
   6340             v_min = ImMin(v_min, v);
   6341             v_max = ImMax(v_max, v);
   6342         }
   6343         if (scale_min == FLT_MAX)
   6344             scale_min = v_min;
   6345         if (scale_max == FLT_MAX)
   6346             scale_max = v_max;
   6347     }
   6348 
   6349     RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
   6350 
   6351     const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
   6352     int idx_hovered = -1;
   6353     if (values_count >= values_count_min)
   6354     {
   6355         int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
   6356         int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
   6357 
   6358         // Tooltip on hover
   6359         if (hovered && inner_bb.Contains(g.IO.MousePos))
   6360         {
   6361             const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
   6362             const int v_idx = (int)(t * item_count);
   6363             IM_ASSERT(v_idx >= 0 && v_idx < values_count);
   6364 
   6365             const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
   6366             const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
   6367             if (plot_type == ImGuiPlotType_Lines)
   6368                 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
   6369             else if (plot_type == ImGuiPlotType_Histogram)
   6370                 SetTooltip("%d: %8.4g", v_idx, v0);
   6371             idx_hovered = v_idx;
   6372         }
   6373 
   6374         const float t_step = 1.0f / (float)res_w;
   6375         const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
   6376 
   6377         float v0 = values_getter(data, (0 + values_offset) % values_count);
   6378         float t0 = 0.0f;
   6379         ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle
   6380         float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f);   // Where does the zero line stands
   6381 
   6382         const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
   6383         const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
   6384 
   6385         for (int n = 0; n < res_w; n++)
   6386         {
   6387             const float t1 = t0 + t_step;
   6388             const int v1_idx = (int)(t0 * item_count + 0.5f);
   6389             IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
   6390             const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
   6391             const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
   6392 
   6393             // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
   6394             ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
   6395             ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
   6396             if (plot_type == ImGuiPlotType_Lines)
   6397             {
   6398                 window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
   6399             }
   6400             else if (plot_type == ImGuiPlotType_Histogram)
   6401             {
   6402                 if (pos1.x >= pos0.x + 2.0f)
   6403                     pos1.x -= 1.0f;
   6404                 window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
   6405             }
   6406 
   6407             t0 = t1;
   6408             tp0 = tp1;
   6409         }
   6410     }
   6411 
   6412     // Text overlay
   6413     if (overlay_text)
   6414         RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f));
   6415 
   6416     if (label_size.x > 0.0f)
   6417         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
   6418 
   6419     // Return hovered index or -1 if none are hovered.
   6420     // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx().
   6421     return idx_hovered;
   6422 }
   6423 
   6424 struct ImGuiPlotArrayGetterData
   6425 {
   6426     const float* Values;
   6427     int Stride;
   6428 
   6429     ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
   6430 };
   6431 
   6432 static float Plot_ArrayGetter(void* data, int idx)
   6433 {
   6434     ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
   6435     const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
   6436     return v;
   6437 }
   6438 
   6439 void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
   6440 {
   6441     ImGuiPlotArrayGetterData data(values, stride);
   6442     PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
   6443 }
   6444 
   6445 void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
   6446 {
   6447     PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
   6448 }
   6449 
   6450 void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
   6451 {
   6452     ImGuiPlotArrayGetterData data(values, stride);
   6453     PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
   6454 }
   6455 
   6456 void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
   6457 {
   6458     PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
   6459 }
   6460 
   6461 //-------------------------------------------------------------------------
   6462 // [SECTION] Widgets: Value helpers
   6463 // Those is not very useful, legacy API.
   6464 //-------------------------------------------------------------------------
   6465 // - Value()
   6466 //-------------------------------------------------------------------------
   6467 
   6468 void ImGui::Value(const char* prefix, bool b)
   6469 {
   6470     Text("%s: %s", prefix, (b ? "true" : "false"));
   6471 }
   6472 
   6473 void ImGui::Value(const char* prefix, int v)
   6474 {
   6475     Text("%s: %d", prefix, v);
   6476 }
   6477 
   6478 void ImGui::Value(const char* prefix, unsigned int v)
   6479 {
   6480     Text("%s: %d", prefix, v);
   6481 }
   6482 
   6483 void ImGui::Value(const char* prefix, float v, const char* float_format)
   6484 {
   6485     if (float_format)
   6486     {
   6487         char fmt[64];
   6488         ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
   6489         Text(fmt, prefix, v);
   6490     }
   6491     else
   6492     {
   6493         Text("%s: %.3f", prefix, v);
   6494     }
   6495 }
   6496 
   6497 //-------------------------------------------------------------------------
   6498 // [SECTION] MenuItem, BeginMenu, EndMenu, etc.
   6499 //-------------------------------------------------------------------------
   6500 // - ImGuiMenuColumns [Internal]
   6501 // - BeginMenuBar()
   6502 // - EndMenuBar()
   6503 // - BeginMainMenuBar()
   6504 // - EndMainMenuBar()
   6505 // - BeginMenu()
   6506 // - EndMenu()
   6507 // - MenuItem()
   6508 //-------------------------------------------------------------------------
   6509 
   6510 // Helpers for internal use
   6511 void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
   6512 {
   6513     IM_ASSERT(count == IM_ARRAYSIZE(Pos));
   6514     IM_UNUSED(count);
   6515     Width = NextWidth = 0.0f;
   6516     Spacing = spacing;
   6517     if (clear)
   6518         memset(NextWidths, 0, sizeof(NextWidths));
   6519     for (int i = 0; i < IM_ARRAYSIZE(Pos); i++)
   6520     {
   6521         if (i > 0 && NextWidths[i] > 0.0f)
   6522             Width += Spacing;
   6523         Pos[i] = IM_FLOOR(Width);
   6524         Width += NextWidths[i];
   6525         NextWidths[i] = 0.0f;
   6526     }
   6527 }
   6528 
   6529 float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
   6530 {
   6531     NextWidth = 0.0f;
   6532     NextWidths[0] = ImMax(NextWidths[0], w0);
   6533     NextWidths[1] = ImMax(NextWidths[1], w1);
   6534     NextWidths[2] = ImMax(NextWidths[2], w2);
   6535     for (int i = 0; i < IM_ARRAYSIZE(Pos); i++)
   6536         NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
   6537     return ImMax(Width, NextWidth);
   6538 }
   6539 
   6540 float ImGuiMenuColumns::CalcExtraSpace(float avail_w) const
   6541 {
   6542     return ImMax(0.0f, avail_w - Width);
   6543 }
   6544 
   6545 // FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
   6546 // Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
   6547 // Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary.
   6548 // Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
   6549 bool ImGui::BeginMenuBar()
   6550 {
   6551     ImGuiWindow* window = GetCurrentWindow();
   6552     if (window->SkipItems)
   6553         return false;
   6554     if (!(window->Flags & ImGuiWindowFlags_MenuBar))
   6555         return false;
   6556 
   6557     IM_ASSERT(!window->DC.MenuBarAppending);
   6558     BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
   6559     PushID("##menubar");
   6560 
   6561     // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
   6562     // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
   6563     ImRect bar_rect = window->MenuBarRect();
   6564     ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize), IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), IM_ROUND(bar_rect.Max.y));
   6565     clip_rect.ClipWith(window->OuterRectClipped);
   6566     PushClipRect(clip_rect.Min, clip_rect.Max, false);
   6567 
   6568     // We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags).
   6569     window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
   6570     window->DC.LayoutType = ImGuiLayoutType_Horizontal;
   6571     window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
   6572     window->DC.MenuBarAppending = true;
   6573     AlignTextToFramePadding();
   6574     return true;
   6575 }
   6576 
   6577 void ImGui::EndMenuBar()
   6578 {
   6579     ImGuiWindow* window = GetCurrentWindow();
   6580     if (window->SkipItems)
   6581         return;
   6582     ImGuiContext& g = *GImGui;
   6583 
   6584     // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
   6585     if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
   6586     {
   6587         ImGuiWindow* nav_earliest_child = g.NavWindow;
   6588         while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
   6589             nav_earliest_child = nav_earliest_child->ParentWindow;
   6590         if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
   6591         {
   6592             // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
   6593             // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
   6594             const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
   6595             IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check
   6596             FocusWindow(window);
   6597             SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
   6598             g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
   6599             g.NavDisableMouseHover = g.NavMousePosDirty = true;
   6600             g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
   6601             NavMoveRequestCancel();
   6602         }
   6603     }
   6604 
   6605     IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
   6606     IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
   6607     IM_ASSERT(window->DC.MenuBarAppending);
   6608     PopClipRect();
   6609     PopID();
   6610     window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
   6611     g.GroupStack.back().EmitItem = false;
   6612     EndGroup(); // Restore position on layer 0
   6613     window->DC.LayoutType = ImGuiLayoutType_Vertical;
   6614     window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
   6615     window->DC.MenuBarAppending = false;
   6616 }
   6617 
   6618 // Important: calling order matters!
   6619 // FIXME: Somehow overlapping with docking tech.
   6620 // FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)
   6621 bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)
   6622 {
   6623     IM_ASSERT(dir != ImGuiDir_None);
   6624 
   6625     ImGuiWindow* bar_window = FindWindowByName(name);
   6626     if (bar_window == NULL || bar_window->BeginCount == 0)
   6627     {
   6628         // Calculate and set window size/position
   6629         ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
   6630         ImRect avail_rect = viewport->GetBuildWorkRect();
   6631         ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
   6632         ImVec2 pos = avail_rect.Min;
   6633         if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
   6634             pos[axis] = avail_rect.Max[axis] - axis_size;
   6635         ImVec2 size = avail_rect.GetSize();
   6636         size[axis] = axis_size;
   6637         SetNextWindowPos(pos);
   6638         SetNextWindowSize(size);
   6639 
   6640         // Report our size into work area (for next frame) using actual window size
   6641         if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
   6642             viewport->BuildWorkOffsetMin[axis] += axis_size;
   6643         else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
   6644             viewport->BuildWorkOffsetMax[axis] -= axis_size;
   6645     }
   6646 
   6647     window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
   6648     PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
   6649     PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
   6650     bool is_open = Begin(name, NULL, window_flags);
   6651     PopStyleVar(2);
   6652 
   6653     return is_open;
   6654 }
   6655 
   6656 bool ImGui::BeginMainMenuBar()
   6657 {
   6658     ImGuiContext& g = *GImGui;
   6659     ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
   6660 
   6661     // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
   6662     // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
   6663     // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
   6664     g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
   6665     ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
   6666     float height = GetFrameHeight();
   6667     bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
   6668     g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
   6669 
   6670     if (is_open)
   6671         BeginMenuBar();
   6672     else
   6673         End();
   6674     return is_open;
   6675 }
   6676 
   6677 void ImGui::EndMainMenuBar()
   6678 {
   6679     EndMenuBar();
   6680 
   6681     // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
   6682     // FIXME: With this strategy we won't be able to restore a NULL focus.
   6683     ImGuiContext& g = *GImGui;
   6684     if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
   6685         FocusTopMostWindowUnderOne(g.NavWindow, NULL);
   6686 
   6687     End();
   6688 }
   6689 
   6690 bool ImGui::BeginMenu(const char* label, bool enabled)
   6691 {
   6692     ImGuiWindow* window = GetCurrentWindow();
   6693     if (window->SkipItems)
   6694         return false;
   6695 
   6696     ImGuiContext& g = *GImGui;
   6697     const ImGuiStyle& style = g.Style;
   6698     const ImGuiID id = window->GetID(label);
   6699     bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
   6700 
   6701     // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
   6702     ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
   6703     if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu))
   6704         flags |= ImGuiWindowFlags_ChildWindow;
   6705 
   6706     // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().
   6707     // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame.
   6708     // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used.
   6709     if (g.MenusIdSubmittedThisFrame.contains(id))
   6710     {
   6711         if (menu_is_open)
   6712             menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
   6713         else
   6714             g.NextWindowData.ClearFlags();          // we behave like Begin() and need to consume those values
   6715         return menu_is_open;
   6716     }
   6717 
   6718     // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu
   6719     g.MenusIdSubmittedThisFrame.push_back(id);
   6720 
   6721     ImVec2 label_size = CalcTextSize(label, NULL, true);
   6722     bool pressed;
   6723     bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back());
   6724     ImGuiWindow* backed_nav_window = g.NavWindow;
   6725     if (menuset_is_open)
   6726         g.NavWindow = window;  // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
   6727 
   6728     // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
   6729     // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
   6730     // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
   6731     ImVec2 popup_pos, pos = window->DC.CursorPos;
   6732     if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
   6733     {
   6734         // Menu inside an horizontal menu bar
   6735         // Selectable extend their highlight by half ItemSpacing in each direction.
   6736         // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
   6737         popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());
   6738         window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
   6739         PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
   6740         float w = label_size.x;
   6741         pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
   6742         PopStyleVar();
   6743         window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
   6744     }
   6745     else
   6746     {
   6747         // Menu inside a menu
   6748         // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
   6749         //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
   6750         popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
   6751         float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); // Feedback to next frame
   6752         float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
   6753         pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(min_w, 0.0f));
   6754         ImU32 text_col = GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled);
   6755         RenderArrow(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right);
   6756     }
   6757 
   6758     const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
   6759     if (menuset_is_open)
   6760         g.NavWindow = backed_nav_window;
   6761 
   6762     bool want_open = false;
   6763     bool want_close = false;
   6764     if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
   6765     {
   6766         // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
   6767         // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
   6768         bool moving_toward_other_child_menu = false;
   6769 
   6770         ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL;
   6771         if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar))
   6772         {
   6773             // FIXME-DPI: Values should be derived from a master "scale" factor.
   6774             ImRect next_window_rect = child_menu_window->Rect();
   6775             ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
   6776             ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
   6777             ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
   6778             float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f);    // add a bit of extra slack.
   6779             ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
   6780             tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f);                // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
   6781             tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
   6782             moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
   6783             //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
   6784         }
   6785         if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu)
   6786             want_close = true;
   6787 
   6788         if (!menu_is_open && hovered && pressed) // Click to open
   6789             want_open = true;
   6790         else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open
   6791             want_open = true;
   6792 
   6793         if (g.NavActivateId == id)
   6794         {
   6795             want_close = menu_is_open;
   6796             want_open = !menu_is_open;
   6797         }
   6798         if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
   6799         {
   6800             want_open = true;
   6801             NavMoveRequestCancel();
   6802         }
   6803     }
   6804     else
   6805     {
   6806         // Menu bar
   6807         if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
   6808         {
   6809             want_close = true;
   6810             want_open = menu_is_open = false;
   6811         }
   6812         else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
   6813         {
   6814             want_open = true;
   6815         }
   6816         else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
   6817         {
   6818             want_open = true;
   6819             NavMoveRequestCancel();
   6820         }
   6821     }
   6822 
   6823     if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
   6824         want_close = true;
   6825     if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))
   6826         ClosePopupToLevel(g.BeginPopupStack.Size, true);
   6827 
   6828     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
   6829 
   6830     if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
   6831     {
   6832         // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
   6833         OpenPopup(label);
   6834         return false;
   6835     }
   6836 
   6837     menu_is_open |= want_open;
   6838     if (want_open)
   6839         OpenPopup(label);
   6840 
   6841     if (menu_is_open)
   6842     {
   6843         SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
   6844         menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
   6845     }
   6846     else
   6847     {
   6848         g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
   6849     }
   6850 
   6851     return menu_is_open;
   6852 }
   6853 
   6854 void ImGui::EndMenu()
   6855 {
   6856     // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).
   6857     // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
   6858     // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
   6859     ImGuiContext& g = *GImGui;
   6860     ImGuiWindow* window = g.CurrentWindow;
   6861     if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
   6862     {
   6863         ClosePopupToLevel(g.BeginPopupStack.Size, true);
   6864         NavMoveRequestCancel();
   6865     }
   6866 
   6867     EndPopup();
   6868 }
   6869 
   6870 bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
   6871 {
   6872     ImGuiWindow* window = GetCurrentWindow();
   6873     if (window->SkipItems)
   6874         return false;
   6875 
   6876     ImGuiContext& g = *GImGui;
   6877     ImGuiStyle& style = g.Style;
   6878     ImVec2 pos = window->DC.CursorPos;
   6879     ImVec2 label_size = CalcTextSize(label, NULL, true);
   6880 
   6881     // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
   6882     // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.
   6883     ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
   6884     bool pressed;
   6885     if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
   6886     {
   6887         // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
   6888         // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.
   6889         float w = label_size.x;
   6890         window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
   6891         PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
   6892         pressed = Selectable(label, selected, flags, ImVec2(w, 0.0f));
   6893         PopStyleVar();
   6894         window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
   6895     }
   6896     else
   6897     {
   6898         // Menu item inside a vertical menu
   6899         // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
   6900         //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
   6901         float shortcut_w = shortcut ? CalcTextSize(shortcut, NULL).x : 0.0f;
   6902         float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame
   6903         float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
   6904         pressed = Selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f));
   6905         if (shortcut_w > 0.0f)
   6906         {
   6907             PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
   6908             RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
   6909             PopStyleColor();
   6910         }
   6911         if (selected)
   6912             RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize  * 0.866f);
   6913     }
   6914 
   6915     IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
   6916     return pressed;
   6917 }
   6918 
   6919 bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
   6920 {
   6921     if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
   6922     {
   6923         if (p_selected)
   6924             *p_selected = !*p_selected;
   6925         return true;
   6926     }
   6927     return false;
   6928 }
   6929 
   6930 //-------------------------------------------------------------------------
   6931 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
   6932 //-------------------------------------------------------------------------
   6933 // - BeginTabBar()
   6934 // - BeginTabBarEx() [Internal]
   6935 // - EndTabBar()
   6936 // - TabBarLayout() [Internal]
   6937 // - TabBarCalcTabID() [Internal]
   6938 // - TabBarCalcMaxTabWidth() [Internal]
   6939 // - TabBarFindTabById() [Internal]
   6940 // - TabBarRemoveTab() [Internal]
   6941 // - TabBarCloseTab() [Internal]
   6942 // - TabBarScrollClamp() [Internal]
   6943 // - TabBarScrollToTab() [Internal]
   6944 // - TabBarQueueChangeTabOrder() [Internal]
   6945 // - TabBarScrollingButtons() [Internal]
   6946 // - TabBarTabListPopupButton() [Internal]
   6947 //-------------------------------------------------------------------------
   6948 
   6949 struct ImGuiTabBarSection
   6950 {
   6951     int                 TabCount;               // Number of tabs in this section.
   6952     float               Width;                  // Sum of width of tabs in this section (after shrinking down)
   6953     float               Spacing;                // Horizontal spacing at the end of the section.
   6954 
   6955     ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
   6956 };
   6957 
   6958 namespace ImGui
   6959 {
   6960     static void             TabBarLayout(ImGuiTabBar* tab_bar);
   6961     static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);
   6962     static float            TabBarCalcMaxTabWidth();
   6963     static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
   6964     static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
   6965     static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);
   6966     static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
   6967 }
   6968 
   6969 ImGuiTabBar::ImGuiTabBar()
   6970 {
   6971     memset(this, 0, sizeof(*this));
   6972     CurrFrameVisible = PrevFrameVisible = -1;
   6973     LastTabItemIdx = -1;
   6974 }
   6975 
   6976 static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
   6977 {
   6978     return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
   6979 }
   6980 
   6981 static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
   6982 {
   6983     const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
   6984     const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
   6985     const int a_section = TabItemGetSectionIdx(a);
   6986     const int b_section = TabItemGetSectionIdx(b);
   6987     if (a_section != b_section)
   6988         return a_section - b_section;
   6989     return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
   6990 }
   6991 
   6992 static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)
   6993 {
   6994     const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
   6995     const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
   6996     return (int)(a->BeginOrder - b->BeginOrder);
   6997 }
   6998 
   6999 static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
   7000 {
   7001     ImGuiContext& g = *GImGui;
   7002     return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
   7003 }
   7004 
   7005 static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
   7006 {
   7007     ImGuiContext& g = *GImGui;
   7008     if (g.TabBars.Contains(tab_bar))
   7009         return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
   7010     return ImGuiPtrOrIndex(tab_bar);
   7011 }
   7012 
   7013 bool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
   7014 {
   7015     ImGuiContext& g = *GImGui;
   7016     ImGuiWindow* window = g.CurrentWindow;
   7017     if (window->SkipItems)
   7018         return false;
   7019 
   7020     ImGuiID id = window->GetID(str_id);
   7021     ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
   7022     ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
   7023     tab_bar->ID = id;
   7024     return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);
   7025 }
   7026 
   7027 bool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
   7028 {
   7029     ImGuiContext& g = *GImGui;
   7030     ImGuiWindow* window = g.CurrentWindow;
   7031     if (window->SkipItems)
   7032         return false;
   7033 
   7034     if ((flags & ImGuiTabBarFlags_DockNode) == 0)
   7035         PushOverrideID(tab_bar->ID);
   7036 
   7037     // Add to stack
   7038     g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
   7039     g.CurrentTabBar = tab_bar;
   7040 
   7041     // Append with multiple BeginTabBar()/EndTabBar() pairs.
   7042     tab_bar->BackupCursorPos = window->DC.CursorPos;
   7043     if (tab_bar->CurrFrameVisible == g.FrameCount)
   7044     {
   7045         window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
   7046         tab_bar->BeginCount++;
   7047         return true;
   7048     }
   7049 
   7050     // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable
   7051     if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
   7052         if (tab_bar->Tabs.Size > 1)
   7053             ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
   7054     tab_bar->TabsAddedNew = false;
   7055 
   7056     // Flags
   7057     if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
   7058         flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
   7059 
   7060     tab_bar->Flags = flags;
   7061     tab_bar->BarRect = tab_bar_bb;
   7062     tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
   7063     tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
   7064     tab_bar->CurrFrameVisible = g.FrameCount;
   7065     tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;
   7066     tab_bar->CurrTabsContentsHeight = 0.0f;
   7067     tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;
   7068     tab_bar->FramePadding = g.Style.FramePadding;
   7069     tab_bar->TabsActiveCount = 0;
   7070     tab_bar->BeginCount = 1;
   7071 
   7072     // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap
   7073     window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
   7074 
   7075     // Draw separator
   7076     const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive);
   7077     const float y = tab_bar->BarRect.Max.y - 1.0f;
   7078     {
   7079         const float separator_min_x = tab_bar->BarRect.Min.x - IM_FLOOR(window->WindowPadding.x * 0.5f);
   7080         const float separator_max_x = tab_bar->BarRect.Max.x + IM_FLOOR(window->WindowPadding.x * 0.5f);
   7081         window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
   7082     }
   7083     return true;
   7084 }
   7085 
   7086 void    ImGui::EndTabBar()
   7087 {
   7088     ImGuiContext& g = *GImGui;
   7089     ImGuiWindow* window = g.CurrentWindow;
   7090     if (window->SkipItems)
   7091         return;
   7092 
   7093     ImGuiTabBar* tab_bar = g.CurrentTabBar;
   7094     if (tab_bar == NULL)
   7095     {
   7096         IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
   7097         return;
   7098     }
   7099 
   7100     // Fallback in case no TabItem have been submitted
   7101     if (tab_bar->WantLayout)
   7102         TabBarLayout(tab_bar);
   7103 
   7104     // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
   7105     const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
   7106     if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
   7107     {
   7108         tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);
   7109         window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;
   7110     }
   7111     else
   7112     {
   7113         window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;
   7114     }
   7115     if (tab_bar->BeginCount > 1)
   7116         window->DC.CursorPos = tab_bar->BackupCursorPos;
   7117 
   7118     if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
   7119         PopID();
   7120 
   7121     g.CurrentTabBarStack.pop_back();
   7122     g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
   7123 }
   7124 
   7125 // This is called only once a frame before by the first call to ItemTab()
   7126 // The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
   7127 static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
   7128 {
   7129     ImGuiContext& g = *GImGui;
   7130     tab_bar->WantLayout = false;
   7131 
   7132     // Garbage collect by compacting list
   7133     // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section)
   7134     int tab_dst_n = 0;
   7135     bool need_sort_by_section = false;
   7136     ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing
   7137     for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
   7138     {
   7139         ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
   7140         if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)
   7141         {
   7142             // Remove tab
   7143             if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }
   7144             if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }
   7145             if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }
   7146             continue;
   7147         }
   7148         if (tab_dst_n != tab_src_n)
   7149             tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
   7150 
   7151         tab = &tab_bar->Tabs[tab_dst_n];
   7152         tab->IndexDuringLayout = (ImS16)tab_dst_n;
   7153 
   7154         // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
   7155         int curr_tab_section_n = TabItemGetSectionIdx(tab);
   7156         if (tab_dst_n > 0)
   7157         {
   7158             ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
   7159             int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
   7160             if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
   7161                 need_sort_by_section = true;
   7162             if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
   7163                 need_sort_by_section = true;
   7164         }
   7165 
   7166         sections[curr_tab_section_n].TabCount++;
   7167         tab_dst_n++;
   7168     }
   7169     if (tab_bar->Tabs.Size != tab_dst_n)
   7170         tab_bar->Tabs.resize(tab_dst_n);
   7171 
   7172     if (need_sort_by_section)
   7173         ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
   7174 
   7175     // Calculate spacing between sections
   7176     sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
   7177     sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
   7178 
   7179     // Setup next selected tab
   7180     ImGuiID scroll_to_tab_id = 0;
   7181     if (tab_bar->NextSelectedTabId)
   7182     {
   7183         tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
   7184         tab_bar->NextSelectedTabId = 0;
   7185         scroll_to_tab_id = tab_bar->SelectedTabId;
   7186     }
   7187 
   7188     // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
   7189     if (tab_bar->ReorderRequestTabId != 0)
   7190     {
   7191         if (TabBarProcessReorder(tab_bar))
   7192             if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)
   7193                 scroll_to_tab_id = tab_bar->ReorderRequestTabId;
   7194         tab_bar->ReorderRequestTabId = 0;
   7195     }
   7196 
   7197     // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
   7198     const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
   7199     if (tab_list_popup_button)
   7200         if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!
   7201             scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
   7202 
   7203     // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central
   7204     // (whereas our tabs are stored as: leading, central, trailing)
   7205     int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
   7206     g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
   7207 
   7208     // Compute ideal tabs widths + store them into shrink buffer
   7209     ImGuiTabItem* most_recently_selected_tab = NULL;
   7210     int curr_section_n = -1;
   7211     bool found_selected_tab_id = false;
   7212     for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
   7213     {
   7214         ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
   7215         IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
   7216 
   7217         if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button))
   7218             most_recently_selected_tab = tab;
   7219         if (tab->ID == tab_bar->SelectedTabId)
   7220             found_selected_tab_id = true;
   7221         if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)
   7222             scroll_to_tab_id = tab->ID;
   7223 
   7224         // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
   7225         // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
   7226         // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
   7227         const char* tab_name = tab_bar->GetTabName(tab);
   7228         const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true;
   7229         tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x;
   7230 
   7231         int section_n = TabItemGetSectionIdx(tab);
   7232         ImGuiTabBarSection* section = &sections[section_n];
   7233         section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
   7234         curr_section_n = section_n;
   7235 
   7236         // Store data so we can build an array sorted by width if we need to shrink tabs down
   7237         IM_MSVC_WARNING_SUPPRESS(6385);
   7238         int shrink_buffer_index = shrink_buffer_indexes[section_n]++;
   7239         g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n;
   7240         g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth;
   7241 
   7242         IM_ASSERT(tab->ContentWidth > 0.0f);
   7243         tab->Width = tab->ContentWidth;
   7244     }
   7245 
   7246     // Compute total ideal width (used for e.g. auto-resizing a window)
   7247     tab_bar->WidthAllTabsIdeal = 0.0f;
   7248     for (int section_n = 0; section_n < 3; section_n++)
   7249         tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;
   7250 
   7251     // Horizontal scrolling buttons
   7252     // (note that TabBarScrollButtons() will alter BarRect.Max.x)
   7253     if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll))
   7254         if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
   7255         {
   7256             scroll_to_tab_id = scroll_and_select_tab->ID;
   7257             if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)
   7258                 tab_bar->SelectedTabId = scroll_to_tab_id;
   7259         }
   7260 
   7261     // Shrink widths if full tabs don't fit in their allocated space
   7262     float section_0_w = sections[0].Width + sections[0].Spacing;
   7263     float section_1_w = sections[1].Width + sections[1].Spacing;
   7264     float section_2_w = sections[2].Width + sections[2].Spacing;
   7265     bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();
   7266     float width_excess;
   7267     if (central_section_is_visible)
   7268         width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section
   7269     else
   7270         width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
   7271 
   7272     // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
   7273     if (width_excess > 0.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))
   7274     {
   7275         int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);
   7276         int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
   7277         ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess);
   7278 
   7279         // Apply shrunk values into tabs and sections
   7280         for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)
   7281         {
   7282             ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
   7283             float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width);
   7284             if (shrinked_width < 0.0f)
   7285                 continue;
   7286 
   7287             int section_n = TabItemGetSectionIdx(tab);
   7288             sections[section_n].Width -= (tab->Width - shrinked_width);
   7289             tab->Width = shrinked_width;
   7290         }
   7291     }
   7292 
   7293     // Layout all active tabs
   7294     int section_tab_index = 0;
   7295     float tab_offset = 0.0f;
   7296     tab_bar->WidthAllTabs = 0.0f;
   7297     for (int section_n = 0; section_n < 3; section_n++)
   7298     {
   7299         ImGuiTabBarSection* section = &sections[section_n];
   7300         if (section_n == 2)
   7301             tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);
   7302 
   7303         for (int tab_n = 0; tab_n < section->TabCount; tab_n++)
   7304         {
   7305             ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
   7306             tab->Offset = tab_offset;
   7307             tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
   7308         }
   7309         tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
   7310         tab_offset += section->Spacing;
   7311         section_tab_index += section->TabCount;
   7312     }
   7313 
   7314     // If we have lost the selected tab, select the next most recently active one
   7315     if (found_selected_tab_id == false)
   7316         tab_bar->SelectedTabId = 0;
   7317     if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
   7318         scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
   7319 
   7320     // Lock in visible tab
   7321     tab_bar->VisibleTabId = tab_bar->SelectedTabId;
   7322     tab_bar->VisibleTabWasSubmitted = false;
   7323 
   7324     // Update scrolling
   7325     if (scroll_to_tab_id != 0)
   7326         TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
   7327     tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
   7328     tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
   7329     if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
   7330     {
   7331         // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
   7332         // Teleport if we are aiming far off the visible line
   7333         tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
   7334         tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
   7335         const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
   7336         tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);
   7337     }
   7338     else
   7339     {
   7340         tab_bar->ScrollingSpeed = 0.0f;
   7341     }
   7342     tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
   7343     tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
   7344 
   7345     // Clear name buffers
   7346     if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
   7347         tab_bar->TabsNames.Buf.resize(0);
   7348 
   7349     // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)
   7350     ImGuiWindow* window = g.CurrentWindow;
   7351     window->DC.CursorPos = tab_bar->BarRect.Min;
   7352     ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
   7353     window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);
   7354 }
   7355 
   7356 // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
   7357 static ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label)
   7358 {
   7359     if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
   7360     {
   7361         ImGuiID id = ImHashStr(label);
   7362         KeepAliveID(id);
   7363         return id;
   7364     }
   7365     else
   7366     {
   7367         ImGuiWindow* window = GImGui->CurrentWindow;
   7368         return window->GetID(label);
   7369     }
   7370 }
   7371 
   7372 static float ImGui::TabBarCalcMaxTabWidth()
   7373 {
   7374     ImGuiContext& g = *GImGui;
   7375     return g.FontSize * 20.0f;
   7376 }
   7377 
   7378 ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
   7379 {
   7380     if (tab_id != 0)
   7381         for (int n = 0; n < tab_bar->Tabs.Size; n++)
   7382             if (tab_bar->Tabs[n].ID == tab_id)
   7383                 return &tab_bar->Tabs[n];
   7384     return NULL;
   7385 }
   7386 
   7387 // The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
   7388 void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
   7389 {
   7390     if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
   7391         tab_bar->Tabs.erase(tab);
   7392     if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }
   7393     if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }
   7394     if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
   7395 }
   7396 
   7397 // Called on manual closure attempt
   7398 void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
   7399 {
   7400     IM_ASSERT(!(tab->Flags & ImGuiTabItemFlags_Button));
   7401     if (!(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
   7402     {
   7403         // This will remove a frame of lag for selecting another tab on closure.
   7404         // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure
   7405         tab->WantClose = true;
   7406         if (tab_bar->VisibleTabId == tab->ID)
   7407         {
   7408             tab->LastFrameVisible = -1;
   7409             tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
   7410         }
   7411     }
   7412     else
   7413     {
   7414         // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup)
   7415         if (tab_bar->VisibleTabId != tab->ID)
   7416             tab_bar->NextSelectedTabId = tab->ID;
   7417     }
   7418 }
   7419 
   7420 static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
   7421 {
   7422     scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());
   7423     return ImMax(scrolling, 0.0f);
   7424 }
   7425 
   7426 // Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys
   7427 static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)
   7428 {
   7429     ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
   7430     if (tab == NULL)
   7431         return;
   7432     if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
   7433         return;
   7434 
   7435     ImGuiContext& g = *GImGui;
   7436     float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar)
   7437     int order = tab_bar->GetTabOrder(tab);
   7438 
   7439     // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
   7440     // FIXME: This is all confusing.
   7441     float scrollable_width = tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;
   7442 
   7443     // We make all tabs positions all relative Sections[0].Width to make code simpler
   7444     float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);
   7445     float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f);
   7446     tab_bar->ScrollingTargetDistToVisibility = 0.0f;
   7447     if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))
   7448     {
   7449         // Scroll to the left
   7450         tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
   7451         tab_bar->ScrollingTarget = tab_x1;
   7452     }
   7453     else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)
   7454     {
   7455         // Scroll to the right
   7456         tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);
   7457         tab_bar->ScrollingTarget = tab_x2 - scrollable_width;
   7458     }
   7459 }
   7460 
   7461 void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset)
   7462 {
   7463     IM_ASSERT(offset != 0);
   7464     IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
   7465     tab_bar->ReorderRequestTabId = tab->ID;
   7466     tab_bar->ReorderRequestOffset = (ImS16)offset;
   7467 }
   7468 
   7469 void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* src_tab, ImVec2 mouse_pos)
   7470 {
   7471     ImGuiContext& g = *GImGui;
   7472     IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
   7473     if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
   7474         return;
   7475 
   7476     const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
   7477     const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
   7478 
   7479     // Count number of contiguous tabs we are crossing over
   7480     const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
   7481     const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
   7482     int dst_idx = src_idx;
   7483     for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
   7484     {
   7485         // Reordered tabs must share the same section
   7486         const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
   7487         if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
   7488             break;
   7489         if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
   7490             break;
   7491         dst_idx = i;
   7492 
   7493         // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
   7494         const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
   7495         const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
   7496         //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
   7497         if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
   7498             break;
   7499     }
   7500 
   7501     if (dst_idx != src_idx)
   7502         TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
   7503 }
   7504 
   7505 bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
   7506 {
   7507     ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);
   7508     if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))
   7509         return false;
   7510 
   7511     //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
   7512     int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestOffset;
   7513     if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
   7514         return false;
   7515 
   7516     // Reordered tabs must share the same section
   7517     // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
   7518     ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
   7519     if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
   7520         return false;
   7521     if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
   7522         return false;
   7523 
   7524     ImGuiTabItem item_tmp = *tab1;
   7525     ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
   7526     ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
   7527     const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
   7528     memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
   7529     *tab2 = item_tmp;
   7530 
   7531     if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
   7532         MarkIniSettingsDirty();
   7533     return true;
   7534 }
   7535 
   7536 static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
   7537 {
   7538     ImGuiContext& g = *GImGui;
   7539     ImGuiWindow* window = g.CurrentWindow;
   7540 
   7541     const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
   7542     const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
   7543 
   7544     const ImVec2 backup_cursor_pos = window->DC.CursorPos;
   7545     //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255));
   7546 
   7547     int select_dir = 0;
   7548     ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
   7549     arrow_col.w *= 0.5f;
   7550 
   7551     PushStyleColor(ImGuiCol_Text, arrow_col);
   7552     PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
   7553     const float backup_repeat_delay = g.IO.KeyRepeatDelay;
   7554     const float backup_repeat_rate = g.IO.KeyRepeatRate;
   7555     g.IO.KeyRepeatDelay = 0.250f;
   7556     g.IO.KeyRepeatRate = 0.200f;
   7557     float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);
   7558     window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);
   7559     if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
   7560         select_dir = -1;
   7561     window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);
   7562     if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
   7563         select_dir = +1;
   7564     PopStyleColor(2);
   7565     g.IO.KeyRepeatRate = backup_repeat_rate;
   7566     g.IO.KeyRepeatDelay = backup_repeat_delay;
   7567 
   7568     ImGuiTabItem* tab_to_scroll_to = NULL;
   7569     if (select_dir != 0)
   7570         if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
   7571         {
   7572             int selected_order = tab_bar->GetTabOrder(tab_item);
   7573             int target_order = selected_order + select_dir;
   7574 
   7575             // Skip tab item buttons until another tab item is found or end is reached
   7576             while (tab_to_scroll_to == NULL)
   7577             {
   7578                 // If we are at the end of the list, still scroll to make our tab visible
   7579                 tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order];
   7580 
   7581                 // Cross through buttons
   7582                 // (even if first/last item is a button, return it so we can update the scroll)
   7583                 if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)
   7584                 {
   7585                     target_order += select_dir;
   7586                     selected_order += select_dir;
   7587                     tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL;
   7588                 }
   7589             }
   7590         }
   7591     window->DC.CursorPos = backup_cursor_pos;
   7592     tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
   7593 
   7594     return tab_to_scroll_to;
   7595 }
   7596 
   7597 static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
   7598 {
   7599     ImGuiContext& g = *GImGui;
   7600     ImGuiWindow* window = g.CurrentWindow;
   7601 
   7602     // We use g.Style.FramePadding.y to match the square ArrowButton size
   7603     const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
   7604     const ImVec2 backup_cursor_pos = window->DC.CursorPos;
   7605     window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
   7606     tab_bar->BarRect.Min.x += tab_list_popup_button_width;
   7607 
   7608     ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
   7609     arrow_col.w *= 0.5f;
   7610     PushStyleColor(ImGuiCol_Text, arrow_col);
   7611     PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
   7612     bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);
   7613     PopStyleColor(2);
   7614 
   7615     ImGuiTabItem* tab_to_select = NULL;
   7616     if (open)
   7617     {
   7618         for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
   7619         {
   7620             ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
   7621             if (tab->Flags & ImGuiTabItemFlags_Button)
   7622                 continue;
   7623 
   7624             const char* tab_name = tab_bar->GetTabName(tab);
   7625             if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
   7626                 tab_to_select = tab;
   7627         }
   7628         EndCombo();
   7629     }
   7630 
   7631     window->DC.CursorPos = backup_cursor_pos;
   7632     return tab_to_select;
   7633 }
   7634 
   7635 //-------------------------------------------------------------------------
   7636 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
   7637 //-------------------------------------------------------------------------
   7638 // - BeginTabItem()
   7639 // - EndTabItem()
   7640 // - TabItemButton()
   7641 // - TabItemEx() [Internal]
   7642 // - SetTabItemClosed()
   7643 // - TabItemCalcSize() [Internal]
   7644 // - TabItemBackground() [Internal]
   7645 // - TabItemLabelAndCloseButton() [Internal]
   7646 //-------------------------------------------------------------------------
   7647 
   7648 bool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
   7649 {
   7650     ImGuiContext& g = *GImGui;
   7651     ImGuiWindow* window = g.CurrentWindow;
   7652     if (window->SkipItems)
   7653         return false;
   7654 
   7655     ImGuiTabBar* tab_bar = g.CurrentTabBar;
   7656     if (tab_bar == NULL)
   7657     {
   7658         IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
   7659         return false;
   7660     }
   7661     IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
   7662 
   7663     bool ret = TabItemEx(tab_bar, label, p_open, flags);
   7664     if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
   7665     {
   7666         ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
   7667         PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
   7668     }
   7669     return ret;
   7670 }
   7671 
   7672 void    ImGui::EndTabItem()
   7673 {
   7674     ImGuiContext& g = *GImGui;
   7675     ImGuiWindow* window = g.CurrentWindow;
   7676     if (window->SkipItems)
   7677         return;
   7678 
   7679     ImGuiTabBar* tab_bar = g.CurrentTabBar;
   7680     if (tab_bar == NULL)
   7681     {
   7682         IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
   7683         return;
   7684     }
   7685     IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
   7686     ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
   7687     if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
   7688         PopID();
   7689 }
   7690 
   7691 bool    ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
   7692 {
   7693     ImGuiContext& g = *GImGui;
   7694     ImGuiWindow* window = g.CurrentWindow;
   7695     if (window->SkipItems)
   7696         return false;
   7697 
   7698     ImGuiTabBar* tab_bar = g.CurrentTabBar;
   7699     if (tab_bar == NULL)
   7700     {
   7701         IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
   7702         return false;
   7703     }
   7704     return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder);
   7705 }
   7706 
   7707 bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags)
   7708 {
   7709     // Layout whole tab bar if not already done
   7710     if (tab_bar->WantLayout)
   7711         TabBarLayout(tab_bar);
   7712 
   7713     ImGuiContext& g = *GImGui;
   7714     ImGuiWindow* window = g.CurrentWindow;
   7715     if (window->SkipItems)
   7716         return false;
   7717 
   7718     const ImGuiStyle& style = g.Style;
   7719     const ImGuiID id = TabBarCalcTabID(tab_bar, label);
   7720 
   7721     // If the user called us with *p_open == false, we early out and don't render.
   7722     // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
   7723     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
   7724     if (p_open && !*p_open)
   7725     {
   7726         PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
   7727         ItemAdd(ImRect(), id);
   7728         PopItemFlag();
   7729         return false;
   7730     }
   7731 
   7732     IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));
   7733     IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing
   7734 
   7735     // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
   7736     if (flags & ImGuiTabItemFlags_NoCloseButton)
   7737         p_open = NULL;
   7738     else if (p_open == NULL)
   7739         flags |= ImGuiTabItemFlags_NoCloseButton;
   7740 
   7741     // Calculate tab contents size
   7742     ImVec2 size = TabItemCalcSize(label, p_open != NULL);
   7743 
   7744     // Acquire tab data
   7745     ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
   7746     bool tab_is_new = false;
   7747     if (tab == NULL)
   7748     {
   7749         tab_bar->Tabs.push_back(ImGuiTabItem());
   7750         tab = &tab_bar->Tabs.back();
   7751         tab->ID = id;
   7752         tab->Width = size.x;
   7753         tab_bar->TabsAddedNew = true;
   7754         tab_is_new = true;
   7755     }
   7756     tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
   7757     tab->ContentWidth = size.x;
   7758     tab->BeginOrder = tab_bar->TabsActiveCount++;
   7759 
   7760     const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
   7761     const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
   7762     const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
   7763     const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
   7764     tab->LastFrameVisible = g.FrameCount;
   7765     tab->Flags = flags;
   7766 
   7767     // Append name with zero-terminator
   7768     tab->NameOffset = (ImS16)tab_bar->TabsNames.size();
   7769     tab_bar->TabsNames.append(label, label + strlen(label) + 1);
   7770 
   7771     // Update selected tab
   7772     if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
   7773         if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
   7774             if (!is_tab_button)
   7775                 tab_bar->NextSelectedTabId = id;  // New tabs gets activated
   7776     if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // SetSelected can only be passed on explicit tab bar
   7777         if (!is_tab_button)
   7778             tab_bar->NextSelectedTabId = id;
   7779 
   7780     // Lock visibility
   7781     // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)
   7782     bool tab_contents_visible = (tab_bar->VisibleTabId == id);
   7783     if (tab_contents_visible)
   7784         tab_bar->VisibleTabWasSubmitted = true;
   7785 
   7786     // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
   7787     if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)
   7788         if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
   7789             tab_contents_visible = true;
   7790 
   7791     // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted
   7792     // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'.
   7793     if (tab_appearing && (!tab_bar_appearing || tab_is_new))
   7794     {
   7795         PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
   7796         ItemAdd(ImRect(), id);
   7797         PopItemFlag();
   7798         if (is_tab_button)
   7799             return false;
   7800         return tab_contents_visible;
   7801     }
   7802 
   7803     if (tab_bar->SelectedTabId == id)
   7804         tab->LastFrameSelected = g.FrameCount;
   7805 
   7806     // Backup current layout position
   7807     const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
   7808 
   7809     // Layout
   7810     const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
   7811     size.x = tab->Width;
   7812     if (is_central_section)
   7813         window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
   7814     else
   7815         window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
   7816     ImVec2 pos = window->DC.CursorPos;
   7817     ImRect bb(pos, pos + size);
   7818 
   7819     // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation)
   7820     const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);
   7821     if (want_clip_rect)
   7822         PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
   7823 
   7824     ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
   7825     ItemSize(bb.GetSize(), style.FramePadding.y);
   7826     window->DC.CursorMaxPos = backup_cursor_max_pos;
   7827 
   7828     if (!ItemAdd(bb, id))
   7829     {
   7830         if (want_clip_rect)
   7831             PopClipRect();
   7832         window->DC.CursorPos = backup_main_cursor_pos;
   7833         return tab_contents_visible;
   7834     }
   7835 
   7836     // Click to Select a tab
   7837     ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowItemOverlap);
   7838     if (g.DragDropActive)
   7839         button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
   7840     bool hovered, held;
   7841     bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
   7842     if (pressed && !is_tab_button)
   7843         tab_bar->NextSelectedTabId = id;
   7844     hovered |= (g.HoveredId == id);
   7845 
   7846     // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
   7847     if (g.ActiveId != id)
   7848         SetItemAllowOverlap();
   7849 
   7850     // Drag and drop: re-order tabs
   7851     if (held && !tab_appearing && IsMouseDragging(0))
   7852     {
   7853         if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
   7854         {
   7855             // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
   7856             if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
   7857             {
   7858                 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
   7859             }
   7860             else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
   7861             {
   7862                 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
   7863             }
   7864         }
   7865     }
   7866 
   7867 #if 0
   7868     if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
   7869     {
   7870         // Enlarge tab display when hovering
   7871         bb.Max.x = bb.Min.x + IM_FLOOR(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
   7872         display_draw_list = GetForegroundDrawList(window);
   7873         TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
   7874     }
   7875 #endif
   7876 
   7877     // Render tab shape
   7878     ImDrawList* display_draw_list = window->DrawList;
   7879     const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused));
   7880     TabItemBackground(display_draw_list, bb, flags, tab_col);
   7881     RenderNavHighlight(bb, id);
   7882 
   7883     // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
   7884     const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
   7885     if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)))
   7886         if (!is_tab_button)
   7887             tab_bar->NextSelectedTabId = id;
   7888 
   7889     if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
   7890         flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
   7891 
   7892     // Render tab label, process close button
   7893     const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0;
   7894     bool just_closed;
   7895     bool text_clipped;
   7896     TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);
   7897     if (just_closed && p_open != NULL)
   7898     {
   7899         *p_open = false;
   7900         TabBarCloseTab(tab_bar, tab);
   7901     }
   7902 
   7903     // Restore main window position so user can draw there
   7904     if (want_clip_rect)
   7905         PopClipRect();
   7906     window->DC.CursorPos = backup_main_cursor_pos;
   7907 
   7908     // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer)
   7909     // We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar (which g.HoveredId ignores)
   7910     if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered())
   7911         if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
   7912             SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
   7913 
   7914     IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected
   7915     if (is_tab_button)
   7916         return pressed;
   7917     return tab_contents_visible;
   7918 }
   7919 
   7920 // [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
   7921 // To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().
   7922 // Tabs closed by the close button will automatically be flagged to avoid this issue.
   7923 void    ImGui::SetTabItemClosed(const char* label)
   7924 {
   7925     ImGuiContext& g = *GImGui;
   7926     bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
   7927     if (is_within_manual_tab_bar)
   7928     {
   7929         ImGuiTabBar* tab_bar = g.CurrentTabBar;
   7930         ImGuiID tab_id = TabBarCalcTabID(tab_bar, label);
   7931         if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
   7932             tab->WantClose = true; // Will be processed by next call to TabBarLayout()
   7933     }
   7934 }
   7935 
   7936 ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
   7937 {
   7938     ImGuiContext& g = *GImGui;
   7939     ImVec2 label_size = CalcTextSize(label, NULL, true);
   7940     ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
   7941     if (has_close_button)
   7942         size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
   7943     else
   7944         size.x += g.Style.FramePadding.x + 1.0f;
   7945     return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
   7946 }
   7947 
   7948 void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
   7949 {
   7950     // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it.
   7951     ImGuiContext& g = *GImGui;
   7952     const float width = bb.GetWidth();
   7953     IM_UNUSED(flags);
   7954     IM_ASSERT(width > 0.0f);
   7955     const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
   7956     const float y1 = bb.Min.y + 1.0f;
   7957     const float y2 = bb.Max.y - 1.0f;
   7958     draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
   7959     draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
   7960     draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
   7961     draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
   7962     draw_list->PathFillConvex(col);
   7963     if (g.Style.TabBorderSize > 0.0f)
   7964     {
   7965         draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
   7966         draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
   7967         draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
   7968         draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
   7969         draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
   7970     }
   7971 }
   7972 
   7973 // Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
   7974 // We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
   7975 void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped)
   7976 {
   7977     ImGuiContext& g = *GImGui;
   7978     ImVec2 label_size = CalcTextSize(label, NULL, true);
   7979 
   7980     if (out_just_closed)
   7981         *out_just_closed = false;
   7982     if (out_text_clipped)
   7983         *out_text_clipped = false;
   7984 
   7985     if (bb.GetWidth() <= 1.0f)
   7986         return;
   7987 
   7988     // In Style V2 we'll have full override of all colors per state (e.g. focused, selected)
   7989     // But right now if you want to alter text color of tabs this is what you need to do.
   7990 #if 0
   7991     const float backup_alpha = g.Style.Alpha;
   7992     if (!is_contents_visible)
   7993         g.Style.Alpha *= 0.7f;
   7994 #endif
   7995 
   7996     // Render text label (with clipping + alpha gradient) + unsaved marker
   7997     const char* TAB_UNSAVED_MARKER = "*";
   7998     ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);
   7999     if (flags & ImGuiTabItemFlags_UnsavedDocument)
   8000     {
   8001         text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x;
   8002         ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + frame_padding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + frame_padding.y + IM_FLOOR(-g.FontSize * 0.25f));
   8003         RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL);
   8004     }
   8005     ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
   8006 
   8007     // Return clipped state ignoring the close button
   8008     if (out_text_clipped)
   8009     {
   8010         *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x;
   8011         //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255));
   8012     }
   8013 
   8014     // Close Button
   8015     // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
   8016     //  'hovered' will be true when hovering the Tab but NOT when hovering the close button
   8017     //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
   8018     //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
   8019     bool close_button_pressed = false;
   8020     bool close_button_visible = false;
   8021     if (close_button_id != 0)
   8022         if (is_contents_visible || bb.GetWidth() >= g.Style.TabMinWidthForCloseButton)
   8023             if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id)
   8024                 close_button_visible = true;
   8025     if (close_button_visible)
   8026     {
   8027         ImGuiLastItemDataBackup last_item_backup;
   8028         const float close_button_sz = g.FontSize;
   8029         PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding);
   8030         if (CloseButton(close_button_id, ImVec2(bb.Max.x - frame_padding.x * 2.0f - close_button_sz, bb.Min.y)))
   8031             close_button_pressed = true;
   8032         PopStyleVar();
   8033         last_item_backup.Restore();
   8034 
   8035         // Close with middle mouse button
   8036         if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
   8037             close_button_pressed = true;
   8038 
   8039         text_pixel_clip_bb.Max.x -= close_button_sz;
   8040     }
   8041 
   8042     // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist..
   8043     float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;
   8044     RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size);
   8045 
   8046 #if 0
   8047     if (!is_contents_visible)
   8048         g.Style.Alpha = backup_alpha;
   8049 #endif
   8050 
   8051     if (out_just_closed)
   8052         *out_just_closed = close_button_pressed;
   8053 }
   8054 
   8055 
   8056 #endif // #ifndef IMGUI_DISABLE